{"id":20983695,"url":"https://github.com/andersnm/typedsql","last_synced_at":"2025-08-23T13:17:03.143Z","repository":{"id":66334508,"uuid":"202984696","full_name":"andersnm/TypedSql","owner":"andersnm","description":null,"archived":false,"fork":false,"pushed_at":"2024-01-16T19:21:02.000Z","size":256,"stargazers_count":4,"open_issues_count":1,"forks_count":2,"subscribers_count":2,"default_branch":"develop","last_synced_at":"2025-04-20T03:46:06.315Z","etag":null,"topics":["database","micro-orm","mysql","orm","sql","sql-server","sqlserver"],"latest_commit_sha":null,"homepage":null,"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/andersnm.png","metadata":{"files":{"readme":"README.md","changelog":null,"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":"2019-08-18T09:38:27.000Z","updated_at":"2025-03-08T03:07:42.000Z","dependencies_parsed_at":null,"dependency_job_id":"e82e3cc6-f23e-4f15-b7b3-513b02666f7f","html_url":"https://github.com/andersnm/TypedSql","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/andersnm%2FTypedSql","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/andersnm%2FTypedSql/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/andersnm%2FTypedSql/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/andersnm%2FTypedSql/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/andersnm","download_url":"https://codeload.github.com/andersnm/TypedSql/tar.gz/refs/heads/develop","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":254183231,"owners_count":22028455,"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":["database","micro-orm","mysql","orm","sql","sql-server","sqlserver"],"created_at":"2024-11-19T05:49:48.928Z","updated_at":"2025-05-14T16:32:45.375Z","avatar_url":"https://github.com/andersnm.png","language":"C#","readme":"# TypedSql\n\n**EXPERIMENTAL** Write database queries in C# and syntax as close to real SQL as possible.\n\n[![NuGet](https://img.shields.io/nuget/v/TypedSql.svg)](https://www.nuget.org/packages/TypedSql)\n[![Build status](https://ci.appveyor.com/api/projects/status/luv9d8m96u2nweqk?svg=true)](https://ci.appveyor.com/project/andersnm/typedsql)\n[![Code coverage](https://codecov.io/gh/andersnm/TypedSql/branch/master/graph/badge.svg)](https://codecov.io/gh/andersnm/TypedSql)\n\n## About\n\nThe primary focus of TypedSql is to write readable and maintainable SQL queries. Object-relational mapping is generally left to the user, although TypedSql is capable of returning complex object hierarchies without arrays.\nTypedSql is inspired by and somewhat similar to Entity Framework and Linq2Sql, but by design there is:\n\n- No change tracking =\u003e scales better\n- No navigation properties =\u003e explicit joins\n- No client evaluation =\u003e fewer surprises\n- No Linq =\u003e leaner abstraction\n\n## Features\n\n- SELECT, INSERT, UPDATE, DELETE\n- INNER JOIN, LEFT JOIN\n- GROUP BY, HAVING\n- ORDER BY, LIMIT, OFFSET\n- DECLARE, SET SQL variables\n- Aggregate SQL functions AVERAGE(), COUNT(), SUM(), MIN(), MAX()\n- Scalar SQL functions YEAR(), MONTH(), DAY(), HOUR(), MINUTE(), SECOND(), LAST_INSERT_ID()\n- Batch multiple SQL statements\n- Composable SQL subqueries\n- Compiled object materialization\n- Implementations for SQL Server, MySQL, PostgreSQL and in-memory\n- Migrations\n\n## Examples\n\nThe examples are based on the following data context definition:\n\n```c#\npublic class Product\n{\n    [PrimaryKey(AutoIncrement = true)]\n    public int ProductId { get; set; }\n    public string Name { get; set; }\n}\n\npublic class Unit\n{\n    [PrimaryKey(AutoIncrement = true)]\n    public int UnitId { get; set; }\n    public int ProductId { get; set; }\n    public string Name { get; set; }\n}\n\npublic class TestDataContext : DatabaseContext\n{\n    public FromQuery\u003cProduct\u003e Products { get; set; }\n    public FromQuery\u003cUnit\u003e Units { get; set; }\n}\n```\n\n### Basic example: SELECT ... WHERE ...\n\nQuery in C#:\n\n```c#\nvar runner = new InMemoryQueryRunner();\nvar db = new TestDataContext();\nvar stmtList = new StatementList();\nvar query = stmtList.Select(db.Products.Where(p =\u003e p.ProductId == 1));\n\nforeach (var row in runner.ExecuteQuery(query)) {\n    Console.WriteLine(\"{0}: {1}\", row.ProductId, row.Name);\n}\n```\n\nTranslated to SQL:\n\n```sql\nSELECT a.ProductId, a.ProductName\nFROM Product a\nWHERE a.ProductId = 1\n```\n\nThere are also extension methods `Select()`, `Update()`, `Insert()` and `Delete()` on the `IQueryRunner` interface for one-shot queries:\n\n```c#\nvar rows = runner.Select(db.Products.Where(p =\u003e p.ProductId == 1)).ToList();\n```\n\n### SELECT ... INNER JOIN [table]\n\nA table can be specified in the first parameter to `Join()`, which generates SQL with a plain join:\n\n```c#\nvar query = stmtList.Select(\n    db.Products\n        .Where(p =\u003e p.ProductId == 1)\n        .Join(\n            Db.Units,\n            (actx, a, bctx, b) =\u003e a.ProductId == b.ProductId,\n            (actx, a, bctx, b) =\u003e new {\n                a.ProductId,\n                a.ProductName,\n                b.UnitId,\n                b.UnitName\n            }\n        ));\n```\n\nTranslated to SQL:\n\n```sql\nSELECT a.ProductId, a.ProductName\nFROM Product a\nINNER JOIN Unit b ON a.ProductId = b.ProductId\nWHERE a.ProductId = 1\n```\n\n### SELECT ... INNER JOIN [subquery]\n\nAny query that is not a table object can be specified in the first parameter to `Join()`, which generates SQL with a joined subquery:\n\n```c#\nvar query = stmtList.Select(\n    db.Products\n        .Where(p =\u003e p.ProductId == 1)\n        .Join(\n            Db.Units.Project((ctx, u) =\u003e new { u.ProductId, u.Name }),\n            (actx, a, bctx, b) =\u003e a.ProductId == b.ProductId,\n            (actx, a, bctx, b) =\u003e new {\n                a.ProductId,\n                a.ProductName,\n                b.UnitId,\n                b.UnitName\n            }\n        ));\n```\n\nTranslated to SQL:\n\n```sql\nSELECT a.ProductId, a.ProductName\nFROM Product a\nINNER JOIN (SELECT b.ProductId, b.Name FROM Unit b) c ON a.ProductId = c.ProductId\nWHERE a.ProductId = 1\n```\n\n### SELECT ... LEFT JOIN\n\nThe joined side in a LEFT JOIN can be null, so field accesses in the query code must be null-checked.\nThe SQL generator recognizes null-checking conditionals, and generates SQL without any actual null checks, since this is handled transparently in the SQL language:\n\n```c#\nvar query = stmtList.Select(\n    db.Products\n        .Where(p =\u003e p.ProductId == 1)\n        .LeftJoin(\n            Db.Units,\n            (actx, a, bctx, b) =\u003e a.ProductId == b.ProductId,\n            (actx, a, bctx, b) =\u003e new {\n                a.ProductId,\n                a.ProductName,\n                UnitId = b != null ? (int?)b.UnitId : null,\n                UnitName = b != null ? b.UnitName : null,\n            }\n        ));\n```\n\nTranslated to SQL:\n\n```sql\nSELECT a.ProductId, a.ProductName, b.UnitId, b.UnitName\nFROM Product a\nLEFT JOIN Unit b ON a.ProductId = b.ProductId\nWHERE a.ProductId = 1\n```\n\n### SELECT ... GROUP BY\n\n```c#\nvar query = stmtList.Select(\n    db.Units\n        .Where(p =\u003e p.ProductId == 1)\n        .GroupBy(\n            a =\u003e new { a.ProductId },\n            (ctx, p) =\u003e new {\n                p.ProductId,\n                UnitCount = Function.Count(ctx, u =\u003e u.UnitId)\n            });\n```\n\nTranslated to SQL:\n\n```sql\nSELECT a.ProductId, COUNT(a.UnitId) AS UnitCount\nFROM Unit a\nGROUP BY a.ProductId\nWHERE a.ProductId = 1\n```\n\n### INSERT INTO ... VALUES ...\n\nInsert (and update) statements use the `InsertBuilder` class to assign to SQL fields in a typed way:\n\n```c#\nstmtList.Insert(\n    DB.Products, insert =\u003e\n        insert.Value(p =\u003e p.Name, \"Happy T-Shirt\"));\n```\n\nTranslated to SQL:\n\n```sql\nINSERT INTO Product (Name) VALUES (\"Happy T-Shirt\")\n```\n\n### INSERT INTO ... SELECT ...\n\n```c#\nstmtList.Insert(\n    DB.Products,\n    DB.Units,\n    (x, insert) =\u003e insert\n        .Value(p =\u003e p.Name, \"Product from \" + x.Name));\n```\n\nTranslated to SQL:\n\n```sql\nINSERT INTO Product (Name)\nSELECT CONCAT(\"Product from \", a.Name) AS Name\nFROM Unit a\n```\n\n### UPDATE\n\nUpdate (and insert) statements use the `InsertBuilder` class to assign to SQL fields in a typed way:\n\n```c#\nstmtList.Update(\n    DB.Products\n        .Where(p =\u003e p.ProductId == 1),\n    (p, builder) =\u003e builder\n        .Value(b =\u003e b.Name, p + \": Not tonight\"));\n```\n\nTranslated to SQL:\n\n```sql\nUPDATE Product\nSET Name = CONCAT(Name, \": Not tonight\")\nWHERE ProductId = 1\n```\n\n### SELECT ... FROM (SELECT ...)\n\nUse the `Select()` method to wrap a query in a subquery:\n\n```c#\nstmtList.Select(DB.Products.Select((ctx, p) =\u003e p));\n```\n\nTranslated to SQL:\n\n```sql\nSELECT a.ProductId, a.Name\nFROM (SELECT b.ProductId, b.Name FROM Product b) a\n```\n\n### SELECT (SELECT ...) FROM ...\n\nUse the `AsExpression()` method to treat a query as an expression:\n\n```c#\nstmtList.Select(\n    DB.Products.Project((ctx, p) =\u003e new {\n        p.ProductId,\n        SomeUnitId = DB.Units.Limit(1).Project(u =\u003e u.UnitId).AsExpression(ctx),\n    })\n);\n```\n\nTranslated to SQL:\n\n```sql\nSELECT a.ProductId, (SELECT b.UnitId FROM Unit b LIMIT 1) SomeUnitId\nFROM Product a\n```\n\n## Important classes\n\n### The DatabaseContext class\n\n`DatabaseContext` is the base class for a database schema. Any members having type `FromQuery` in derived classes are automatically instantiated by the constructor. This class is independent of the database connection.\n\n### The SelectorContext class\n\nMost query expression take a parameter of type `SelectorContext` or `SelectorContext\u003cT\u003e` for keeping track of intermediate state during in-memory evaluation.\nThe context is a required parameter in many `Function.*` helper methods like `Sum` or `Average`.\n\n### The InsertBuilder class\n\nThe `InsertBuilder` class is used in insert and update statements to assign to SQL fields in a typed way.\n\nUse the `Value()` method to assign a value to field. The syntax is a bit unusual, f.ex the following assigns a constant string to the ProductName property of a table type:\n\n```c#\nbuilder.Value(p =\u003e p.ProductName, \"New name\")` \n```\n\nUse the `Values()` method to copy fields and values to set from another InsertBuilder instance. F.ex to selectively update/insert specific fields:\n\n```c#\nvar productId = /* ... */\nvar productName = /* ... */\n\nvar builder = new InsertBuilder\u003cProduct\u003e();\nbuilder.Value(p =\u003e p.UpdateDate, DateTime.Now);\n\n// Only update if specified\nif (productName != null)\n{\n    builder.Value(p =\u003e p.ProductName, \"New name\")` \n}\n\nrunner.Update(\n    db.Products.Where(p =\u003e p.ProductId == productId),\n    (p, insert) =\u003e insert.Values(builder));\n```\n\n### The StatementList class\n\nThe `StatementList` class defines a batch of SQL statements to send to the database server.\n\n## Basic usage with SQL Server\n\nAdd a dependency on the `TypedSql.SqlServer` package.\n\n```c#\nusing TypedSql;\nusing TypedSql.SqlServer;\n// ...\nvar connection = new SqlConnection(connectionString);\nvar runner = new SqlServerQueryRunner(connection);\n// ...\nrunner.ExecuteNonQuery(stmtList);\n```\n\n## Basic usage with MySQL\n\nAdd a dependency on the `TypedSql.MySql` package.\n\nThe MySQL connection string must include the statement `AllowUserVariables=true;`.\n\n```c#\nusing TypedSql;\nusing TypedSql.MySql;\n// ...\nvar connection = new MySqlConnection(connectionString);\nvar runner = new MySqlQueryRunner(connection);\n// ...\nrunner.ExecuteNonQuery(stmtList);\n```\n\n## Basic usage with PostgreSQL\n\nAdd a dependency on the `TypedSql.PostgreSql` package.\n\n```c#\nusing TypedSql;\nusing TypedSql.PostgreSql;\n// ...\nvar connection = new NpgsqlConnection(connectionString);\nvar runner = new PostgreSqlQueryRunner(connection);\n// ...\nrunner.ExecuteNonQuery(stmtList);\n```\n\n## Basic in-memory usage\n\nThe in-memory runner is included in the `TypedSql` package.\n\nThe data context is the data store when using the in-memory query runner, and therefore a singleton.\n\n```c#\nusing TypedSql;\n// ...\nvar runner = new InMemoryQueryRunner();\n// ...\nrunner.ExecuteNonQuery(stmtList);\n```\n\n## Using with ASP.NET Core and MySQL\n\nRegister the connection and query runner as scoped. Register the data context as a singleton. In Startup.cs `ConfigureServices()`:\n\n```c#\nservices.AddScoped(provider =\u003e\n{\n    var connection = new MySqlConnection(Configuration[\"ConnectionString\"]);\n    connection.Open();\n    return connection;\n});\n\nservices.AddScoped\u003cIQueryRunner\u003e(provider =\u003e\n{\n    return new MySqlQueryRunner(provider.GetRequiredService\u003cMySqlConnection\u003e());\n});\n\nservices.AddSingleton\u003cTestDataContext\u003e();\n```\n\n## Default SQL types\n\n|.NET Type|SQL Server|MySQL|PostgreSQL|\n|-|-|-|-|\n|`bool`|`BIT`|`BIT`|`BOOLEAN`|\n|`byte`|`TINYINT`|`TINYINT UNSIGNED`|`SMALLINT` (!)|\n|`sbyte`|Throws|`TINYINT`|Throws|\n|`short`|`SMALLINT`|`SMALLINT`|`SMALLINT`|\n|`ushort`|Throws|`SMALLINT UNSIGNED`|Throws|\n|`int`|`INT`|`INT`|`INT`|\n|`uint`|Throws|`INT UNSIGNED`|Throws|\n|`long`|`BIGINT`|`BIGINT`|`BIGINT`|\n|`decimal`|`DECIMAL(13, 5)`|`DECIMAL(13, 5)`|`DECIMAL(13, 5)`|\n|`float`|`REAL`|`REAL`|`REAL`|\n|`double`|`REAL`|`REAL`|`DOUBLE PRECISION`|\n|`string`|`NVARCHAR(MAX)`|`VARCHAR(1024)`|`VARCHAR`|\n|`DateTime`|`DATETIME2`|`DATETIME`|`TIMESTAMP`|\n|`byte[]`|`VARBINARY(MAX)`|`MEDIUMBLOB`|`BYTEA`|\n\n## SQL type modifier attributes\n\nProperties may be decorated with attributes to specify the default types:\n\n```c#\npublic class Example {\n    // NVARCHAR(100) on SqlServer\n    // VARCHAR(100) on MySql\n    [SqlString(Length = 100, NVarChar = true)]\n    public string Length100Unicode { get; set; }\n\n    // DECIMAL(10,7)\n    [SqlDecimal(Precision = 10, Scale = 7)]\n    public decimal DecimalPrecision { get; set; }\n\n    // Nullable VARCHAR\n    [SqlNullable]\n    public string NullableString { get; set; }\n};\n```\n\n## SQL functions and operators\n\nTypedSql supports SQL functions and operators through a static `Function` class with the following methods:\n\n|.NET Method|SQL Equivalent|\n|-|-|\n|`Function.Count(ctx, selector)`|`COUNT()`|\n|`Function.Sum(ctx, selector)`|`SUM()`|\n|`Function.Average(ctx, selector)`|`AVG()`|\n|`Function.Min(ctx, selector)`|`MIN()`|\n|`Function.Max(ctx, selector)`|`MAX()`|\n|`Function.Like(lhs, rhs)`|`lhs LIKE rhs`|\n|`Function.Contains(ctx, value, subquery)`|`value IN (SELECT ...)`|\n|`Function.Contains(value, enumerable)`|`value IN (...)`|\n|`Function.LastInsertIdentity(ctx)`|`SCOPE_IDENTITY` in SQL Server\u003cbr\u003e`LAST_INSERT_ID` in MySQL|\n|`Function.Hour(dateTime)`|`HOUR()`|\n|`Function.Minute(dateTime)`|`MINUTE()`|\n|`Function.Second(dateTime)`|`SECOND()`|\n|`Function.Year(dateTime)`|`YEAR()`|\n|`Function.Month(dateTime)`|`MONTH()`|\n|`Function.Day(dateTime)`|`DAY()`|\n\n## Migrations\n\n### Migration tool\n\nThe migration tool requires .NET Core 3.0 SDK or newer, and is installed as a local tool in the database project directory.\n\nThe migration tool is a simple code generator and implements a single \"add-migration\" command which does the following:\n\n* Load the database project assembly\n* Scan for a class inheriting from DatabaseContext and existing migration classes\n* Generate a migration class with the current database state, and Up()/Down() methods migrating the database from the previous to the current state\n\nThere are limitations what kind of assemblies can be loaded dynamically by the tool. \nThe tool supports any .NET standard class library and most netcoreapp3.0 application assemblies. Only application assemblies referencing version 3.0 of the shared frameworks Microsoft.NETCore.App, Microsoft.AspNetCore.App and/or Microsoft.WindowsDesktop.App are supported.\n\nThis means f.ex when developing for ASP.NET Core 2.x and want to use TypedSql migrations, the DatabaseContext class should reside in a separate class library outside of the web project.\n\nSome times during development, users might want to unapply and remove a migration before generating a new migration with improvements. In these cases, please note the following:\n\n- The tool does not support to connect to a database and apply/unapply migrations. This is left to the user to implement.\n- The tool does not support to remove generated migration classes. Instead the user should delete the generated files.\n- Remember to build the database project after deleting a migration, before generating a new migration. Some times a full rebuild might be required for the build tools to detect deleted files.\n\nInstall and use the tool in a shell from the database project directory:\n\n```bash\n# Run this once if you haven't installed any local tools yet\ndotnet new tool-manifest\n\n# Run this once to install the TypedSql CLI tool\ndotnet tool install TypedSql.CliTool\n\n# Run this later to update the TypedSql CLI tool\ndotnet tool update TypedSql.CliTool\n\n# Show available commands\ndotnet typedsql --help\ndotnet typedsql add-migration --help\n\n# Generate a new migration class named \"Initial\" in ./Migrations\ndotnet typedsql add-migration -a ./path/to/your/assembly.dll -n Initial\n```\n\n### Applying migrations\n\nThe application can apply migrations using the `TypedSql.Migrator` class:\n\n```c#\nSqlQueryRunner runner = /* ... */\nvar migrator = new Migrator();\nmigrator.ReadAssemblyMigrations(typeof(MyDatabaseContext).Assembly);\nmigrator.ReadAppliedMigrations(runner);\nmigrator.MigrateToLatest(runner);\n```\n","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fandersnm%2Ftypedsql","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fandersnm%2Ftypedsql","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fandersnm%2Ftypedsql/lists"}