{"id":25924959,"url":"https://github.com/yavorfingarov/embeddedsql","last_synced_at":"2026-06-09T05:31:49.672Z","repository":{"id":279793940,"uuid":"939888467","full_name":"yavorfingarov/EmbeddedSql","owner":"yavorfingarov","description":"A clean and organized way to manage SQL statements and database migrations using .sql files.","archived":false,"fork":false,"pushed_at":"2025-02-27T13:23:49.000Z","size":58,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"master","last_synced_at":"2025-02-27T18:17:29.119Z","etag":null,"topics":["db-migrations","dotnet","sql"],"latest_commit_sha":null,"homepage":"","language":"C#","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/yavorfingarov.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2025-02-27T09:19:53.000Z","updated_at":"2025-02-27T13:22:52.000Z","dependencies_parsed_at":"2025-02-27T18:17:57.265Z","dependency_job_id":"c9cccaf1-0298-4f8e-8938-7010266324c9","html_url":"https://github.com/yavorfingarov/EmbeddedSql","commit_stats":null,"previous_names":["yavorfingarov/embeddedsql"],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/yavorfingarov%2FEmbeddedSql","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/yavorfingarov%2FEmbeddedSql/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/yavorfingarov%2FEmbeddedSql/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/yavorfingarov%2FEmbeddedSql/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/yavorfingarov","download_url":"https://codeload.github.com/yavorfingarov/EmbeddedSql/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":241718425,"owners_count":20008573,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["db-migrations","dotnet","sql"],"created_at":"2025-03-03T18:33:26.783Z","updated_at":"2026-06-09T05:31:49.667Z","avatar_url":"https://github.com/yavorfingarov.png","language":"C#","funding_links":[],"categories":[],"sub_categories":[],"readme":"# EmbeddedSql\n\n[![nuget](https://img.shields.io/nuget/v/EmbeddedSql)](https://www.nuget.org/packages/EmbeddedSql)\n[![downloads](https://img.shields.io/nuget/dt/EmbeddedSql?color=blue)](https://www.nuget.org/stats/packages/EmbeddedSql?groupby=Version)\n[![build](https://img.shields.io/github/actions/workflow/status/yavorfingarov/EmbeddedSql/cd.yml?branch=master)](https://github.com/yavorfingarov/EmbeddedSql/actions/workflows/cd.yml?query=branch%3Amaster)\n[![loc](https://img.shields.io/endpoint?url=https://gist.githubusercontent.com/yavorfingarov/479024692dc528451f53707bff2b443a/raw/lines-of-code.json)](https://github.com/yavorfingarov/EmbeddedSql/actions/workflows/cd.yml?query=branch%3Amaster)\n[![maintainability](https://img.shields.io/endpoint?url=https://gist.githubusercontent.com/yavorfingarov/479024692dc528451f53707bff2b443a/raw/maintainability.json)](https://github.com/yavorfingarov/EmbeddedSql/actions/workflows/cd.yml?query=branch%3Amaster)\n[![test coverage](https://img.shields.io/endpoint?url=https://gist.githubusercontent.com/yavorfingarov/479024692dc528451f53707bff2b443a/raw/test-coverage.json)](https://github.com/yavorfingarov/EmbeddedSql/actions/workflows/cd.yml?query=branch%3Amaster)\n[![mutation score](https://img.shields.io/endpoint?url=https://gist.githubusercontent.com/yavorfingarov/479024692dc528451f53707bff2b443a/raw/mutation-score.json)](https://github.com/yavorfingarov/EmbeddedSql/actions/workflows/cd.yml?query=branch%3Amaster)\n[![openssf scorecard](https://img.shields.io/ossf-scorecard/github.com/yavorfingarov/EmbeddedSql?label=openssf+scorecard)](https://scorecard.dev/viewer/?uri=github.com/yavorfingarov/EmbeddedSql\u0026sort_by=risk-level\u0026sort_direction=desc)\n\nEmbeddedSql is a NuGet package that provides a clean and organized way to manage SQL statements and database migrations using `.sql` files. It works on top of `System.Data` abstractions, making it database provider and ORM agnostic.\n\n## Rationale\n\nHaving SQL statements as plain strings scattered around your codebase is not only ugly but error-prone as well. When all of them are organized in `.sql` files, you can have a better overview, reduce the cognitive overhead of mixed languages, employ proper code style, and have nice syntax highlighting.\n\n## Setup\n\n* Install the [EmbeddedSql NuGet package](https://www.nuget.org/packages/EmbeddedSql).\n\n* Make sure all `.sql` files are going to be embedded in the assembly by adding the following lines to your `.csproj` file:\n\n```xml\n\u003cItemGroup\u003e\n  \u003cEmbeddedResource Include=\"**/*.sql\" /\u003e\n\u003c/ItemGroup\u003e\n```\n\n* Add EmbeddedSql services:\n\n```csharp\nbuilder.Services.AddEmbeddedSql();\n```\n\n* Alternatively, you can also point specific assemblies for scanning and apply filename-based filtering:\n\n```csharp\nbuilder.Services.AddEmbeddedSql(options =\u003e \n{\n    options.Assemblies = new[] { typeof(TestApi.Common.Entry).Assembly };\n    options.UseFilter(resourceName =\u003e !resourceName.Contains(\"Scripts\"));\n});\n```\n\n## SQL statements\n\nA special type of comment with three dashes `---` denotes a key for a SQL statement.\n\n\u003e [!NOTE]\n\u003e All code samples use [Dapper](https://github.com/DapperLib/Dapper) and [SQLite](https://www.sqlite.org/index.html).\n\n```sql\n--- AppUser.Get\n\nSELECT Id, FirstName, LastName\nFROM AppUser\nWHERE Id = @Id\n```\n\nOn application startup, a singleton service `ISql` is registered in the DI container. All embedded `.sql` resources are scanned and stored internally in a [FrozenDictionary](https://learn.microsoft.com/en-us/dotnet/api/system.collections.frozen.frozendictionary-2?view=net-9.0) for better performance.\n\n\u003e [!NOTE]\n\u003e The name of the files containing SQL statements doesn't matter.\n\nGetting SQL statements is then straightforward:\n\n```csharp\napp.MapGet(\"/api/users/{id}\", (string id, IDbConnection db, ISql sql) =\u003e\n{\n    var user = db.QuerySingleOrDefault\u003cUser\u003e(sql[\"AppUser.Get\"], new { Id = id });\n    if (user == null)\n    {\n        return Results.NotFound();\n    }\n\n    return Results.Ok(user);\n});\n```\n\n### Unsafe format\n\nThe `UnsafeFormat` overloads offer a great way to construct SQL statements dynamically.\n\nConsider the following statement:\n\n```sql\n--- AppUser.Search\n\nSELECT Id, FirstName, LastName\nFROM AppUser\nWHERE {0}\n```\n\nYou can use it like that:\n\n```csharp\napp.MapGet(\"/api/users/search\", (string? firstName, string? lastName, IDbConnection db, ISql sql) =\u003e\n{\n    var parameters = new Dictionary\u003cstring, object\u003e();\n    var predicates = new List\u003cstring\u003e();\n    if (firstName != null)\n    {\n        parameters.Add(\"FirstName\", firstName);\n        predicates.Add(\"FirstName = @FirstName\");\n    }\n\n    if (lastName != null)\n    {\n        parameters.Add(\"LastName\", lastName);\n        predicates.Add(\"LastName = @LastName\");\n    }\n\n    if (parameters.Count == 0)\n    {\n        return Results.BadRequest();\n    }\n\n    var condition = string.Join(\" AND \", predicates);\n    var query = sql.UnsafeFormat(\"AppUser.Search\", condition);\n    var users = db.Query\u003cUser\u003e(query, parameters);\n\n    return Results.Ok(users);\n});\n```\n\n\u003e [!WARNING]\n\u003e Never pass non-validated user-provided values into this method. Doing so may expose your application to SQL injection attacks.\n\n## Migrations\n\nEmbeddedSql offers a simple way to handle your database migrations.\n\n```csharp\nprivate static void MigrateDb(this WebApplication app)\n{\n    using var scope = app.Services.CreateScope();\n    var migrator = scope.ServiceProvider.GetRequiredService\u003cIMigrator\u003e();\n    migrator.Run();\n}\n\n// OR\n\nprivate static async Task MigrateDb(this WebApplication app)\n{\n    await using var scope = app.Services.CreateAsyncScope();\n    var migrator = scope.ServiceProvider.GetRequiredService\u003cIMigrator\u003e();\n    await migrator.RunAsync();\n}\n\n```\n\nA scoped `IMigrator` service is registered by default. Without further customization, it will create a table for tracking all applied migrations. You should provide its definition and the statements to work with it. Furthermore, the migrator will expect to find an `IDbConnection` in the DI container.\n\n```sql\n--- _Migration.EnsureTable\n\nCREATE TABLE IF NOT EXISTS _Migration (\n    Id TEXT NOT NULL,\n    CONSTRAINT PK__Migration PRIMARY KEY (Id)\n)\n\n--- _Migration.GetAll\n\nSELECT Id\nFROM _Migration\n\n--- _Migration.Create\n\nINSERT INTO _Migration (Id)\nVALUES (@Id)\n```\n\n\u003e [!NOTE]\n\u003e The script for ensuring the migration tracking table is run every time, so it should be indempotent.\n\nBy default, all statements that are prefixed with `Migration.` are considered migration scripts.\n\n```sql\n--- Migration.AppUser.0001_Init\n\nCREATE TABLE AppUser (\n    Id TEXT NOT NULL,\n    FirstName TEXT NOT NULL,\n    LastName TEXT NOT NULL,\n    CONSTRAINT PK_AppUser PRIMARY KEY (Id)\n)\n```\n\nMigration scripts are going to be applied in alphabetical order.\n\n### Customization\n\nYou can customize many aspects of the behavior of the migrator.\n\n```csharp\nbuilder.Services.AddEmbeddedSql(options =\u003e \n{\n    options.ConfigureMigrator(migratorOptions =\u003e\n    {\n        // ...\n    });\n});\n```\n\n#### Idempotent scripts\n\nBy default, the migrator will expect non-idempotent scripts. You can change this:\n\n```csharp\nmigratorOptions.Idempotent = true;\n```\n\nThis would mean that no migration tracking table will be created.\n\n#### Transaction behavior\n\nBy default, every script is wrapped in a transaction. Instead, you can also wrap all migrations in a single transaction:\n\n```csharp\nmigratorOptions.TransactionBehavior = TransactionBehavior.Overarching;\n```\n\nAlternatively, you can disable transactions altogether:\n\n```csharp\nmigratorOptions.TransactionBehavior = TransactionBehavior.None;\n```\n\n\u003e [!WARNING]\n\u003e Please make sure you know how DDL statements are handled by your db.\n\u003e\n\u003e Currently, the only check that the migrator does is whether your provider is MySql/MariaDb and logs a warning if you are using transactions due to [implicit commit](https://dev.mysql.com/doc/refman/5.7/en/implicit-commit.html).\n\n#### Script naming\n\nIf the default naming conventions don't fit well in your codebase, you can change them:\n\n```csharp\nmigratorOptions.EnsureMigrationTableCommand = \"_MyMigration.EnsureTable\";\nmigratorOptions.GetMigrationsQuery = \"_MyMigration.GetAll\";\nmigratorOptions.CreateMigrationCommand = \"_MyMigration.Create\";\nmigratorOptions.MigrationScriptPrefix = \"MyMigration.\";\n```\n\nAlso, since the migration tracking table is defined by you, you can pimp it up too:\n\n```sql\n--- _MyMigration.EnsureTable\n\nCREATE TABLE IF NOT EXISTS _Migration (\n    Id TEXT NOT NULL,\n    Timestamp TEXT NOT NULL,\n    CONSTRAINT PK__Migration PRIMARY KEY (Id)\n)\n\n--- _MyMigration.Create\n\nINSERT INTO _Migration (Id, Timestamp)\nVALUES (@Id, @Timestamp)\n```\n\nThen you should change the parameters for the create migration command accordingly.\n\n```csharp\nmigratorOptions.ConfigureCreateMigrationCommandParameters((parameters, migration) =\u003e\n{\n    parameters[\"@Id\"] = migration;\n    parameters[\"@Timestamp\"] = DateTime.UtcNow;\n});\n```\n\n## Keyed services\n\nYou can set a service key for `ISql` and `IMigrator` when registering:\n\n```csharp\nbuilder.Services.AddEmbeddedSql(options =\u003e \n{\n    options.ServiceKey = \"AppUsers\";\n});\n```\n\nThe migrator will first try to resolve the `IDbConnection` with the same key, and if such is not found, it will fall back to the keyless one. This way you can have many sets of SQL statements, migrators, and database connections.\n\n## Additional resources\n\n* [API reference](https://github.com/yavorfingarov/EmbeddedSql/blob/master/docs/EmbeddedSql.md)\n\n* [Sample app](https://github.com/yavorfingarov/EmbeddedSql/tree/master/samples/EmbeddedSql.SampleApi)\n\n* [Changelog](https://github.com/yavorfingarov/EmbeddedSql/blob/master/CHANGELOG.md)\n\n* [License](https://github.com/yavorfingarov/EmbeddedSql/blob/master/LICENSE)\n\n## Feedback\n\n* [Issue tracker](https://github.com/yavorfingarov/EmbeddedSql/issues)\n\n* [Discussions](https://github.com/yavorfingarov/EmbeddedSql/discussions)\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fyavorfingarov%2Fembeddedsql","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fyavorfingarov%2Fembeddedsql","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fyavorfingarov%2Fembeddedsql/lists"}