{"id":26806009,"url":"https://github.com/atis-orm/atis-orm","last_synced_at":"2025-04-23T09:56:28.418Z","repository":{"id":282876532,"uuid":"928402472","full_name":"atis-orm/atis-orm","owner":"atis-orm","description":"Extremely lightweight ORM which aims to allow developers to write most complex SQL Queries in LINQ.","archived":false,"fork":false,"pushed_at":"2025-04-12T09:52:34.000Z","size":565,"stargazers_count":4,"open_issues_count":0,"forks_count":1,"subscribers_count":0,"default_branch":"main","last_synced_at":"2025-04-23T09:56:23.084Z","etag":null,"topics":["c-sharp","database","orm","orm-framework","orm-library"],"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/atis-orm.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,"zenodo":null}},"created_at":"2025-02-06T15:25:05.000Z","updated_at":"2025-04-12T09:52:38.000Z","dependencies_parsed_at":null,"dependency_job_id":"bac8c06d-a71a-410d-9b36-434e80b855ec","html_url":"https://github.com/atis-orm/atis-orm","commit_stats":null,"previous_names":["atis-orm/atis-orm"],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/atis-orm%2Fatis-orm","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/atis-orm%2Fatis-orm/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/atis-orm%2Fatis-orm/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/atis-orm%2Fatis-orm/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/atis-orm","download_url":"https://codeload.github.com/atis-orm/atis-orm/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":250412532,"owners_count":21426285,"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":["c-sharp","database","orm","orm-framework","orm-library"],"created_at":"2025-03-29T23:18:33.542Z","updated_at":"2025-04-23T09:56:28.411Z","avatar_url":"https://github.com/atis-orm.png","language":"C#","readme":"\n# Atis ORM\n\n\u003e **⚠️ Work in Progress**  \n\u003e This project is currently under development and **has not been published as a NuGet package yet**.  \n\u003e The structure, APIs, and internal logic are subject to change without notice.  \n\u003e Please use it **for review and experimental purposes only**.\n\n---\n\n## Overview\n\n**Atis ORM** is a lightweight and extensible library focused on **transforming LINQ expression trees into structured SQL expression representations (ASTs)**.  \n\nIt is designed to serve as the core query engine of a future ORM system, with a strong emphasis on **customization, plugin-based normalization, and minimalism**.\n\nThe current version focuses purely on the **transformation and modeling layer**, enabling advanced SQL query generation and inspection.   Query execution and materialization will be introduced in future phases.\n\n---\n\n## Objectives\n\n- **Enable Complex SQL via LINQ** — The primary goal of Atis ORM is to empower developers to write **nearly any type of complex SQL** `SELECT` query using LINQ syntax, including deeply nested queries, recursive CTEs, outer applies, and dynamic projections — all translated into a structured SQL AST.\n- **Expression-Centric Design** — Focused on transforming LINQ expression trees into structured, strongly-typed SQL expression models (ASTs), rather than generating SQL strings directly.\n- **Plugin-Based Normalization** — Provides a modular and plugin-friendly architecture, where expression normalization, conversion, and post-processing can be extended or replaced without modifying the core.\n- **Support for Complex Query Scenarios**, including:\n  - Recursive CTEs\n  - Navigation chains with smart join inference\n  - Complex navigations (Outer Apply, etc.)\n  - Calculated properties\n  - Specification pattern integration\n  - Bulk update/delete feature\n  - etc.\n- **Minimalism \u0026 Explicit Control** — No built-in model discovery, conventions, or tracking. The system avoids magic — every behavior is explicitly controlled and injectable.\n- **Ideal for Complex Business Applications** — Targets enterprise scenarios where centralizing business rules and dynamic query shaping are essential.\n\n---\n\n## Deep Dives\n\n- [How Conversion Works in Atis ORM?](docs/StringCompareToConverterDocs_WithCode.md)\n\n---\n\n## Examples\n\n### Direct Select without From\n\n**LINQ**\n\n```csharp\nvar q = dbc.Select(() =\u003e new { n = 1 })\n                .Where(x =\u003e x.n \u003e 5);\n```\n\n**SQL**\n\n```sql\nselect\ta_1.n as n\nfrom\t(\n    select\t1 as n\n) as a_1\nwhere\t(n \u003e 5)\n```\n\n---\n\n### Explicit Joins\n\n**LINQ**\n```csharp\nvar q = employees\n        .OuterApply(e =\u003e employeeDegrees.Where(d =\u003e d.EmployeeId == e.EmployeeId).Take(1), (e, ed) =\u003e new { e, ed })\n        .Select(x =\u003e new { x.e.EmployeeId, x.e.Name, x.ed.Degree });\n```\n\n**SQL**\n```sql\nselect\ta_1.EmployeeId as EmployeeId, a_1.Name as Name, a_3.Degree as Degree\nfrom\tEmployee as a_1\n\touter apply (\n\t\tselect\ttop (1)\ta_2.RowId as RowId, a_2.EmployeeId as EmployeeId, \n                a_2.Degree as Degree, a_2.University as University\n\t\tfrom\tEmployeeDegree as a_2\n\t\twhere\t(a_2.EmployeeId = a_1.EmployeeId)\n\t) as a_3\n```\n\n---\n\n### Recursive CTE using LINQ\n\n**LINQ**\n```csharp\nvar q = employees\n            .Where(x =\u003e x.ManagerId == null)\n            .RecursiveUnion(anchor =\u003e anchor.SelectMany(anchorMember =\u003e anchorMember.NavSubOrdinates))\n            .Select(x =\u003e new { x.EmployeeId, x.Name, x.ManagerId });\n```\n\n**SQL**\n```sql\nwith cte_1 as \n(\t\n\tselect\ta_2.RowId as RowId, a_2.EmployeeId as EmployeeId, a_2.Name as Name, \n            a_2.Department as Department, a_2.ManagerId as ManagerId\t\n\tfrom\tEmployee as a_2\t\n\twhere\ta_2.ManagerId is null\t\n\tunion all\t\n\tselect\tNavSubOrdinates_4.RowId as RowId, NavSubOrdinates_4.EmployeeId as EmployeeId, \n            NavSubOrdinates_4.Name as Name, NavSubOrdinates_4.Department as Department, \n            NavSubOrdinates_4.ManagerId as ManagerId\t\n\tfrom\tcte_1 as a_3\t\n\t\t    inner join Employee as NavSubOrdinates_4 on (a_3.EmployeeId = NavSubOrdinates_4.ManagerId)\t\n)\nselect\tcte_1.EmployeeId as EmployeeId, cte_1.Name as Name, cte_1.ManagerId as ManagerId\nfrom\tcte_1 as cte_1\n```\n\n---\n\n### Recursive Query as Sub-Query\n\n**LINQ**\n```csharp\nvar q = from manager in employees\n        where manager.EmployeeId == \"123\"       // picking specific manager\n        select new\n        {\n            ManagerId = manager.EmployeeId,\n            ManagerName = manager.Name,\n            NestedCount = employees\n                            .Where(immediateChild =\u003e immediateChild.ManagerId == manager.EmployeeId)\n                            .RecursiveUnion(anchor =\u003e anchor.SelectMany(anchorMember =\u003e anchorMember.NavSubOrdinates))\n                            .Count()\n        };\n```\n\n**SQL**\n```sql\nwith cte_1 as \n(\t\n    -- we picked up the query that was materialized upto the point when RecursiveUnion found\n    -- and moved into cte_1\n\tselect\ta_2.RowId as RowId, a_2.EmployeeId as EmployeeId, \n            a_2.Name as Name, a_2.Department as Department, \n            a_2.ManagerId as ManagerId\t\n\tfrom\tEmployee as a_2\t\n\twhere\t(a_2.EmployeeId = '123')\t\n), cte_3 as \n(\t\n\tselect\ta_4.RowId as RowId, a_4.EmployeeId as EmployeeId, \n            a_4.Name as Name, a_4.Department as Department, \n            a_4.ManagerId as ManagerId\t\n\tfrom\tEmployee as a_4\t\n            cross join cte_1 as cte_1\t            -- auto resolved reference for manager.EmployeeId\n\twhere\t(a_4.ManagerId = cte_1.EmployeeId)\t    -- cte_1.EmployeeId is manager.EmployeeId\n\tunion all\t\n\tselect\tNavSubOrdinates_6.RowId as RowId, NavSubOrdinates_6.EmployeeId as EmployeeId, \n            NavSubOrdinates_6.Name as Name, NavSubOrdinates_6.Department as Department, \n            NavSubOrdinates_6.ManagerId as ManagerId\t\n\tfrom\tcte_3 as a_5\t\n\t\t    inner join Employee as NavSubOrdinates_6 on (a_5.EmployeeId = NavSubOrdinates_6.ManagerId)\t\n)\nselect\tcte_1.EmployeeId as ManagerId, cte_1.Name as ManagerName, \n        (select\tCount(1) as Col1 from cte_3 as cte_3) as NestedCount\nfrom\tcte_1 as cte_1\n```\n\n---\n\n### Navigation Properties\n\n**LINQ**\n```csharp\nvar q = equipmentList\n            // Equipment.NavItem : Equipment (child) -\u003e Item (parent) is nullable which will render as left join\n            .Where(x =\u003e x.NavItem().UnitPrice \u003e 500)\n            // Item (child) -\u003e ItemBase (parent) is not-nullable which should render as inner join\n            // however, because of previous left join this join should render as left as well\n            // similarly, all later joins should become left even if they are inner\n            .Where(x =\u003e x.NavItem().NavItemBase().NavItemMoreInfo().TrackingType == \"SRN\")\n            .Select(x =\u003e new\n            {\n                x.NavItem().NavItemBase().NavItemMoreInfo().TrackingType,\n                x.NavItem().NavItemBase().NavItemMoreInfo().ItemId,\n                x.NavItem().NavItemBase().ItemDescription\n            });\n```\n\n**SQL**\n```sql\nselect\tNavItemMoreInfo_4.TrackingType as TrackingType, \n        NavItemMoreInfo_4.ItemId as ItemId, \n        NavItemBase_3.ItemDescription as ItemDescription\nfrom\tEquipment as a_1\n\t\tleft join ItemExtension as NavItem_2 on (NavItem_2.ItemId = a_1.ItemId)\n\t\tleft join ItemBase as NavItemBase_3 on (NavItemBase_3.ItemId = NavItem_2.ItemId)\n\t\tleft join ItemMoreInfo as NavItemMoreInfo_4 on (NavItemBase_3.ItemId = NavItemMoreInfo_4.ItemId)\nwhere\t(NavItem_2.UnitPrice \u003e 500) and \n        (NavItemMoreInfo_4.TrackingType = 'SRN')\n```\n\n---\n\n### Navigation Property Encapsulating Recursive Query\n\n**LINQ**\n```csharp\nvar q = from manager in employees\n        where manager.EmployeeId == \"123\"       // picking specific manager\n        select new\n        {\n            ManagerId = manager.EmployeeId,\n            ManagerName = manager.Name,\n            // NavNestedChildren internally is using RecursiveUnion\n            FilteredNested = manager.NavNestedChildren.Where(x =\u003e x.NavEmployee().Department == \"IT\").Count(),\n        };\n```\n\n**SQL**\n```sql\nwith cte_1 as \n(\t\n\tselect\ta_2.RowId as RowId, a_2.EmployeeId as EmployeeId, \n            a_2.Name as Name, a_2.Department as Department,\n            a_2.ManagerId as ManagerId\t\n\tfrom\tEmployee as a_2\t\n\twhere\t(a_2.EmployeeId = '123')\t\n), cte_3 as \n(\t\n\tselect\ta_4.RowId as RowId, a_4.EmployeeId as EmployeeId, \n            a_4.Name as Name, a_4.Department as Department, \n            a_4.ManagerId as ManagerId\t\n\tfrom\tEmployee as a_4\t\n\t\t    cross join cte_1 as cte_1\t\n\twhere\t(cte_1.EmployeeId = a_4.ManagerId)\t\n\tunion all\t\n\tselect\tNavSubOrdinates_6.RowId as RowId, NavSubOrdinates_6.EmployeeId as EmployeeId, \n            NavSubOrdinates_6.Name as Name, NavSubOrdinates_6.Department as Department, \n            NavSubOrdinates_6.ManagerId as ManagerId\t\n\tfrom\tcte_3 as a_5\t\n\t\t    inner join Employee as NavSubOrdinates_6 on (a_5.EmployeeId = NavSubOrdinates_6.ManagerId)\t\n)\nselect\tcte_1.EmployeeId as ManagerId, cte_1.Name as ManagerName, \n        (\n\t    select\tCount(1) as Col1\n\t    from\t(\n\t\t        select\tcte_3.EmployeeId as EmployeeId, cte_3.ManagerId as ImmediateManagerId, cte_1.EmployeeId as TopManagerId\n\t\t        from\tcte_3 as cte_3\n\t            ) as a_7\n\t\t        inner join Employee as NavEmployee_8 on (NavEmployee_8.EmployeeId = a_7.EmployeeId)\n        where\t(a_7.TopManagerId = a_7.TopManagerId) and \n                (NavEmployee_8.Department = 'IT')\n        ) as FilteredNested\nfrom\tcte_1 as cte_1\n```\n\n---\n\n### Calculated Properties\n\n```csharp\n// Calculated property that can be used within LINQ query as well as\n// in-memory data manipulation after loaded in C#\n[CalculatedProperty(nameof(CalcPercentageExpression))]\npublic decimal? CalcPercentage =\u003e CalcPercentageCompiled(this);\n\n// This will be used by converter to translate into SQL\npublic static readonly Expression\u003cFunc\u003cMarksheet, decimal?\u003e\u003e CalcPercentageExpression = \n    m =\u003e m.TotalMarks \u003e 0 ? m.MarksGained / m.TotalMarks * 100.0m : 0;\n\n// this is to centralize the logic so that we don't have to \n// write the same logic 2 times, i.e., one for translation and one to be used\n// for in-memory\npublic static readonly Func\u003cMarksheet, decimal?\u003e CalcPercentageCompiled = CalcPercentageExpression.Compile();\n```\n\n**LINQ**\n```csharp\nvar q = marksheets.Where(x =\u003e x.CalcPercentage \u003e 50).Select(x =\u003e new { x.Course, x.Grade });\n```\n\n**SQL**\n```sql\nselect\ta_1.Course as Course, a_1.Grade as Grade\nfrom\tMarksheet as a_1\nwhere\t(case when (a_1.TotalMarks \u003e 0) then ((a_1.MarksGained / a_1.TotalMarks) * 100.0) else 0 end \u003e 50)\n```\n\n---\n\n### Specification Pattern\n\n```csharp\npublic class InvoiceIsDueOnGivenDateSpecification : ExpressionSpecificationBase\u003cInvoice\u003e\n{\n    public InvoiceIsDueOnGivenDateSpecification(DateTime? givenDate)\n    {\n        this.GivenDate = givenDate;\n    }\n\n    public DateTime? GivenDate { get; }\n\n    public override Expression\u003cFunc\u003cInvoice, bool\u003e\u003e ToExpression()\n    {\n        return invoice =\u003e invoice.DueDate \u003e= this.GivenDate;\n    }\n}\n```\n\n**LINQ**\n```csharp\n// here x.InvoiceDate is being supplied as parameter which will \n// replace the property in the expression\nvar q = invoices.Where(x =\u003e new InvoiceIsDueOnGivenDateSpecification(x.InvoiceDate).IsSatisfiedBy(x))\n                  .Where(x =\u003e !new CustomerIsInvalidSpecification().IsSatisfiedBy(x.NavCustomer()));\n```\n\n**SQL**\n```sql\nselect\ta_1.RowId as RowId, a_1.InvoiceId as InvoiceId, a_1.InvoiceDate as InvoiceDate, \n        a_1.Description as Description, a_1.CustomerId as CustomerId, a_1.DueDate as DueDate\nfrom\tInvoice as a_1\n\t\tinner join Customer as NavCustomer_2 on (NavCustomer_2.RowId = a_1.CustomerId)\nwhere\t(a_1.DueDate \u003e= a_1.InvoiceDate) and \n        not ((NavCustomer_2.Status = 'Disabled') or (NavCustomer_2.Status = 'Blocked'))\n```\n\n---\n\n### Complex Outer Apply Navigation\n\n```csharp\npublic class InvoiceWithInvoiceDetailFirstLineRelation : EntityRelation\u003cInvoice, InvoiceDetail\u003e\n{\n    // JoinExpression = null will make it outer apply\n    public override Expression\u003cFunc\u003cInvoice, InvoiceDetail, bool\u003e\u003e? JoinExpression =\u003e null;\n    public override Expression\u003cFunc\u003cInvoice, IQueryable\u003cInvoiceDetail\u003e\u003e\u003e? FromParentToChild(IQueryProvider queryProvider)\n    {\n        return parent =\u003e parent.NavLines.Top(1);\n    }\n}\n```\n\n**LINQ**\n```csharp\nvar q = invoice.Select(\n            x =\u003e new { \n                    x.InvoiceId, \n                    Item = x.NavFirstLine().ItemId, \n                    x.NavFirstLine().NavItem().ItemDescription, \n                    x.NavFirstLine().UnitPrice \n            });\n```\n\n**SQL**\n```sql\nselect\ta_1.InvoiceId as InvoiceId, NavFirstLine_3.ItemId as Item, \n        NavItem_4.ItemDescription as ItemDescription, NavFirstLine_3.UnitPrice as UnitPrice\nfrom\tInvoice as a_1\n        outer apply (\n            select\ttop (1)\ta_2.RowId as RowId, a_2.InvoiceId as InvoiceId, a_2.ItemId as ItemId, \n                    a_2.UnitPrice as UnitPrice, a_2.Quantity as Quantity, a_2.LineTotal as LineTotal\n            from\tInvoiceDetail as a_2\n            where\t(a_1.RowId = a_2.InvoiceId)\n        ) as NavFirstLine_3\n        left join ItemBase as NavItem_4 on (NavItem_4.ItemId = NavFirstLine_3.ItemId)\n```\n\n---\n\n### Bulk Update\n\n**LINQ**\n```csharp\nExpression\u003cFunc\u003cint\u003e\u003e expr = () =\u003e (\n                                    from asset in assets\n                                    join item in items on asset.ItemId equals item.ItemId\n                                    select new { asset, item }                  // joined 2 tables in 1 query\n                                   )\n                                   .Update(                                     // \u003c- Update query\n                                        ms =\u003e ms.item,                          // \u003c- which table to update\n                                        ms =\u003e new ItemBase                      // \u003c- which fields to update\n                                            { \n                                                ItemDescription = ms.item.ItemDescription + ms.asset.SerialNumber \n                                            }, \n                                        ms =\u003e ms.asset.SerialNumber == \"123\"    // \u003c- where condition\n                                    );\n```\n\n**SQL**\n```sql\nupdate a_1\n    set ItemDescription = (a_1.ItemDescription + a_2.SerialNumber)\nfrom\tAsset as a_2\n\t    inner join ItemBase as a_1 on (a_2.ItemId = a_1.ItemId)\nwhere\t(a_2.SerialNumber = '123')\n```\n\n---\n\n## Contribution\n\n⚙️ **Work in Progress** — Contributions are currently not open until the first stable draft is completed.  \nHowever, feel free to explore and suggest improvements via issues.\n\n---\n","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fatis-orm%2Fatis-orm","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fatis-orm%2Fatis-orm","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fatis-orm%2Fatis-orm/lists"}