{"id":36924135,"url":"https://github.com/0x1000000/SqExpress","last_synced_at":"2026-01-19T18:00:44.355Z","repository":{"id":50150229,"uuid":"303515449","full_name":"0x1000000/SqExpress","owner":"0x1000000","description":"SqExpress is a sql query builder which allows creating SQL expressions directly in C# code with strong typing and intellisense.","archived":false,"fork":false,"pushed_at":"2025-08-24T19:37:25.000Z","size":1140,"stargazers_count":105,"open_issues_count":1,"forks_count":7,"subscribers_count":4,"default_branch":"main","last_synced_at":"2025-11-04T09:18:24.739Z","etag":null,"topics":["data-access","ms-sql","mysql","postgres-sql","sql-builder"],"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/0x1000000.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":"Roadmap.md","authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null}},"created_at":"2020-10-12T21:18:43.000Z","updated_at":"2025-06-09T21:01:25.000Z","dependencies_parsed_at":"2023-02-08T16:31:34.118Z","dependency_job_id":"3ffe033d-c4af-4afc-9b68-12ec8a981f8e","html_url":"https://github.com/0x1000000/SqExpress","commit_stats":{"total_commits":93,"total_committers":2,"mean_commits":46.5,"dds":"0.010752688172043001","last_synced_commit":"dc0c710666ce9eefa588cdb079173c14d39bfdfe"},"previous_names":[],"tags_count":17,"template":false,"template_full_name":null,"purl":"pkg:github/0x1000000/SqExpress","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/0x1000000%2FSqExpress","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/0x1000000%2FSqExpress/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/0x1000000%2FSqExpress/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/0x1000000%2FSqExpress/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/0x1000000","download_url":"https://codeload.github.com/0x1000000/SqExpress/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/0x1000000%2FSqExpress/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":28578952,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-01-19T17:42:58.221Z","status":"ssl_error","status_checked_at":"2026-01-19T17:40:54.158Z","response_time":67,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.6:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"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":["data-access","ms-sql","mysql","postgres-sql","sql-builder"],"created_at":"2026-01-12T19:00:25.487Z","updated_at":"2026-01-19T18:00:44.338Z","avatar_url":"https://github.com/0x1000000.png","language":"C#","readme":"# SqExpress\n![Logo](https://github.com/0x1000000/SqExpress/blob/main/SqExpress/Icon.png)\n\nThe library provides a generic SQL syntax tree with export to MS T-SQL, PostgreSQL, and MySQL text. It includes polyfills to compensate for features lacking in certain databases, such as the \"MERGE\" command. It also provides a set of builders and operators that will help you build complex SQL expressions.\n\nIt does not use LINQ, and your C# code will be as close to real SQL as possible. This makes it ideal when you need full SQL flexibility to create efficient DB requests.\n\nSqExpress comes with a simple but efficient data access mechanism that wraps ADO.Net DbConnection and can be used with MS SQL Client, Npgsql, or MySQL Connector.\n\nYou can use SqExpress together with the “Code First” concept when you declare SQL tables as C# classes with the possibility to generate recreation scripts for a target platform (MS SQL or PostgreSQL or MySQL).\n\nYou can also use it in conjunction with the “Database First” concept using an included code modification utility. The utility can also be used to generate flexible DTO classes with all required database mappings.\n\n## Video Tutorial\n1. [Basics of SqExpress](https://www.youtube.com/watch?v=Zd-fCb8NimA)\n2. [Working with Database Metadata Using SqExpress](https://youtu.be/vGVpTCt4aqc?si=AWK8GzvoiVlX7vET) (NEW!!)\n\n## Articles\n1. [\"Syntax Tree and Alternative to LINQ in Interaction with SQL Databases\"](https://itnext.io/syntax-tree-and-alternative-to-linq-in-interaction-with-sql-databases-656b78fe00dc?source=friends_link\u0026sk=f5f0587c08166d8824b96b48fe2cf33c) - explains the library principles;\n2. [\"Filtering by Dynamic Attributes\"](https://itnext.io/filtering-by-dynamic-attributes-90ada3504361?source=friends_link\u0026sk=35e273a9f499e6b62bacbac75873a7d2) - shows how to create dynamic queries using the library.\n\n## Demo Application\n\nYou can find a realistic usage of the library in this ASP.Net demo application - [SqGoods](https://github.com/0x1000000/SqGoods)\n\n# Content\n1. [Get Started](#get-started)\n\n### Basics\n\n2. [Recreating Table](#recreating-table)\n3. [Inserting Data](#inserting-data)\n4. [Selecting Data](#selecting-data)\n5. [Updating Data](#updating-data)\n6. [Deleting Data](#deleting-data)\n7. [More Tables and foreign keys](#more-tables-and-foreign-keys)\n8. [Joining Tables](#joining-tables)\n9. [Aliasing](#aliasing)\n10. [Derived Tables](#derived-tables)\n11. [Subquries](#subquries)\n12. [CTE](#cte)\n13. [Analytic And Window Functions](#analytic-and-window-functions)\n14. [Set Operators](#set-operators)\n\n### Advanced Data Modification\n\n15. [Merge](#merge)\n16. [Temporary Tables](#temporary-tables)\n17. [Database Data Export/Import](#database-data-export-import)\n18. [Getting and Comparing Database Table Metadata](#getting-and-comparing-database-table-metadata)\n\n### Database Table Metadata\n\n19. [Retrieving Database Table Metadata](#retrieving-database-table-metadata)\n\n### Working with Expressions\n\n20. [Syntax Tree](#syntax-tree) (Traversal and Modification)\n21. [Serialization to XML](#serialization-to-xml)\n22. [Serialization to JSON](#serialization-to-json)\n23. [Serialization to Plain List](#serialization-to-plain-list)\n\n### Code-generation\n\n24. [Table Descriptors Scaffolding](#table-descriptors-scaffolding)\n25. [DTOs Scaffolding](#dtos-scaffolding)\n26. [Model Selection](#model-selection)\n\n### Usage\n\n26. [Using in ASP.Net](#using-in-aspnet)\n27. [PostgreSQL](#postgresql)\n28. [MySQL](#mysql)\n29. [Auto-Mapper](#auto-mapper)\n\n# Get Started\n\nAdd a reference to [the library package on Nuget.org](https://www.nuget.org/packages/SqExpress/): \n```\nInstall-Package SqExpress\n```\nand start with \"Hello World\":\n```cs\nstatic void Main()\n{\n    var query = SqQueryBuilder.Select(\"Hello World!\").Done();\n\n    Console.WriteLine(TSqlExporter.Default.ToSql(query));\n}\n```\nNow let's get rid of the necessity in writing __\"SqQueryBuilder.\"__:\n```cs\nusing static SqExpress.SqQueryBuilder;\n...\n    var query = /*SqQueryBuilder.*/Select(\"Hello World!\").Done();\n\n    Console.WriteLine(TSqlExporter.Default.ToSql(query));\n\n```\nThe result will be:\n```\nSELECT 'Hello World!'\n```\n## (Re)Creating Table \nOk, let's try to select some data from a real table, but first we need to describe the table:\n\n*Note: Such classes can be auto-generated (updated) using information from an existing database. [See \"Table Descriptors Scaffolding\"](#table-descriptors-scaffolding)*\n\n```Cs\npublic class TableUser : TableBase\n{\n    public readonly Int32TableColumn UserId;\n    public readonly StringTableColumn FirstName;\n    public readonly StringTableColumn LastName;\n    //Audit Columns\n    public readonly Int32TableColumn Version;\n    public readonly DateTimeTableColumn ModifiedAt;\n\n    public TableUser(): this(default){}\n\n    public TableUser(Alias alias) : base(\"dbo\", \"User\", alias)\n    {\n        this.UserId = this.CreateInt32Column(\"UserId\", \n            ColumnMeta.PrimaryKey().Identity());\n\n        this.FirstName = this.CreateStringColumn(\"FirstName\", \n            size: 255, isUnicode: true);\n\n        this.LastName = this.CreateStringColumn(\"LastName\", \n            size: 255, isUnicode: true);\n\n        this.Version = this.CreateInt32Column(\"Version\",\n            ColumnMeta.DefaultValue(0));\n\n        this.ModifiedAt = this.CreateDateTimeColumn(\"ModifiedAt\",\n            columnMeta: ColumnMeta.DefaultValue(SqQueryBuilder.GetUtcDate()));\n\n        //Indexes\n        this.AddIndex(this.FirstName);\n        this.AddIndex(this.LastName);\n    }\n}\n```\nand if the table does not exist let's create it:\n```cs\nstatic async Task Main()\n{\n    using var connection = new SqlConnection(\"connection_string\");\n    {\n        using (var database = new SqDatabase\u003cSqlConnection\u003e(\n            connection: connection,\n            commandFactory: (conn, sql) \n                =\u003e new SqlCommand(cmdText: sql, connection: conn),\n            sqlExporter: TSqlExporter.Default))\n        {\n            var tUser = new TableUser();\n\n            await database.Statement(tUser.Script.DropAndCreate());\n        }\n    }\n}\n```\n*Note: See [PostgreSQL](#postgresql) or [MySQL](#mysql)* sections if you need to work with these databases.\n\n*Actual T-SQL:*\n```sql\n\nIF EXISTS\n(\n    SELECT TOP 1 1 \n    FROM [INFORMATION_SCHEMA].[TABLES] \n    WHERE [TABLE_SCHEMA]='dbo' AND [TABLE_NAME]='User'\n) \n    DROP TABLE [dbo].[User];\n\nCREATE TABLE [dbo].[User]\n(\n    [UserId] int NOT NULL  IDENTITY (1, 1),\n    [FirstName] [nvarchar](255) NOT NULL,\n    [LastName] [nvarchar](255) NOT NULL,\n    [Version] int NOT NULL,\n    [ModifiedAt] datetime NOT NULL,\n    CONSTRAINT [PK_dbo_User] PRIMARY KEY ([UserId])\n);\n```\n## Inserting Data\nNow it is the time to insert some date in the table:\n```cs\n...\nvar data = new[]\n{\n    new {FirstName = \"Francois\", LastName = \"Sturman\"},\n    new {FirstName = \"Allina\", LastName = \"Freeborne\"},\n    new {FirstName = \"Maye\", LastName = \"Maloy\"},\n};\n\nawait /*SqQueryBuilder.*/InsertDataInto(tUser, data)\n    .MapData(s =\u003e s\n        .Set(s.Target.FirstName, s.Source.FirstName)\n        .Set(s.Target.LastName, s.Source.LastName))\n    .AlsoInsert(s =\u003e s\n        .Set(s.Target.Version, 1)\n        .Set(s.Target.ModifiedAt, GetUtcDate()))\n    .Exec(database);\n...\n```\n*Actual T-SQL:*\n```sql\nINSERT INTO [dbo].[User]([FirstName],[LastName],[Version],[ModifiedAt]) \nSELECT [FirstName],[LastName],1,GETUTCDATE() \nFROM \n(VALUES \n    ('Francois','Sturman'),\n    ('Allina','Freeborne')\n    ,('Maye','Maloy')\n)[A0]([FirstName],[LastName])\n```\n## Selecting data\nand select it:\n```cs\nvar selectResult = await /*SqQueryBuilder.*/Select(tUser.UserId, tUser.FirstName, tUser.LastName)\n    .From(tUser)\n    .OrderBy(tUser.FirstName, tUser.LastName)\n    .QueryList(database,\n        r =\u003e (\n            Id: tUser.UserId.Read(r),\n            FirstName: tUser.FirstName.Read(r),\n            LastName: tUser.LastName.Read(r)));\nforeach (var record in selectResult)\n{\n    Console.WriteLine(record);\n}\n```\n*Actual T-SQL:*\n```sql\nSELECT [A0].[UserId],[A0].[FirstName],[A0].[LastName] \nFROM [dbo].[User] [A0] \nORDER BY [A0].[FirstName],[A0].[LastName]\n```\n*Result:*\n```\n(2, Allina, Freeborne)\n(1, Francois, Sturman)\n(3, Maye, Maloy)\n```\n## Updating data\nNow let's fix the typo:\n```cs\nawait /*SqQueryBuilder.*/Update(tUser)\n    .Set(tUser.LastName, \"Malloy\")\n    .Set(tUser.Version, tUser.Version+1)\n    .Set(tUser.ModifiedAt, GetUtcDate())\n    .Where(tUser.LastName == \"Maloy\")\n    .Exec(database);\n\n//Writing to console without storing data in memory\nawait /*SqQueryBuilder.*/Select(tUser.Columns)\n    .From(tUser)\n    .Query(database, record=\u003e\n    {\n        Console.Write(tUser.UserId.Read(record) + \",\");\n        Console.Write(tUser.FirstName.Read(record) + \" \");\n        Console.Write(tUser.LastName.Read(record) + \",\");\n        Console.Write(tUser.Version.Read(record) + \",\");\n        Console.WriteLine(tUser.ModifiedAt.Read(record).ToString(\"s\"));\n        return agg;\n    });\n```\n*Actual T-SQL:*\n```sql\nUPDATE [A0] SET \n    [A0].[LastName]='Malloy',\n    [A0].[Version]=[A0].[Version]+1,\n    [A0].[ModifiedAt]=GETUTCDATE() \nFROM [dbo].[User] [A0] \nWHERE [A0].[LastName]='Maloy'```\n```\n*Result:*\n```\n1,Francois Sturman,1,2020-10-12T11:32:16\n2,Allina Freeborne,1,2020-10-12T11:32:16\n3,Maye Malloy,2,2020-10-12T11:32:17\n```\n*Note: In addition to **Update** the library also has **Insert** and **IdentityInsert** helpers (see [Database Data Export/Import](#database-data-export-import) to find an example)*\n\n## Deleting data\nUnfortunately, regardless the fact the typo is fixed, we have to say \"Good Bye\" to May*:\n```cs\nawait /*SqQueryBuilder.*/Delete(tUser)\n    .Where(tUser.FirstName.Like(\"May%\"))\n    .Output(tUser.UserId)\n    .Query(database, (record)=\u003e\n    {\n        Console.WriteLine(\"Removed user id: \" + tUser.UserId.Read(record));\n    });\n```\n*Actual T-SQL:*\n```sql\nDELETE [A0] \nOUTPUT DELETED.[UserId] \nFROM [dbo].[User] [A0] \nWHERE [A0].[FirstName] LIKE 'May%'\n```\n*Result:*\n```\nRemoved user id: 3\n```\n## More Tables and foreign keys\nTo crete more complex queries we need more than one table. Let's add a couple more:\n\n*dbo.Company*\n```cs\npublic class TableCompany : TableBase\n{\n    public readonly Int32TableColumn CompanyId;\n    public readonly StringTableColumn CompanyName;\n\n    //Audit Columns\n    public readonly Int32TableColumn Version;\n    public readonly DateTimeTableColumn ModifiedAt;\n\n    public TableCompany() : this(default) { }\n\n    public TableCompany(Alias alias) : base(\"dbo\", \"Company\", alias)\n    {\n        this.CompanyId = this.CreateInt32Column(\n            nameof(this.CompanyId), ColumnMeta.PrimaryKey().Identity());\n\n        this.CompanyName = this.CreateStringColumn(\n            nameof(this.CompanyName), 250);\n\n        this.Version = this.CreateInt32Column(\"Version\",\n            ColumnMeta.DefaultValue(0));\n\n        this.ModifiedAt = this.CreateDateTimeColumn(\"ModifiedAt\",\n            columnMeta: ColumnMeta.DefaultValue(SqQueryBuilder.GetUtcDate()));\n    }\n}\n```\n*dbo.Customer*\n```cs\npublic class TableCustomer : TableBase\n{\n    public Int32TableColumn CustomerId { get; }\n    public NullableInt32TableColumn UserId { get; }\n    public NullableInt32TableColumn CompanyId { get; }\n\n    public TableCustomer() : this(default) { }\n\n    public TableCustomer(Alias alias) : base(\"dbo\", \"Customer\", alias)\n    {\n        this.CustomerId = this.CreateInt32Column(\n            nameof(this.CustomerId), ColumnMeta.PrimaryKey().Identity());\n\n        this.UserId = this.CreateNullableInt32Column(\n            nameof(this.UserId), \n            ColumnMeta.ForeignKey\u003cTableUser\u003e(u =\u003e u.UserId));\n\n        this.CompanyId = this.CreateNullableInt32Column(\n            nameof(this.CompanyId), \n            ColumnMeta.ForeignKey\u003cTableCompany\u003e(u =\u003e u.CompanyId));\n\n        //Indexes            \n        this.AddUniqueIndex(this.UserId, this.CompanyId);\n        this.AddUniqueIndex(this.CompanyId, this.UserId);\n    }\n}\n```\nPay attention to the way how the foreign keys are defined:\n```cs\nColumnMeta.ForeignKey\u003cTableUser\u003e(u =\u003e u.UserId)\n```\nAnd indexes:\n```cs\nthis.AddUniqueIndex(this.UserId, this.CompanyId);\nthis.AddUniqueIndex(this.CompanyId, this.UserId);\n```\nSince now we have the foreign keys we have to delete and create the table in the specific order:\n```cs\nvar tables = new TableBase[]{ new TableUser() , new TableCompany(), new TableCustomer() };\n\nforeach (var table in tables.Reverse())\n{\n    await database.Statement(table.Script.DropIfExist());\n}\nforeach (var table in tables)\n{\n    await database.Statement(table.Script.Create());\n}\n```\nNow we can insert some companies:\n```cs\nvar tCompany = new TableCompany();\n\nConsole.WriteLine(\"Companies:\");\n\nawait /*SqQueryBuilder.*/InsertDataInto(tCompany, new[] {\"Microsoft\", \"Google\"})\n    .MapData(s =\u003e s.Set(s.Target.CompanyName, s.Source))\n    .AlsoInsert(s =\u003e s\n        .Set(s.Target.Version, 1)\n        .Set(s.Target.ModifiedAt, GetUtcDate()))\n    .Output(tCompany.CompanyId, tCompany.CompanyName)\n    .Query(database, (r) =\u003e\n    {\n        Console.WriteLine($\"Id: {tCompany.CompanyId.Read(r)}, Name: {tCompany.CompanyName.Read(r)}\");\n    });\n```\nand create \"Customers\":\n```cs\nvar tUser = new TableUser();\nvar tCompany = new TableCompany();\nvar tCustomer = new TableCustomer();\nvar tSubCustomer = new TableCustomer();\n\n//Users\nawait /*SqQueryBuilder.*/InsertInto(tCustomer, tCustomer.UserId)\n    .From(\n        Select(tUser.UserId)\n            .From(tUser)\n            .Where(!Exists(\n                SelectOne()\n                    .From(tSubCustomer)\n                    .Where(tSubCustomer.UserId == tUser.UserId))))\n    .Exec(database);\n\n//Companies\nawait InsertInto(tCustomer, tCustomer.CompanyId)\n    .From(\n        Select(tCompany.CompanyId)\n            .From(tCompany)\n            .Where(!Exists(\n                SelectOne()\n                    .From(tSubCustomer)\n                    .Where(tSubCustomer.CompanyId == tCompany.CompanyId))))\n    .Exec(database);\n```\n*Actual T-SQL:*\n```sql\nINSERT INTO [dbo].[Customer]([UserId]) \nSELECT [A0].[UserId] \nFROM [dbo].[User] [A0] WHERE NOT EXISTS\n(\n    SELECT 1 \n    FROM [dbo].[Customer] [A1] \n    WHERE [A1].[UserId]=[A0].[UserId]\n)\n\nINSERT INTO [dbo].[Customer]([CompanyId]) \nSELECT [A0].[CompanyId] \nFROM [dbo].[Company] [A0] \nWHERE NOT EXISTS(\n    SELECT 1 FROM [dbo].[Customer] [A1] \n    WHERE [A1].[CompanyId]=[A0].[CompanyId]\n)\n```\n_Note: SqExpress actively uses operator overloads. Therefore, operators \u003e, \u003e=, \u003c, \u003c=, ==, \u0026, |, !, /, +, -, *, and % are overloaded when applied to SqExpress syntax nodes, resulting in new syntax nodes._\n# Data Selection\n\n## Joining Tables\nNow we can Join all the tables:\n```cs\nvar tUser = new TableUser();\nvar tCompany = new TableCompany();\nvar tCustomer = new TableCustomer();\n\nvar cType = CustomColumnFactory.Int16(\"Type\");\nvar cName = CustomColumnFactory.String(\"Name\");\n\nvar customers = await /*SqQueryBuilder.*/Select(\n        tCustomer.CustomerId,\n        /*SqQueryBuilder.*/Case()\n            .When(IsNotNull(tUser.UserId))\n            .Then(Cast(Literal(1), SqlType.Int16))\n            .When(IsNotNull(tCompany.CompanyId))\n            .Then(Cast(Literal(2), SqlType.Int16))\n            .Else(Null)\n            .As(cType),\n        /*SqQueryBuilder.*/Case()\n            .When(IsNotNull(tUser.UserId))\n            .Then(tUser.FirstName + \" \" + tUser.LastName)\n            .When(IsNotNull(tCompany.CompanyId))\n            .Then(tCompany.CompanyName)\n            .Else(Null)\n            .As(cName)\n    )\n    .From(tCustomer)\n    .LeftJoin(tUser, on: tUser.UserId == tCustomer.UserId)\n    .LeftJoin(tCompany, on: tCompany.CompanyId == tCustomer.CompanyId)\n    .QueryList(database,\n        r =\u003e (Id: tCustomer.CustomerId.Read(r), CustomerType: cType.Read(r), Name: cName.Read(r)));\n\nforeach (var customer in customers)\n{\n    Console.WriteLine($\"Id: {customer.Id}, Name: {customer.Name}, Type: {customer.CustomerType}\");\n}\n```\n*Actual T-SQL:*\n```sql\nSELECT \n    [A0].[CustomerId],\n    CASE \n        WHEN [A1].[UserId] IS NOT NULL \n        THEN CAST(1 AS smallint) \n        WHEN [A2].[CompanyId] IS NOT NULL \n        THEN CAST(2 AS smallint) \n        ELSE NULL END \n    [Type],\n    CASE \n        WHEN [A1].[UserId] IS NOT NULL \n        THEN [A1].[FirstName]+' '+[A1].[LastName] \n        WHEN [A2].[CompanyId] IS NOT NULL \n        THEN [A2].[CompanyName] \n        ELSE NULL END \n    [Name] \nFROM [dbo].[Customer] [A0] \nLEFT JOIN [dbo].[User] [A1] \n    ON [A1].[UserId]=[A0].[UserId] \nLEFT JOIN [dbo].[Company] [A2] \n    ON [A2].[CompanyId]=[A0].[CompanyId]\n```\n*Result:*\n```\nId: 1, Name: Francois Sturman, Type: 1\nId: 2, Name: Allina Freeborne, Type: 1\nId: 3, Name: Microsoft, Type: 2\nId: 4, Name: Google, Type: 2\n```\n## Aliasing\nEvery time you create a table object, it is associated by default with an alias that will be used wherever you refer to the table. Each new instance will use a new alias. However you can explicitly specify your own alias or omit it:\n\n```cs\nvar tUser = new User(\"USR\");\nvar tUserNoAlias = new User(Alias.Empty);\n\nSelect(tUser.UserId).From(tUser);\nSelect(tUserNoAlias.UserId).From(tUserNoAlias);\n```\n*Actual T-SQL:*\n```sql\n--var tUser = new User(\"USR\");\nSELECT [USR].[UserId] FROM [dbo].[user] [USR]\n\n--var tUserNoAlias = new User(Alias.Empty);\nSELECT [UserId] FROM [dbo].[user]\n```\n## Derived Tables\nThe previous query is quite complex so it makes sense to store it as a derived table and reuse it in future:\n```cs\npublic class DerivedTableCustomer : DerivedTableBase\n{\n    public readonly Int32CustomColumn CustomerId;\n\n    public readonly Int16CustomColumn Type;\n\n    public readonly StringCustomColumn Name;\n\n    public DerivedTableCustomer(Alias alias = default) : base(alias)\n    {\n        this.CustomerId = this.CreateInt32Column(\"CustomerId\");\n        this.Type = this.CreateInt16Column(\"Type\");\n        this.Name = this.CreateStringColumn(\"Name\");\n    }\n\n    protected override IExprSubQuery CreateQuery()\n    {\n        var tUser = new TableUser();\n        var tCompany = new TableCompany();\n        var tCustomer = new TableCustomer();\n\n        return /*SqQueryBuilder.*/Select(\n                tCustomer.CustomerId.As(this.CustomerId),\n                /*SqQueryBuilder.*/Case()\n                    .When(IsNotNull(tUser.UserId))\n                    .Then(Cast(Literal(1), SqlType.Int16))\n                    .When(IsNotNull(tCompany.CompanyId))\n                    .Then(Cast(Literal(2), SqlType.Int16))\n                    .Else(Null)\n                    .As(this.Type),\n                /*SqQueryBuilder.*/Case()\n                    .When(IsNotNull(tUser.UserId))\n                    .Then(tUser.FirstName + \" \" + tUser.LastName)\n                    .When(IsNotNull(tCompany.CompanyId))\n                    .Then(tCompany.CompanyName)\n                    .Else(Null)\n                    .As(this.Name)\n            )\n            .From(tCustomer)\n            .LeftJoin(tUser, on: tUser.UserId == tCustomer.UserId)\n            .LeftJoin(tCompany, on: tCompany.CompanyId == tCustomer.CompanyId)\n            .Done();\n    }\n}\n```\nand this is how it can be reused:\n```cs\nvar tCustomer = new DerivedTableCustomer(\"CUST\");\n\nvar customers = await /*SqQueryBuilder.*/Select(tCustomer.Columns)\n    .From(tCustomer)\n    .Where(tCustomer.Type == 2 | tCustomer.Name.Like(\"%Free%\"))\n    .OrderBy(Desc(tCustomer.Name))\n    .OffsetFetch(1, 2)\n    .QueryList(database,\n        r =\u003e (Id: tCustomer.CustomerId.Read(r), CustomerType: tCustomer.Type.Read(r), Name: tCustomer.Name.Read(r)));\n\nforeach (var customer in customers)\n{\n    Console.WriteLine($\"Id: {customer.Id}, Name: {customer.Name}, Type: {customer.CustomerType}\");\n}\n\n```\n*Actual T-SQL:*\n```sql\nSELECT \n    [CUST].[CustomerId],\n    [CUST].[Type],\n    [CUST].[Name] \nFROM \n(\n    SELECT \n        [A0].[CustomerId] [CustomerId],\n        CASE \n        WHEN [A1].[UserId] IS NOT NULL \n        THEN CAST(1 AS smallint) \n        WHEN [A2].[CompanyId] IS NOT NULL \n        THEN CAST(2 AS smallint) \n        ELSE NULL \n        END [Type],\n        CASE \n        WHEN [A1].[UserId] IS NOT NULL \n        THEN [A1].[FirstName]+' '+[A1].[LastName] \n        WHEN [A2].[CompanyId] IS NOT NULL \n        THEN [A2].[CompanyName] \n        ELSE NULL END [Name] \n    FROM [dbo].[Customer] [A0] \n    LEFT JOIN [dbo].[User] [A1] \n        ON [A1].[UserId]=[A0].[UserId] \n    LEFT JOIN [dbo].[Company] [A2] \n        ON [A2].[CompanyId]=[A0].[CompanyId]\n)[CUST] \nWHERE \n    [CUST].[Type]=2 OR [CUST].[Name] LIKE '%Free%' \nORDER BY [CUST].[Name] DESC \nOFFSET 1 ROW FETCH NEXT 2 ROW ONLY\n```\n*Result:*\n```\nId: 4, Name: Google, Type: 2\nId: 2, Name: Allina Freeborne, Type: 1\n```\n## Subquries\nIt is not necessary to create a new class when you need a subquery - it can be directly described in an original expression. It is enough just to predefine the aliases for columns and tables:\n```cs\nvar num = CustomColumnFactory.Int32(\"3\");\n//Note: \"3\" (the first value) is for compatibility with MySql\n//which does not properly support values constructors\n\nvar sum = CustomColumnFactory.Int32(\"Sum\");\n\nvar numbers = Values(3, 1, 1, 7, 3, 7, 3, 7, 7, 8).AsColumns(num);\nvar numbersSubQuery = TableAlias();\n\nvar mostFrequentNum = (int) await\n    /*SqQueryBuilder.*/SelectTop(1, numbersSubQuery.Column(num))\n        .From(\n            /*SqQueryBuilder.*/Select(numbers.Column(num), CountOne().As(sum))\n                .From(numbers)\n                .GroupBy(numbers.Column(num))\n                .As(numbersSubQuery)\n        )\n        .OrderBy(/*SqQueryBuilder.*/Desc(numbersSubQuery.Column(sum)))\n        .QueryScalar(database);\n\nConsole.WriteLine(\"The most frequent number: \"  + mostFrequentNum);\n```\n*Actual T-SQL:*\n```sql\nSELECT \n    TOP 1 [A0].[3] \nFROM \n(\n    SELECT [A1].[3],COUNT(1) [Sum] \n    FROM (VALUES (3),(1),(1),(7),(3),(7),(3),(7),(7),(8))[A1]([3]) \n    GROUP BY [A1].[3]\n) [A0] \nORDER BY [A0].[Sum] DESC\n```\n*Note: In this example you can see how to use **Table Value Constructor***\n\n## CTE\n\nTo perform recursive (*actually \"incremental\"*) requests the library supports CTE (Common Table Expressions).\n\nThe typical scenario is traversing some hierarchical data stored in a table, for example the following query will return a tree closure table:\n\n```cs\nclass CteTreeClosure : CteBase\n{\n    public CteTreeClosure(Alias alias = default) : base(nameof(CteTreeClosure), alias)\n    {\n        this.Id = this.CreateInt32Column(nameof(this.Id));\n        this.ParentId = this.CreateNullableInt32Column(nameof(this.ParentId));\n        this.Depth = this.CreateInt32Column(nameof(this.Depth));\n    }\n\n    public Int32CustomColumn Id { get; }\n\n    public NullableInt32CustomColumn ParentId { get; }\n\n    public Int32CustomColumn Depth { get; }\n\n    public override IExprSubQuery CreateQuery()\n    {\n        var initial = new TreeData();\n        var current = new TreeData();\n\n        var previous = new CteTreeClosure();\n\n        return /*SqQueryBuilder.*/Select(initial.Id, initial.ParentId, /*SqQueryBuilder.*/Literal(1).As(this.Depth))\n            .From(initial)\n            .UnionAll(Select(\n                    previous.Id,\n                    current.ParentId,\n                    (previous.Depth + 1).As(this.Depth))\n                .From(current)\n                .InnerJoin(previous, on: previous.ParentId == current.Id))\n            .Done();\n    }\n}\n...\n\nvar result = await /*SqQueryBuilder.*/Select(treeClosure.Id, treeClosure.ParentId, treeClosure.Depth)\n    .From(treeClosure)\n    .QueryList(context.Database,\n        r =\u003e (\n            Id: treeClosure.Id.Read(r),\n            ParentId: treeClosure.ParentId.Read(r),\n            Depth: treeClosure.Depth.Read(r)));\n\n```\n\nWorking with CTEs in SqExpress is very similar to derived tables - you need to create a class derived from  **CteBase** abstract class, describe columns and implement **CreateQuery** method which will return actual CTE query where the class can be used as a table descriptor (to create recursion if it is required).\n\nThe example code will generate the following sql:\n\n```sql\nWITH [CteTreeClosure] AS(\n        SELECT [A1].[Id],[A1].[ParentId],1 [Depth] \n        FROM [#TreeData] [A1] \n    UNION ALL \n        SELECT [A2].[Id],[A3].[ParentId],[A2].[Depth]+1 [Depth] \n        FROM [#TreeData] [A3] \n        JOIN [CteTreeClosure] [A2] \n            ON [A2].[ParentId]=[A3].[Id]\n)\n                \nSELECT [A0].[Id],[A0].[ParentId],[A0].[Depth] FROM [CteTreeClosure] [A0]\n```\n*MySql*\n```sql\nWITH RECURSIVE `CteTreeClosure` AS(\n        SELECT `A0`.`Id`,`A0`.`ParentId`,1 `Depth` \n        FROM `TreeData` `A0` \n    UNION ALL \n        SELECT `A1`.`Id`,`A2`.`ParentId`,`A1`.`Depth`+1 `Depth` \n        FROM `TreeData` `A2` \n        JOIN `CteTreeClosure` `A1` \n            ON `A1`.`ParentId`=`A2`.`Id`\n) \n\nSELECT `A3`.`Id`,`A3`.`ParentId`,`A3`.`Depth` FROM `CteTreeClosure` `A3````\n```\n\n## Analytic And Window Functions\nSqExpress supports common analytic and window functions like **ROW_NUMBER**, **RANK**, **FIRST_VALUE**, **LAST_VALUE** etc.\n```cs\nvar cUserName = CustomColumnFactory.String(\"Name\");\nvar cNum = CustomColumnFactory.Int64(\"Num\");\nvar cFirst = CustomColumnFactory.String(\"First\");\nvar cLast = CustomColumnFactory.String(\"Last\");\n\nvar user = new TableUser();\n\nawait /*SqQueryBuilder.*/Select(\n        (user.FirstName + \" \" + user.LastName)\n        .As(cUserName),\n        /*SqQueryBuilder.*/RowNumber()\n            /*.OverPartitionBy(some fields)*/\n            .OverOrderBy(user.FirstName)\n            .As(cNum),\n        /*SqQueryBuilder.*/FirstValue(user.FirstName + \" \" + user.LastName)\n            /*.OverPartitionBy(some fields)*/\n            .OverOrderBy(user.FirstName)\n            .FrameClauseEmpty()\n            .As(cFirst),\n        /*SqQueryBuilder.*/LastValue(user.FirstName + \" \" + user.LastName)\n            /*.OverPartitionBy(some fields)*/\n            .OverOrderBy(user.FirstName)\n            .FrameClause(\n                FrameBorder.UnboundedPreceding,\n                FrameBorder.UnboundedFollowing)\n            .As(cLast))\n    .From(user)\n    .Query(database,\n        r =\u003e Console.WriteLine(\n            $\"Num: {cNum.Read(r)}, Name: {cUserName.Read(r)}, \" +\n            $\"First: {cFirst.Read(r)}, Last: {cLast.Read(r)}\"));\n```\n*Actual T-SQL:*\n```sql\nSELECT \n    [A0].[FirstName]+' '+[A0].[LastName] \n        [Name],\n    ROW_NUMBER()OVER(ORDER BY [A0].[FirstName]) \n        [Num],\n    FIRST_VALUE([A0].[FirstName]+' '+[A0].[LastName])\n        OVER(ORDER BY [A0].[FirstName]) \n        [First],\n    LAST_VALUE([A0].[FirstName]+' '+[A0].[LastName])\n        OVER(ORDER BY [A0].[FirstName] \n            ROWS BETWEEN \n            UNBOUNDED PRECEDING \n            AND UNBOUNDED FOLLOWING) \n        [Last] \nFROM [dbo].[User] [A0]\n```\n## Set Operators\nThe library supports all the SET operators:\n```cs\n//If you need to repeat one query several times \n// you can store it in a variable\nvar select1 = /*SqQueryBuilder.*/Select(1);\nvar select2 = /*SqQueryBuilder.*/Select(2);\n\nvar result = await select1\n    .Union(select2)\n    .UnionAll(select2)\n    .Except(select2)\n    .Intersect(select1.Union(select2))\n    .QueryList(database, r =\u003e r.GetInt32(0));\n\nConsole.WriteLine(\"Result Of Set Operators:\");\nConsole.WriteLine(result[0]);\n```\nAns actual SQL will be:\n```sql\n(\n    (\n        (\n            SELECT 1 \n            UNION \n            SELECT 2\n        ) \n        UNION ALL \n        SELECT 2\n    ) \n    EXCEPT \n    SELECT 2\n) \nINTERSECT \n(\n    SELECT 1 \n    UNION \n    SELECT 2\n)\n```\n## Merge\nAs a bonus, if you use MS SQL Server, you can use **Merge** statement:\n```cs\nvar data = new[]\n{\n    new {FirstName = \"Francois\", LastName = \"Sturman2\"},\n    new {FirstName = \"Allina\", LastName = \"Freeborne2\"},\n    new {FirstName = \"Maye\", LastName = \"Malloy\"},\n};\n\nvar action = CustomColumnFactory.String(\"Actions\");\nvar inserted = CustomColumnFactory.NullableInt32(\"Inserted\");\nvar deleted = CustomColumnFactory.NullableInt32(\"Deleted\");\n\nvar tableUser = new TableUser();\nawait /*SqQueryBuilder.*/MergeDataInto(tableUser, data)\n    .MapDataKeys(s =\u003e s\n        .Set(s.Target.FirstName, s.Source.FirstName))\n    .MapData(s =\u003e s\n        .Set(s.Target.LastName, s.Source.LastName))\n    .WhenMatchedThenUpdate()\n    .AlsoSet(s =\u003e s\n        .Set(s.Target.Version, s.Target.Version + 1)\n        .Set(s.Target.ModifiedAt, GetUtcDate()))\n    .WhenNotMatchedByTargetThenInsert()\n    .AlsoInsert(s =\u003e s\n        .Set(s.Target.Version, 1)\n        .Set(s.Target.ModifiedAt, GetUtcDate()))\n    .Output((t, s, m) =\u003e m.Inserted(t.UserId.As(inserted)).Deleted(t.UserId.As(deleted)).Action(action))\n    .Done()\n    .Query(database,\n        (r) =\u003e\n        {\n            Console.WriteLine($\"UserId Inserted: {inserted.Read(r)},UserId Deleted: {deleted.Read(r)} , Action: {action.Read(r)}\");\n        });\n```\n*Actual T-SQL:*\n```sql\nMERGE [dbo].[User] [A0] \nUSING (\n    VALUES \n    ('Francois','Sturman2'),\n    ('Allina','Freeborne2'),\n    ('Maye','Malloy'))[A1]([FirstName],[LastName]) \nON [A0].[FirstName]=[A1].[FirstName] \nWHEN MATCHED \nTHEN UPDATE SET [A0].[LastName]=[A1].[LastName],[A0].[Version]=[A0].[Version]+1,[A0].[ModifiedAt]=GETUTCDATE() \nWHEN NOT MATCHED \nTHEN INSERT([FirstName],[LastName],[Version],[ModifiedAt]) \nVALUES([A1].[FirstName],[A1].[LastName],1,GETUTCDATE()) \nOUTPUT INSERTED.[UserId] [Inserted],DELETED.[UserId] [Deleted],$ACTION [Actions];\n```\n*Result:*\n```\nUserId Inserted: 4,UserId Deleted:  , Action: INSERT\nUserId Inserted: 1,UserId Deleted: 1 , Action: UPDATE\nUserId Inserted: 2,UserId Deleted: 2 , Action: UPDATE\n```\n\nFor PostgresSQL or MySQL the library generates polyfills that use a temporary table to store passed data. For example the previous query will be converted into the following statements (OUTPUT is not supported):\n\n*Actual MYSQL:*\n```sql\nCREATE TEMPORARY TABLE `tmpMergeDataSource`(\n    `FirstName` varchar(8) character set utf8,\n    `LastName` varchar(10) character set utf8,\n    CONSTRAINT PRIMARY KEY (`FirstName`))\n;\nINSERT INTO `tmpMergeDataSource`(`FirstName`,`LastName`) \nVALUES ('Francois','Sturman2'),('Allina','Freeborne2'),('Maye','Malloy')\n;\nUPDATE `User` `A0`,`tmpMergeDataSource` `A1` \nSET \n    `A0`.`LastName`=`A1`.`LastName`,\n    `A0`.`Version`=`A0`.`Version`+1,\n    `A0`.`ModifiedAt`=UTC_TIMESTAMP()\nWHERE `A0`.`FirstName`=`A1`.`FirstName`\n;\nINSERT INTO `User`(`FirstName`,`LastName`,`Version`,`ModifiedAt`) \nSELECT `A1`.`FirstName`,`A1`.`LastName`,1,UTC_TIMESTAMP() \nFROM `tmpMergeDataSource` `A1` \nWHERE NOT EXISTS(SELECT 1 FROM `User` `A0` WHERE `A0`.`FirstName`=`A1`.`FirstName`)\n;\nDROP TABLE `tmpMergeDataSource`;\n```\n\n## Temporary Tables\nIn some scenarios temporary tables might be very useful and you can create such table as follows:\n```cs\npublic class TempTable : TempTableBase\n{\n    public TempTable(Alias alias = default) : base(\"tempTable\", alias)\n    {\n        this.Id = CreateInt32Column(nameof(Id),\n            ColumnMeta.PrimaryKey().Identity());\n\n        this.Name = CreateStringColumn(nameof(Name), 255);\n    }\n\n    public readonly Int32TableColumn Id;\n\n    public readonly StringTableColumn Name;\n}\n```\nand then use it:\n```cs\nvar tmp = new TempTable();\n\nvar tableUser = new TableUser();\nvar tableCompany = new TableCompany();\n\nawait database.Statement(tmp.Script.Create());\n\n//Users\nawait /*SqQueryBuilder.*/InsertInto(tmp, tmp.Name)\n    .From(Select(tableUser.FirstName + \" \"+ tableUser.LastName)\n    .From(tableUser))\n    .Exec(database);\n\n//Companies\nawait /*SqQueryBuilder.*/InsertInto(tmp, tmp.Name)\n    .From(Select(tableCompany.CompanyName)\n    .From(tableCompany))\n    .Exec(database);\n\nawait /*SqQueryBuilder.*/Select(tmp.Columns)\n    .From(tmp)\n    .OrderBy(tmp.Name)\n    .Query(database,\n        (r) =\u003e\n        {\n            Console.WriteLine($\"Id: {tmp.Id.Read(r)}, Name: {tmp.Name.Read(r)}\");\n        });\n\n//Dropping the temp table is optional\n//It will be automatically removed when\n//the connection is closed\nawait database.Statement(tmp.Script.Drop());\n```\nThe result will be:\n```\nId: 2, Name: Allina Freeborne\nId: 1, Name: Francois Sturman\nId: 4, Name: Google\nId: 3, Name: Microsoft\n```\n## Database Data Export Import\n\nHaving a list of table descriptors you can easily export all theirs data into any text format - JSON for example:\n```cs\nstatic async Task\u003cstring\u003e ToJsonString(ISqDatabase database, TableBase[] tableBases)\n{\n    using var ms = new MemoryStream();\n    using Utf8JsonWriter writer = new Utf8JsonWriter(ms);\n\n    writer.WriteStartObject();\n    foreach (var table in tableBases)\n    {\n        await ReadTableDataIntoJson(writer, database, table);\n    }\n\n    writer.WriteEndObject();\n    writer.Flush();\n\n    var s = Encoding.UTF8.GetString(ms.ToArray());\n    return s;\n}\n\nstatic async Task ReadTableDataIntoJson(Utf8JsonWriter writer, ISqDatabase database, TableBase table)\n{\n    writer.WriteStartArray(table.FullName.AsExprTableFullName().TableName.Name);\n\n    writer.WriteStartArray();\n    foreach (var column in table.Columns)\n    {\n        writer.WriteStringValue(column.ColumnName.Name);\n    }\n\n    writer.WriteEndArray();\n\n    await /*SqQueryBuilder.*/Select(table.Columns)\n        .From(table)\n        .Query(database,\n            r =\u003e\n            {\n                writer.WriteStartArray();\n                foreach (var column in table.Columns)\n                {\n                    var readAsString = column.ReadAsString(r);\n                    writer.WriteStringValue(readAsString);\n                }\n\n                writer.WriteEndArray();\n            });\n\n    writer.WriteEndArray();\n}\n```\nResult:\n```json\n{\n    \"User\": [\n\t[\"UserId\", \"FirstName\", \"LastName\", \"Version\", \"ModifiedAt\"], \n\t[\"1\", \"Francois\", \"Sturman2\", \"2\", \"2021-10-26T08:07:03.160\"], \n\t[\"2\", \"Allina\", \"Freeborne2\", \"2\", \"2021-10-26T08:07:03.160\"], \n\t[\"4\", \"Maye\", \"Malloy\", \"1\", \"2021-10-26T08:07:03.160\"]],\n    \"Company\": [\n\t[\"CompanyId\", \"CompanyName\", \"Version\", \"ModifiedAt\"], \n\t[\"1\", \"Microsoft\", \"1\", \"2021-10-26T08:07:03.080\"], \n\t[\"2\", \"Google\", \"1\", \"2021-10-26T08:07:03.080\"]],\n    \"Customer\": [\n\t[\"CustomerId\", \"UserId\", \"CompanyId\"], \n\t[\"3\", null, \"1\"], \n\t[\"4\", null, \"2\"], \n\t[\"1\", \"1\", null], \n\t[\"2\", \"2\", null]]\n}\n```\nImport from a text format is not difficult as well:\n```cs\nstatic async Task InsertTableData(ISqDatabase database, TableBase table, JsonElement element)\n{\n    var columnsDict = table.Columns.ToDictionary(i =\u003e i.ColumnName.Name, i =\u003e i);\n    var colIndexes = element.EnumerateArray().First().EnumerateArray().Select(c =\u003e c.GetString()).ToList();\n\n    var rowsEnumerable = element\n        .EnumerateArray()\n        .Skip(1)\n        .Select(e =\u003e\n            e.EnumerateArray()\n                .Select((c, i) =\u003e\n                    columnsDict[colIndexes[i]]\n                        .FromString(c.ValueKind == JsonValueKind.Null ? null : c.GetString()))\n                .ToList());\n\n    var insertExpr = /*SqQueryBuilder.*/IdentityInsertInto(table, table.Columns).Values(rowsEnumerable);\n    if (!insertExpr.Insert.Source.IsEmpty)\n    {\n        await insertExpr.Exec(database);\n    }\n}\n```\n## Getting and Comparing Database Table Metadata\nYou can a list of dynamic table descriptors directly from a database using ```GetTables()``` method of ```ISqDatabase``` object. For example, this how you can read a list of all tables with all columns:\n\n```cs\nasync Task ShowAllTablesWithColumns(ISqDatabase database)\n{\n    var actualTables = await database.GetTables();\n    foreach (var table in actualTables)\n    {\n        Console.WriteLine(table.FullName.TableName);\n        foreach (var tableColumn in table.Columns)\n        {\n            Console.WriteLine($\"   -{tableColumn.ColumnName.Name}:{TSqlExporter.Default.ToSql(tableColumn.SqlType)}\");\n        }\n    }\n}\n```\n\nYou also can compare 2 lists of table to find any kind of differences:\n\n```cs\nasync Task\u003cbool\u003e CheckDatabaseIsUpdated(ISqDatabase database, IReadOnlyList\u003cTableBase\u003e expectedTableList)\n{\n    var actualTables = await database.GetTables();\n\n    var comparison = expectedTableList.CompareWith(actualTables);\n\n    bool result = true;\n\n    if (comparison != null)\n    {\n        if (comparison.ExtraTables.Count \u003e 0)\n        {\n            Console.WriteLine($\"There are {comparison.ExtraTables.Count} extra tables\");\n        }\n        if (comparison.MissedTables.Count \u003e 0)\n        {\n            result = false;\n            Console.WriteLine($\"There are {comparison.MissedTables.Count} missed tables\");\n        }\n        if (comparison.DifferentTables.Count \u003e 0)\n        {\n            result = false;\n            Console.WriteLine($\"There are {comparison.DifferentTables.Count} different tables\");\n\n            foreach (var differentTable in comparison.DifferentTables)\n            {\n                Console.WriteLine($\"Table {differentTable.Table.FullName.TableName}\");\n                foreach (var extra in differentTable.TableComparison.ExtraColumns)\n                {\n                    Console.WriteLine($\"Extra column: {extra.ColumnName.Name}\");\n                }\n                foreach (var missed in differentTable.TableComparison.MissedColumns)\n                {\n                    Console.WriteLine($\"Extra column: {missed.ColumnName.Name}\");\n                }\n                foreach (var differentColumns in differentTable.TableComparison.DifferentColumns)\n                {\n                    Console.WriteLine($\"Different column: {differentColumns.Column.ColumnName.Name} - {differentColumns.ColumnComparison}\");\n                }\n\n            }\n\n        }\n    }\n    return result;\n}\n```\n\nYou cane also create new table dynamic descriptors and modify existing ones:\n```cs\nvar tbl = SqTable.Create(\n    \"schema\",\n    \"table\",\n    b =\u003e b\n        .AppendInt32Column(\"Id\", ColumnMeta.PrimaryKey().Identity())\n        .AppendStringColumn(\"Value\", 255, true)\n        .AppendBooleanColumn(\"IsActive\", ColumnMeta.DefaultValue(false)),\n    i =\u003e i\n        .AppendIndex(i.Asc(\"Id\"), i.Desc(\"Value\"))\n        .AppendIndex(i.Asc(\"Value\"))\n);\n\ntbl = tbl.With(\n    tbl.FullName.WithSchemaName(\"schema2\").WithTableName(\"table2\"),\n    (cols, app) =\u003e app\n        .AppendColumns(cols.Where(c =\u003e c.ColumnName.Name != \"IsActive\"))\n        .AppendDateTimeOffsetColumn(\"modifyDate\"),\n    (indexes, app) =\u003e app\n        .AppendIndexes(indexes.Where(i=\u003ei.Columns.Count \u003e 1))\n        .AddUniqueIndex(app.Desc(\"modifyDate\"))\n);\n```\n\n## Syntax Tree\nYou can go through an existing syntax tree object and modify if it is required:\n```cs\n//Var some external filter..\nExprBoolean filter = CustomColumnFactory.Int16(\"Type\") == 2 /*Company*/;\n\nvar tableCustomer = new TableCustomer();\n\nvar baseSelect = /*SqQueryBuilder.*/Select(tableCustomer.CustomerId)\n    .From(tableCustomer)\n    .Where(filter)\n    .Done();\n\n//Checking that filter has \"Type\" column\nvar hasVirtualColumn = filter.SyntaxTree()\n    .FirstOrDefault\u003cExprColumnName\u003e(e =\u003e e.Name == \"Type\") != null;\n\nif (hasVirtualColumn)\n{\n    baseSelect = (ExprQuerySpecification) baseSelect.SyntaxTree()\n        .Modify(e =\u003e\n        {\n            var result = e;\n            //Joining with the sub query\n            if (e is TableCustomer table)\n            {\n                var derivedTable = new DerivedTableCustomer();\n\n                result = new ExprJoinedTable(\n                    table,\n                    ExprJoinedTable.ExprJoinType.Inner,\n                    derivedTable,\n                    table.CustomerId == derivedTable.CustomerId);\n            }\n\n            return result;\n        });\n}\n\nawait baseSelect!\n    .Query(database,\n        (r) =\u003e\n        {\n            Console.WriteLine($\"Id: {tableCustomer.CustomerId.Read(r)}\");\n        });\n```\nFor simpler scenarios you can just use “With…” functions:\n```cs\nvar tUser = new TableUser();\n\nConsole.WriteLine(\"Original expression:\");\nvar expression = SelectTop(1, tUser.FirstName).From(tUser).Done();\n\nawait expression.QueryScalar(database);\n\nexpression = expression\n    .WithTop(null)\n    .WithSelectList(tUser.UserId, tUser.FirstName + \" \" + tUser.LastName)\n    .WithWhere(tUser.UserId == 7);\n\nConsole.WriteLine(\"With changed selection list and filter:\");\nawait expression.QueryScalar(database);\n\nvar tCustomer = new TableCustomer();\nexpression = expression\n    .WithInnerJoin(tCustomer, on: tCustomer.UserId == tUser.UserId);\n\nConsole.WriteLine(\"With joined table\");\nawait expression.QueryScalar(database);\n```\n*Actual T-SQL:*\n```sql\n--Original expression:\nSELECT TOP 1 \n    [A0].[FirstName] \nFROM [dbo].[User] [A0]\n\n--With changed selection list  and filter:\nSELECT \n    [A0].[UserId],\n    [A0].[FirstName]+' '+[A0].[LastName] \nFROM [dbo].[User] \n    [A0] \nWHERE \n    [A0].[UserId]=7\n\n--With joined table\nSELECT \n    [A0].[UserId],\n    [A0].[FirstName]+' '+[A0].[LastName] \nFROM [dbo].[User] \n    [A0] \nJOIN [dbo].[Customer] \n    [A1] ON \n    [A1].[UserId]=[A0].[UserId] \nWHERE \n    [A0].[UserId]=7\n```\n## Serialization to XML\nEach expression can be exported to a xml string and then restored back. It can be useful to pass expressions over network:\n```cs\nvar tableUser = new TableUser(Alias.Empty);\n\nvar selectExpr = Select(tableUser.FirstName, tableUser.LastName)\n    .From(tableUser)\n    .Where(tableUser.LastName == \"Sturman\")\n    .Done();\n\n//Exporting\nvar stringBuilder = new StringBuilder();\nusing XmlWriter writer = XmlWriter.Create(stringBuilder);\nselectExpr.SyntaxTree().ExportToXml(writer);\n\n//Importing\nXmlDocument document = new XmlDocument();\ndocument.LoadXml(stringBuilder.ToString());\nvar restored = (ExprQuerySpecification)ExprDeserializer\n    .DeserializeFormXml(document.DocumentElement!);\n\nvar result = await restored\n    .QueryList(database, r =\u003e (tableUser.FirstName.Read(r), tableUser.LastName.Read(r)));\n\nforeach (var name in result)\n{\n    Console.WriteLine(name);\n}\n```\nThis an example of the XML text:\n```xml\n\u003cExpr typeTag=\"QuerySpecification\"\u003e\n   \u003cSelectList\u003e\n      \u003cSelectList0 typeTag=\"Column\"\u003e\n         \u003cColumnName typeTag=\"ColumnName\"\u003e\n            \u003cName\u003eFirstName\u003c/Name\u003e\n         \u003c/ColumnName\u003e\n      \u003c/SelectList0\u003e\n      \u003cSelectList1 typeTag=\"Column\"\u003e\n         \u003cColumnName typeTag=\"ColumnName\"\u003e\n            \u003cName\u003eLastName\u003c/Name\u003e\n         \u003c/ColumnName\u003e\n      \u003c/SelectList1\u003e\n   \u003c/SelectList\u003e\n   \u003cFrom typeTag=\"Table\"\u003e\n      \u003cFullName typeTag=\"TableFullName\"\u003e\n         \u003cDbSchema typeTag=\"DbSchema\"\u003e\n            \u003cSchema typeTag=\"SchemaName\"\u003e\n               \u003cName\u003edbo\u003c/Name\u003e\n            \u003c/Schema\u003e\n         \u003c/DbSchema\u003e\n         \u003cTableName typeTag=\"TableName\"\u003e\n            \u003cName\u003eUser\u003c/Name\u003e\n         \u003c/TableName\u003e\n      \u003c/FullName\u003e\n   \u003c/From\u003e\n   \u003cWhere typeTag=\"BooleanEq\"\u003e\n      \u003cLeft typeTag=\"Column\"\u003e\n         \u003cColumnName typeTag=\"ColumnName\"\u003e\n            \u003cName\u003eLastName\u003c/Name\u003e\n         \u003c/ColumnName\u003e\n      \u003c/Left\u003e\n      \u003cRight typeTag=\"StringLiteral\"\u003e\n         \u003cValue\u003eSturman\u003c/Value\u003e\n      \u003c/Right\u003e\n   \u003c/Where\u003e\n   \u003cDistinct\u003efalse\u003c/Distinct\u003e\n\u003c/Expr\u003e\n```\n## Serialization to JSON\nThe similar functionality exists for JSON (.Net Core 3.1+)\n```cs\nvar tableUser = new TableUser(Alias.Empty);\n\nvar selectExpr = Select(tableUser.FirstName, tableUser.LastName)\n    .From(tableUser)\n    .Where(tableUser.LastName == \"Sturman\")\n    .Done();\n\n//Exporting\nvar memoryStream = new MemoryStream();\nvar jsonWriter = new Utf8JsonWriter(memoryStream);\nselectExpr.SyntaxTree().ExportToJson(jsonWriter);\n\nstring json = Encoding.UTF8.GetString(memoryStream.ToArray());\n\n//Importing\nvar restored = (ExprQuerySpecification)ExprDeserializer\n    .DeserializeFormJson(JsonDocument.Parse(json).RootElement);\n\nvar result = await restored\n    .QueryList(database, r =\u003e (tableUser.FirstName.Read(r), tableUser.LastName.Read(r)));\n\nforeach (var name in result)\n{\n    Console.WriteLine(name);\n}\n```\nThis an example of the JSON text:\n```json\n{\n   \"$type\":\"QuerySpecification\",\n   \"SelectList\":[\n      {\n         \"$type\":\"Column\",\n         \"ColumnName\":{\n            \"$type\":\"ColumnName\",\n            \"Name\":\"FirstName\"\n         }\n      },\n      {\n         \"$type\":\"Column\",\n         \"ColumnName\":{\n            \"$type\":\"ColumnName\",\n            \"Name\":\"LastName\"\n         }\n      }\n   ],\n   \"From\":{\n      \"$type\":\"Table\",\n      \"FullName\":{\n         \"$type\":\"TableFullName\",\n         \"DbSchema\":{\n            \"$type\":\"DbSchema\",\n            \"Schema\":{\n               \"$type\":\"SchemaName\",\n               \"Name\":\"dbo\"\n            }\n         },\n         \"TableName\":{\n            \"$type\":\"TableName\",\n            \"Name\":\"User\"\n         }\n      }\n   },\n   \"Where\":{\n      \"$type\":\"BooleanEq\",\n      \"Left\":{\n         \"$type\":\"Column\",\n         \"ColumnName\":{\n            \"$type\":\"ColumnName\",\n            \"Name\":\"LastName\"\n         }\n      },\n      \"Right\":{\n         \"$type\":\"StringLiteral\",\n         \"Value\":\"Sturman\"\n      }\n   },\n   \"Distinct\":false\n}\n```\n## Serialization to Plain List\nAlso an expression can be exported into a list of plain entities. It might be useful if you want to store some expressions (e.g. \"Favorites Filters\") in a plain structure:\n\n```cs\nvar tableUser = new TableUser(Alias.Empty);\n\nExprBoolean filter1 = tableUser.LastName == \"Sturman\";\nExprBoolean filter2 = tableUser.LastName == \"Freeborne\";\n\nvar tableFavoriteFilter = new TableFavoriteFilter();\nvar tableFavoriteFilterItem = new TableFavoriteFilterItem();\n\nvar filterIds = await InsertDataInto(tableFavoriteFilter, new[] {\"Filter 1\", \"Filter 2\"})\n    .MapData(s =\u003e s.Set(s.Target.Name, s.Source))\n    .Output(tableFavoriteFilter.FavoriteFilterId)\n    .QueryList(database, r =\u003e tableFavoriteFilterItem.FavoriteFilterId.Read(r));\n\nvar filter1Items = \n    filter1.SyntaxTree().ExportToPlainList((i, id, index, b, s, value) =\u003e\n    FilterPlainItem.Create(filterIds[0], i, id, index, b, s, value));\n\nvar filter2Items = \n    filter2.SyntaxTree().ExportToPlainList((i, id, index, b, s, value) =\u003e\n    FilterPlainItem.Create(filterIds[1], i, id, index, b, s, value));\n\nawait /*SqQueryBuilder.*/InsertDataInto(tableFavoriteFilterItem, filter1Items.Concat(filter2Items))\n    .MapData(s =\u003e s\n        .Set(s.Target.FavoriteFilterId, s.Source.FavoriteFilterId)\n        .Set(s.Target.Id, s.Source.Id)\n        .Set(s.Target.ParentId, s.Source.ParentId)\n        .Set(s.Target.IsTypeTag, s.Source.IsTypeTag)\n        .Set(s.Target.ArrayIndex, s.Source.ArrayIndex)\n        .Set(s.Target.Tag, s.Source.Tag)\n        .Set(s.Target.Value, s.Source.Value)\n    )\n    .Exec(database);\n\n//Restoring\nvar restoredFilterItems = await /*SqQueryBuilder.*/Select(tableFavoriteFilterItem.Columns)\n    .From(tableFavoriteFilterItem)\n    .Where(tableFavoriteFilterItem.FavoriteFilterId.In(filterIds))\n    .QueryList(\n        database,\n        r =\u003e new FilterPlainItem(\n        favoriteFilterId: tableFavoriteFilterItem.FavoriteFilterId.Read(r),\n        id: tableFavoriteFilterItem.Id.Read(r),\n        parentId: tableFavoriteFilterItem.ParentId.Read(r),\n        isTypeTag: tableFavoriteFilterItem.IsTypeTag.Read(r),\n        arrayIndex: tableFavoriteFilterItem.ArrayIndex.Read(r),\n        tag: tableFavoriteFilterItem.Tag.Read(r),\n        value: tableFavoriteFilterItem.Value.Read(r)));\n\nvar restoredFilter1 = (ExprBoolean)ExprDeserializer\n    .DeserializeFormPlainList(restoredFilterItems.Where(fi =\u003e\n        fi.FavoriteFilterId == filterIds[0]));\n\nvar restoredFilter2 = (ExprBoolean)ExprDeserializer\n    .DeserializeFormPlainList(restoredFilterItems.Where(fi =\u003e\n        fi.FavoriteFilterId == filterIds[1]));\n\nConsole.WriteLine(\"Filter 1\");\nawait /*SqQueryBuilder.*/Select(tableUser.FirstName, tableUser.LastName)\n    .From(tableUser)\n    .Where(restoredFilter1)\n    .Query(database,\n        (r) =\u003e\n        {\n            Console.WriteLine($\"{tableUser.FirstName.Read(r)} {tableUser.LastName.Read(r)}\");\n        });\n\nConsole.WriteLine(\"Filter 2\");\nawait /*SqQueryBuilder.*/Select(tableUser.FirstName, tableUser.LastName)\n    .From(tableUser)\n    .Where(restoredFilter2)\n    .Query(database,\n        (r) =\u003e\n        {\n            Console.WriteLine($\"{tableUser.FirstName.Read(r)} {tableUser.LastName.Read(r)}\");\n        });\n```\n\n## Retrieving Database Table Metadata\n\nThe **SqExpreesDatabase** class includes a method called **GetTables()** that retrieves all table descriptors from a database defined in the connection string:\n\n```cs\nISqDatabase database = ...;\n\nvar allTables = await database.GetTables();\n\nforeach (var table in allTables)\n{\n    Console.WriteLine($\"{table.FullName.TableName}\");\n    foreach (var column in table.Columns)\n    {\n        Console.WriteLine(\n            $\"   *{column.ColumnName.Name} {column.SqlType.ToSql(TSqlExporter.Default)}\");\n    }\n}\n\n```\nResult:\n```\nUser\n   *UserId int\n   *FirstName [nvarchar](255)\n   *LastName [nvarchar](255)\n   *Version int\n   *ModifiedAt datetime\nCustomer\n   *CustomerId int\n   *UserId int\n   *CompanyId int\n\netc...\n```\n\n*Note: The list of tables is sorted in such a way that dependent tables appear last. Therefore, if the list is reversed, tables can be safely deleted:*\n\n```cs\nvar allTables = await database.GetTables();\n\nforeach (var table in allTables.Reverse())\n{\n    await database.Statement(table.Script.Drop());\n}\n```\n\nThe list of tables can be compared with each other:\n\n```cs\nvar declaredTables = AllTables.BuildAllTableList(SqlDialect.TSql);\nvar actualTables = await database.GetTables();\n\nvar comparison = declaredTables.CompareWith(actualTables);\n\nif (comparison != null)\n{\n    if (comparison.ExtraTables.Count \u003e 0)\n    {\n        Console.WriteLine($\"There are {comparison.ExtraTables.Count} extra tables\");\n    }\n    if (comparison.MissedTables.Count \u003e 0)\n    {\n        Console.WriteLine($\"There are {comparison.MissedTables.Count} missed tables\");\n    }\n    if (comparison.DifferentTables.Count \u003e 0)\n    {\n        Console.WriteLine($\"There are {comparison.MissedTables.Count} different tables\");\n    }\n}\n```\n\n## Table Descriptors Scaffolding\n**SqExpress** comes with the code-gen utility (it is located in the nuget package cache). It can read metadata form a database and create table descriptor classes in your code. It requires .Net Core 3.1+\n\n```Package Manager Console```\n```\nSYNTAX\n    Gen-Tables [-DbType] {mssql | mysql | pgsql} [-ConnectionString] \u003cstring\u003e [-OutputDir \u003cstring\u003e] [-TableClassPrefix \u003cstring\u003e] [-Namespace \u003cstring\u003e]\n```\n\n```GenerateTables.cmd```\n```cmd\n@echo off\nset root=%userprofile%\\.nuget\\packages\\sqexpress\n\nfor /F \"tokens=*\" %%a in ('dir \"%root%\" /b /a:d /o:n') do set \"lib=%root%\\%%a\"\n\nset lib=%lib%\\tools\\codegen\\SqExpress.CodeGenUtil.dll\n\ndotnet \"%lib%\" gentables mssql \"MyConnectionString\" --table-class-prefix \"Tbl\" -o \".\\Tables\" -n \"MyCompany.MyProject.Tables\"\n```\n```GenerateTables.sh```\n```sh\n#!/bin/bash\n\nlib=~/.nuget/packages/sqexpress/$(ls ~/.nuget/packages/sqexpress -r|head -n 1)/tools/codegen/SqExpress.CodeGenUtil.dll\n\ndotnet $lib gentables mssql \"MyConnectionString\" --table-class-prefix \"Tbl\" -o \"./Tables\" -n \"MyCompany.MyProject.Tables\"\n```\n###\nIt uses Roslyn compiler so it does not overwrite existing files - it patched it with actual columns. All kind of changes like attributes, namespaces, interfaces will remain after next runs.\n\n## DTOs Scaffolding\nYou can add special attributes to column properties in table descriptors to provide information to the code-gen util to create (update) DTO classes with mappings:\n```cs\npublic class TableUser : TableBase\n{\n    [SqModel(\"UserName\", PropertyName = \"Id\")]\n    public Int32TableColumn UserId { get; }\n\n    [SqModel(\"UserName\")]\n    public StringTableColumn FirstName { get; }\n\n    [SqModel(\"UserName\")]\n    public StringTableColumn LastName { get; }\n\n    //Audit Columns\n    [SqModel(\"AuditData\")]\n    public Int32TableColumn Version { get; }\n\n    [SqModel(\"AuditData\")]\n    public DateTimeTableColumn ModifiedAt { get; }\n\n    public TableUser(Alias alias) : base(\"dbo\", \"User\", alias)\n    {\n        ...\n    }\n}\n```\nTo run the code-gen util before a project building, just define the following property in the project file:\n```\n\u003cProject ..,\u003e\n  \u003cPropertyGroup\u003e\n    ...\n    \u003cSqModelGenEnable\u003etrue\u003c/SqModelGenEnable\u003e\n    ...\n  \u003c/PropertyGroup\u003e\n```\nThe list of all code-generation parameters can be found here: [SqExpress.props](https://github.com/0x1000000/SqExpress/blob/main/SqExpress/SqExpress.props).\n\nThe code generation tool can also be run from the command line:\n\n```Package Manager Console```\n```\nSYNTAX\n    Gen-Models [-InputDir \u003cstring\u003e] [-OutputDir \u003cstring\u003e] [-Namespace \u003cstring\u003e] [-NoRwClasses] [-NullRefTypes] [-CleanOutput] [-ModelType {ImmutableClass | Record}]  [\u003cCommonParameters\u003e]\n```\n\n```GenerateModel.cmd```\n```cmd\n@echo off\nset root=%userprofile%\\.nuget\\packages\\sqexpress\n\nfor /F \"tokens=*\" %%a in ('dir \"%root%\" /b /a:d /o:n') do set \"lib=%root%\\%%a\"\n\nset lib=%lib%\\tools\\codegen\\SqExpress.CodeGenUtil.dll\n\ndotnet \"%lib%\" genmodels -i \".\" -o \".\\Models\" -n \"SqExpress.GetStarted.Models\" --null-ref-types\n```\n```generate-model.sh```\n```\n#!/bin/bash\nlib=~/.nuget/packages/sqexpress/$(ls ~/.nuget/packages/sqexpress -r|head -n 1)/tools/codegen/SqExpress.CodeGenUtil.dll\ndotnet $lib genmodels -i \".\" -o \"./Models\" -n \"SqExpress.GetStarted.Models\"\n```\nThe result will be the following classes:\n\n```UserName.cs```\n```cs\npublic class UserName\n{\n    public UserName(int id, string firstName, string lastName)\n    {\n        this.Id = id;\n        this.FirstName = firstName;\n        this.LastName = lastName;\n    }\n\n    public static UserName Read(ISqDataRecordReader record, TableUser table)\n    {\n        return new UserName(id: table.UserId.Read(record), firstName: table.FirstName.Read(record), lastName: table.LastName.Read(record));\n    }\n\n    public int Id { get; }\n\n    public string FirstName { get; }\n\n    public string LastName { get; }\n\n    public static TableColumn[] GetColumns(TableUser table)\n    {\n        return new TableColumn[]{table.UserId, table.FirstName, table.LastName};\n    }\n\n    public static IRecordSetterNext GetMapping(IDataMapSetter\u003cTableUser, UserName\u003e s)\n    {\n        return s.Set(s.Target.FirstName, s.Source.FirstName).Set(s.Target.LastName, s.Source.LastName);\n    }\n\n    public static IRecordSetterNext GetUpdateKeyMapping(IDataMapSetter\u003cTableUser, UserName\u003e s)\n    {\n        return s.Set(s.Target.UserId, s.Source.Id);\n    }\n\n    public static IRecordSetterNext GetUpdateMapping(IDataMapSetter\u003cTableUser, UserName\u003e s)\n    {\n        return s.Set(s.Target.FirstName, s.Source.FirstName).Set(s.Target.LastName, s.Source.LastName);\n    }\n\n    public UserName WithId(int id)\n    {\n        return new UserName(id: id, firstName: this.FirstName, lastName: this.LastName);\n    }\n\n    public UserName WithFirstName(string firstName)\n    {\n        return new UserName(id: this.Id, firstName: firstName, lastName: this.LastName);\n    }\n\n    public UserName WithLastName(string lastName)\n    {\n        return new UserName(id: this.Id, firstName: this.FirstName, lastName: lastName);\n    }\n}\n```\nand [```AuditData.cs```](https://github.com/0x1000000/SqExpress/blob/main/SqExpress.GetStarted/Models/AuditData.cs)\n\nYou can use them as follows:\n```cs\nvar tUser = new TableUser();\n\nvar users = await Select(UserName.GetColumns(tUser))\n    .From(tUser)\n    .QueryList(database, r =\u003e UserName.Read(r, tUser));\n\nforeach (var userName in users)\n{\n    Console.WriteLine($\"{userName.Id} {userName.FirstName} {userName.LastName}\");\n}\n```\n*Note: **SqModel** attribute can be also used for temporary and derived table descriptors.*\n\n## Model Selection\n\nThe library contains a fluent api that helps selecting tuples of models inner or left joined.\n```\nSqModelSelectBuilder\n    .Select(Model1.GetReader())\n    .InnerJoin(\n        Model2.GetReader(), \n        on: t=\u003e t.Table.Id1 == t.JoinedTable1.Id1)\n    .InnerJoin(\n        Model3.GetReader(), \n        on: t=\u003e t.JoinedTable2.Id2 == t.JoinedTable1.Id2)\n    ...\n    .InnerJoin(\n        ModelN.GetReader(), \n        on: t=\u003e t.JoinedTable(N-1).Id(N-1) == t.JoinedTable(N-2).Id(N-1)))\n    .LeftJoin(\n        Model(N+1).GetReader(), \n        on: t=\u003e t.JoinedTableN.IdN == t.JoinedTable(N-1).IdN))\n    ...\n    .Get(\n        filter: t=\u003e \u003cBoolean Expression\u003e,\n        order: t=\u003e\u003cOrder Expression\u003e,\n        tuple=\u003e \u003cResult Mapping\u003e)\n    .QueryList(database);\n\n    ... or\n    .Find(\n        offset, pageSize,\n        filter: t=\u003e \u003cBoolean Expression\u003e,\n        order: t=\u003e\u003cOrder Expression\u003e,\n        tuple=\u003e \u003cResult Mapping\u003e)\n    .QueryPage(database);\n```\n\nExample:\n\n```cs\nvar page = await SqModelSelectBuilder\n    .Select(ModelEmptyReader.Get\u003cTableCustomer\u003e())\n    .LeftJoin(\n        UserName.GetReader(), \n        on: t =\u003e t.Table.UserId == t.JoinedTable1.UserId)\n    .LeftJoin(\n        CompanyName.GetReader(), \n        on: t =\u003e t.Table.CompanyId == t.JoinedTable2.CompanyId)\n    .Find(0,10,\n        filter: null,\n        order: t =\u003e Asc(\n            IsNull(\n                t.JoinedTable1.FirstName + t.JoinedTable1.LastName,\n                t.JoinedTable2.CompanyName)\n            ),\n        r =\u003e (r.JoinedModel1 != null \n                ? r.JoinedModel1.FirstName + \" \"+ r.JoinedModel1.LastName \n                : null) \n            ??\n            r.JoinedModel2?.Name ?? \"Unknown\")\n    .QueryPage(database);\n\nforeach (var name in page.Items)\n{\n    Console.WriteLine(name);\n}\n\n```\n\n## Using in ASP.Net\nThere is a demo ASP.Net project which is supposed to show how [SqExpress](https://github.com/0x1000000/SqGoods/tree/main) can be used in a real web app.\n\nThe ideas:\n1.\tEach API request uses only one sql connection which is stored in [a connection storage](https://github.com/0x1000000/SqGoods/blob/main/SqGoods.DomainLogic/DataAccess/MsSqlConnectionStorage.cs);\n2.\tThe connection storage [can create an instance of SqDatabase](https://github.com/0x1000000/SqGoods/blob/main/SqGoods.DomainLogic/DataAccess/MsSqlConnectionStorage.cs#L18);\n3.\tThe connection storage and SqDatabase [have “Scoped” lifecycle](https://github.com/0x1000000/SqGoods/blob/main/SqGoods.DomainLogic/DomainLogicRegistration.cs#L17);\n4.\tSqDatabase is used in [entity repositories which are responsible for “Domain Logic”](https://github.com/0x1000000/SqGoods/blob/main/SqGoods.DomainLogic/Repositories/SgCategoryRepository.cs).\n## PostgreSQL\nYou can run all the scenarios using Postgres SQL (of course the actual sql will be different):\n```Cs\nDbCommand NpgsqlCommandFactory(NpgsqlConnection connection, string sqlText)\n{\n    return new NpgsqlCommand(sqlText, connection);\n}\n\nconst string connectionString = \n    \"Host=localhost;Port=5432;Username=postgres;Password=test;Database=test\";\n\nusing (var connection = new NpgsqlConnection(connectionString))\n{\n    using (var database = new SqDatabase\u003cNpgsqlConnection\u003e(\n        connection: connection,\n        commandFactory: NpgsqlCommandFactory,\n        sqlExporter: new PgSqlExporter(builderOptions: SqlBuilderOptions.Default\n            .WithSchemaMap(schemaMap: new[] {\n                new SchemaMap(@from: \"dbo\", to: \"public\")}))))\n    {\n        ...\n    }\n}\n```\n*Note: You need to add **Npgsql** package to your project.*\n## MySQL\nYou also can run all the scenarios using My SQL:\n```Cs\nDbCommand MySqlCommandFactory(MySqlConnection connection, string sqlText)\n{\n    return new MySqlCommand(sqlText, connection);\n}\n\nconst string connectionString = \n    \"server=127.0.0.1;uid=test;pwd=test;database=test\";\n\nusing (var connection = new MySqlConnection(connectionString))\n{\n    using (var database = new SqDatabase\u003cMySqlConnection\u003e(\n        connection: connection,\n        commandFactory: MySqlCommandFactory,\n        sqlExporter: new MySqlExporter(\n            builderOptions: SqlBuilderOptions.Default)))\n    {\n        ...\n    }\n}\n```\n*Note: You need to add **MySql.Data** or **MySqlConnector** package to your project.*\n\n## Auto-Mapper\nSince the DAL works on top the ADO you can use Auto-Mapper (if you like it):\n```cs\nvar mapper = new Mapper(new MapperConfiguration(cfg =\u003e\n{\n    cfg.AddDataReaderMapping();\n    var map = cfg.CreateMap\u003cIDataRecord, AllColumnTypesDto\u003e();\n\n    if (context.IsPostgresSql)\n    {\n        map\n            .ForMember(nameof(table.ColByte), c =\u003e c.Ignore())\n            .ForMember(nameof(table.ColNullableByte), c =\u003e c.Ignore());\n    }\n}));\n\nvar result = await Select(table.Columns)\n    .From(table)\n    .QueryList(context.Database, r =\u003e mapper.Map\u003cIDataRecord, AllColumnTypesDto\u003e(r));\n```\n[(taken from \"Test/SqExpress.IntTest/Scenarios/ScAllColumnTypes.cs\")](https://github.com/0x1000000/SqExpress/blob/main/Test/SqExpress.IntTest/Scenarios/ScAllColumnTypes.cs#L26)\n","funding_links":[],"categories":["C# #"],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2F0x1000000%2FSqExpress","html_url":"https://awesome.ecosyste.ms/projects/github.com%2F0x1000000%2FSqExpress","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2F0x1000000%2FSqExpress/lists"}