{"id":23032002,"url":"https://github.com/uwrit/composure","last_synced_at":"2025-04-02T21:41:09.489Z","repository":{"id":97970299,"uuid":"199477098","full_name":"uwrit/composure","owner":"uwrit","description":"Dynamic SQL Done Right","archived":false,"fork":false,"pushed_at":"2023-04-19T16:07:11.000Z","size":31,"stargazers_count":3,"open_issues_count":0,"forks_count":0,"subscribers_count":2,"default_branch":"master","last_synced_at":"2025-02-08T12:13:17.411Z","etag":null,"topics":["csharp","dotnet-core","intellisense","sql"],"latest_commit_sha":null,"homepage":"","language":"C#","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"bsd-3-clause","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/uwrit.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-07-29T15:16:03.000Z","updated_at":"2022-08-12T00:12:48.000Z","dependencies_parsed_at":"2023-05-23T21:30:51.657Z","dependency_job_id":null,"html_url":"https://github.com/uwrit/composure","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/uwrit%2Fcomposure","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/uwrit%2Fcomposure/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/uwrit%2Fcomposure/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/uwrit%2Fcomposure/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/uwrit","download_url":"https://codeload.github.com/uwrit/composure/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":246899625,"owners_count":20851893,"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":["csharp","dotnet-core","intellisense","sql"],"created_at":"2024-12-15T15:49:26.995Z","updated_at":"2025-04-02T21:41:09.466Z","avatar_url":"https://github.com/uwrit.png","language":"C#","readme":"# Composure\n**Composure** is a simple SQL-authoring library for .NET Core. Composure allows for statically-typed SQL-like query syntax directly in C#, with minimal messy string manipulation. Composure is designed for readability, clarity, and testability, and was developed for the [Leaf Clinical Data Explorer app](https://github.com/uwrit/leaf) at the [University of Washington](http://www.washington.edu/) by [@ndobb](https://github.com/ndobb) and [@cspital](https://github.com/cspital).\n\n**Note: If you are able to create and use Stored Procedures, Views, and other SQL objects, please read no further and do so!** \n\n\u003e In addition to certain performance gains and so on, precompiled SQL drastically reduces the risk of [SQL injection](https://en.wikipedia.org/wiki/SQL_injection) and other security concerns related to dynamically creating queries. It also allows separation of database and app code, often making for cleaner, more maintainable code bases.\n\nAlso note that **Composure is *not*** an object-relational-mapper, and doesn't handle SQL connections or execute queries itself. Libraries such as [Dapper](https://stackexchange.github.io/Dapper/) do a great job of that already. \n\nRather, **Composure** allows you to reason about complex SQL queries in an object-oriented, visually intuition fashion using a simple domain-specific-language and operators. Let's get started!\n\n## Installation\nLoad from Nuget via the `dotnet CLI`:\n```bash\n$ dotnet add package Composure --version 1.1.0\n```\nor build locally:\n```bash\n$ git clone https://github.com/uwrit/composure.git\n$ cd composure/src/Composure\n$ dotnet build -c Release\n\n# Outputs dll to /bin/Release/netcoreapp2.2/\n```\n\n- [Basic SELECT from a table/view](#basic-select-from-a-tableview)\n- [Nested WHERE conditions](#nested-where-conditions)\n- [Basic JOIN](#basic-join)\n- [JOIN and GROUP BY](#join-and-group-by)\n- [UNION and wrap as subquery](#union-and-wrap-as-subquery)\n- [CASE WHEN statements](#case-when-statements)\n- [Using inheritance for predefined sets and intellisense](#using-inheritance-for-predefined-sets-and-intellisense)\n- [Syntax cheat-sheet](#syntax-cheat-sheet)\n- [Caveats](#caveats)\n\n## Basic SELECT from a table/view\n```c#\n// Columns\nvar name = new Column(\"Name\");\nvar category = new Column(\"Category\");\nvar deliciousness = new Column(\"Deliciousness\");\n\n// Get query\nvar query = new NamedSet\n{\n    Select = new[] { name, category, deliciousness },\n    From   = \"dbo.Food\",\n    Where  = new[]\n    {\n        deliciousness \u003e 3,\n        category == \"fruit\"\n    }\n};\n\nquery.ToString();\n```\nReturns:\n```sql\nSELECT \n    Name\n  , Category\n  , Deliciousness \nFROM \n    dbo.Food \nWHERE \n    Deliciousness \u003e 3 \n    AND Category = 'fruit'\n```\nAnd that's it! Note that these examples show formatted SQL only for readability. Composure itself does **not** beautify SQL. \n\nLet's try a more interesting example.\n\n## Nested WHERE conditions\n```c#\n// WHERE clause conditions\nvar isDelicious = deliciousness \u003e 3;\nvar isFruit = category == \"fruit\";\n\n// Get query\nvar query = new NamedSet\n{\n    Select = new[] { name, category, deliciousness },\n    From   = \"dbo.Food\",\n    Where  = new[] { (isDelicious \u0026 !isFruit) | isFruit }\n};\n\nquery.ToString();\n```\nReturns:\n```sql\nSELECT \n    Name\n  , Category\n  , Deliciousness\nFROM \n    dbo.Food \nWHERE \n    (\n        (Deliciousness \u003e 3 AND NOT (Category = 'fruit')) OR\n         Category = 'fruit'\n    )\n```\nAt this point if you are thinking the above is readable and clear, great! If however the above syntax looks like voodoo, that's okay too! **Composure** is strongly typed, and leverages [operator overloading](https://en.wikipedia.org/wiki/Operator_overloading) to allow for concise, simple code, that compiles to plain ol' SQL.\n\nNote that we could have just as easily written the above as:\n```c#\nvar isDelicious = new ColumnEval(deliciousness, EvaluationType.GreaterThan, new Expression(3));\nvar isFruit = new ColumnEval(category, EvaluationType.Equal, newQuotedExpression(\"fruit\"));\n\n// Get query\nvar query = new NamedSet\n{\n    Select = new ISelectable[] { name, category, deliciousness },\n    From = new RawSet(\"dbo.Food\"),\n    Where = new IEvaluatable[]\n    {\n        new OrEval\n        (\n            new AndEval(isDelicious, new NotEval(isFruit)),\n            isFruit\n        )\n    }\n};\n```\n...and the resulting SQL would have been identical. **Composure** supports both the shorthand and longhand query syntax, so choose the style that works best for you.\n\nSkip to the [Syntax cheat-sheet](#syntax-cheat-sheet) below for a quick reference.\n\n## Basic JOIN\n```c#\n// Sets\nvar set1 = new RawSet(\"dbo.Food\");\nvar set2 = new RawSet(\"dbo.Category\");\n\n// Joins\nvar j1 = new Join { Set = set1, Alias = \"F\" };\nvar j2 = new Join\n{\n    Set = set2,\n    Alias = \"C\",\n    Type = JoinType.Inner,\n    On = new[] { new Column(\"CategoryId\", j1) == new Column(\"CategoryId\") }\n};\n\n// Columns with Sets specified\nvar name = new Column(\"Name\", j1);\nvar deliciousness = new Column(\"Deliciousness\", j1);\nvar categoryName = new Column(\"CategoryName\", j2);\n\n// Get query\nvar query = new JoinedSet\n{\n    Select  = new[] { name, deliciousness, categoryName },\n    From    = new[] { j1, j2 },\n    OrderBy = new[] { categoryName, name }\n};\n\nquery.ToString();\n```\nReturns:\n```sql\nSELECT \n    F.Name\n  , F.Deliciousness\n  , C.CategoryName \nFROM \n    dbo.Food AS F \n        INNER JOIN \n    dbo.Category AS C \n        ON F.CategoryId = C.CategoryId    \nORDER BY \n    C.CategoryName\n  , F.Name\n```\n\n## JOIN and GROUP BY\n```c#\n// Sets\nvar set1 = new RawSet(\"dbo.Food\");\nvar set2 = new RawSet(\"dbo.Category\");\n\n// Joins\nvar j1 = new Join { Set = set1, Alias = \"F\" };\nvar j2 = new Join\n{\n    Set = set2,\n    Alias = \"C\",\n    Type = JoinType.Inner,\n    On = new[] { new Column(\"CategoryId\", j1) == new Column(\"CategoryId\") }\n};\n\n// Columns with Sets specified\nvar categoryId = new Column(\"CategoryId\", j2);\nvar categoryName = new Column(\"CategoryName\", j2);\nvar deliciousness = new Column(\"Deliciousness\", j1);\n\n// Aggregation expressions\nvar calcMaxDeliciousness = new Expression($\"MAX({deliciousness})\");\nvar calcTotalCount = new Expression(\"COUNT(*)\");\n\n// Aggregate columns\nvar totalCount = new ExpressedColumn(\"TotalCount\", calcTotalCount);\nvar maxDeliciousness = new ExpressedColumn(\"MaxDeliciousness\", calcMaxDeliciousness);\n\n// Get query\nvar query = new JoinedSet\n{\n    Select  = new[] { categoryId, categoryName, totalCount, maxDeliciousness },\n    From    = new[] { j1, j2 },\n    Where   = new[] { deliciousness \u003e 3 },\n    GroupBy = new[] { categoryId, categoryName },\n    Having  = new[] { calcMaxDeliciousness \u003e= 5 },\n    OrderBy = new[] { categoryName }\n};\n\nquery.ToString();\n```\nReturns:\n```sql\nSELECT \n    C.CategoryId\n  , C.CategoryName\n  , COUNT(*) AS TotalCount\n  , MAX(F.Deliciousness) AS MaxDeliciousness \nFROM \n    dbo.Food AS F \n        INNER JOIN \n    dbo.Category AS C \n        ON F.CategoryId = C.CategoryId \nWHERE \n    F.Deliciousness \u003e 3 \nGROUP BY \n    C.CategoryId \n  , C.CategoryName \nHAVING \n    MAX(F.Deliciousness) \u003e= 5 \nORDER BY \n    C.CategoryName\n```\n\n## UNION and wrap as subquery\n```c#\n// Columns\nvar allColumns = new[] { \"Name\", \"Category\", \"Deliciousness\" };\n\n// Reusable function to get columns for each set\nvar getColumns() =\u003e allColumns.Select(c =\u003e new Column(c)).ToArray();\n\n// Sets\nvar set1 = new NamedSet { Select = getColumns(), From = \"dbo.Food\", Alias = \"F\" };\nvar set2 = new NamedSet { Select = getColumns(), From = \"dbo.Beverage\", Alias = \"B\" };\n\n// Union\nvar union = new UnionedSet { set1, set2 };\n\n// Get wrapper query\nvar wrapper = new VirtualSet { Select = getColumns(), From = union, Alias = \"W\" };\n\nwrapper.ToString();\n```\nReturns:\n```sql\nSELECT \n    W.Name\n  , W.Category\n  , W.Deliciousness \nFROM \n     (SELECT F.Name\n           , F.Category\n           , F.Deliciousness \n      FROM dbo.Food AS F \n      UNION \n      SELECT B.Name\n           , B.Category\n           , B.Deliciousness \n      FROM dbo.Beverage AS B) AS W\n```\n\n## CASE WHEN statements\n```c#\n// Columns\nvar name = new Column(\"Name\");\nvar category = new Column(\"Category\");\nvar deliciousness = new Column(\"Deliciousness\");\n\n// Cases\nvar isDelicious = deliciousness \u003e 3;\nvar isFruit = category == \"fruit\";\nvar isVeggie = category == \"vegetable\";\n\n// Case when\nvar foodCases = new CaseWhen\n{\n    Cases = new[]\n    {\n        isFruit                | \"It's a fruit\",\n        isDelicious \u0026 isVeggie | \"It's delicious and a vegetable\",\n        isVeggie               | \"It's a vegetable, but not delicious\",\n        isDelicious            | \"It's something else delicious\"\n    },\n    Else = new QuotedExpression(\"It's something else and not delicious!\")\n};\n\n// Get query\nvar query = new NamedSet\n{\n    Select = new[] { name, new ExpressedColumn(\"FoodCases\", foodCases) },\n    From   = \"dbo.Food\",\n};\n\nquery.ToString();\n```\nReturns:\n```sql\nSELECT \n    Name\n  , Category\n  , CASE \n         WHEN Category = 'fruit' THEN 'It''s a fruit' \n         WHEN (Deliciousness \u003e 3 AND Category = 'vegetable') THEN 'It''s delicious and a vegetable' \n         WHEN Category = 'vegetable' THEN 'It''s a vegetable, but not delicious' \n         WHEN Deliciousness \u003e 3 THEN 'It''s something else delicious' \n         ELSE 'It''s something else and not delicious!' \n    END AS FoodCases \nFROM \n    dbo.Food\n```\n\n## Using inheritance for predefined sets and intellisense\n```c#\npublic class FoodsAndCategoriesSet : JoinedSet\n{\n    public readonly Column FoodName;\n    public readonly Column CategoryId;\n    public readonly Column CategoryName;\n    public readonly Column Deliciousness;\n\n    // Predefine JOINs on initialization\n    public FoodsAndCategoriesSet()\n    {\n        // Sets\n        var foods = new RawSet(\"dbo.Food\");\n        var categories = new RawSet(\"dbo.Category\");\n\n        // Joins\n        var j1 = new Join { Set = foods, Alias = \"F\" };\n        var j2 = new Join\n        {\n            Set = categories,\n            Alias = \"C\",\n            Type = JoinType.Left,\n            On = new[] { new Column(\"CategoryId\", j1) == new Column(\"CategoryId\") }\n        };\n\n        // Columns\n        FoodName = new Column(\"Name\", j1);\n        CategoryId = new Column(\"CategoryId\", j2);\n        CategoryName = new Column(\"CategoryName\", j2);\n        Deliciousness = new Column(\"Deliciousness\", j1);\n\n        // Final joined Sets\n        From = new[] { j1, j2 };\n    }\n}\n```\nThe joined `FoodsAndCategoriesSet` is now conveniently predefined and wrapped in class,\nso its columns can be used as statically-typed properties with full intellisense support.\n```c#\n// Initialize joined set\nvar q = new FoodsAndCategoriesSet();\n\nq.Select = new[] { q.FoodName, q.CategoryName, q.Deliciousness };\nq.Where = new[]\n{\n    q.Deliciousness \u003e 3,\n    q.CategoryName == new[] { \"vegetable\", \"fruit\" }\n};\nq.OrderBy = new[] { q.CategoryName };\n\nq.ToString();\n```\nReturns:\n```sql\nSELECT \n    F.Name\n  , C.CategoryName\n  , F.Deliciousness \nFROM \n    dbo.Food AS F \n        LEFT JOIN \n    dbo.Category AS C \n        ON F.CategoryId = C.CategoryId \nWHERE \n    F.Deliciousness \u003e 3 \n    AND C.CategoryName IN ('vegetable', 'fruit' )  \nORDER BY \n    C.CategoryName\n```\n\n# Syntax cheat-sheet\n```c#\n deliciousness \u003e 3                               // Deliciousness \u003e 3\n deliciousness == 3 \u0026 5                          // Deliciousness BETWEEN 3 AND 5\n !(deliciousness == 2)                           // NOT (Deliciousness = 2)\n name == \"apple\" \u0026 category == \"fruit\"           // (Name = 'apple' AND Category = 'fruit')\n name == new[] { \"apple\", \"banana\" }             // Name IN ('apple', 'banana')\n name != new[] { \"hotdog\", \"sauce\" }             // Name NOT IN ('hotdog', 'sauce') \n\n new CaseWhen\n {\n     Cases = new[]               \n     {                                           // CASE \n         deliciousness \u003e 5  | \"Super delicious\", //      WHEN Deliciousness \u003e 5  THEN 'Super delicious'\n         deliciousness \u003c= 4 | \"So so\"            //      WHEN Deliciousness \u003c= 4 THEN 'So so'\n     },                                          //\n     Else = new QuotedExpression(\"Not yummy\")    //      ELSE 'Not yummy'           \n }                                               // END\n```\n","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fuwrit%2Fcomposure","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fuwrit%2Fcomposure","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fuwrit%2Fcomposure/lists"}