├── .github └── FUNDING.yml ├── .gitignore ├── Advanced.md ├── Builders.md ├── FAQ.md ├── LICENSE ├── README.md └── src ├── .editorconfig ├── InterpolatedSql.Dapper.StrongName ├── InterpolatedSql.Dapper.StrongName.csproj └── InterpolatedSql.Dapper.StrongName.nuspec ├── InterpolatedSql.Dapper.Tests ├── DynamicCRUDTests │ ├── DynamicCRUDTests.cs │ ├── POCO_CRUD_Extensions.cs │ └── Product.cs ├── ExplicitTypeTests.cs ├── ExtensibilityTests.cs ├── FluentQueryBuilderTests.cs ├── GlobalSuppressions.cs ├── InsertUpdateTests.cs ├── InterpolatedSql.Dapper.Tests.csproj ├── InterpolatedSqlBuilderOptionsTests.cs ├── MySQLTests.cs ├── PostgreSQLTests.cs ├── QueryBuilderTests.cs ├── Setup-MSSQL.sql ├── Setup-MySQL.sql ├── SqlBuilderTests.cs ├── TestHelper.cs └── TestSettings.json ├── InterpolatedSql.Dapper.sln ├── InterpolatedSql.Dapper ├── GlobalSuppressions.cs ├── IDapperSqlBuilderExtensions.cs ├── IDapperSqlCommand.cs ├── IDapperSqlCommandExtensions.cs ├── IDbConnectionExtensions.cs ├── ImmutableDapperCommand.cs ├── InterpolatedSql.Dapper.csproj ├── InterpolatedSql.Dapper.nuspec ├── InterpolatedSqlDapperOptions.cs ├── NuGetReadMe.md ├── README.md ├── SqlBuilderFactory.cs ├── SqlBuilders │ ├── FluentQueryBuilder │ │ ├── FluentQueryBuilder.cs │ │ ├── IDbConnectionExtensions.cs │ │ └── IFluentQueryBuilder.cs │ ├── IDapperSqlBuilder.cs │ ├── InsertUpdateBuilder │ │ ├── IDbConnectionExtensions.cs │ │ ├── IInsertUpdateBuilder.cs │ │ ├── InsertUpdateBuilder.cs │ │ └── InsertUpdateBuilder{U,RB,R}.cs │ ├── QueryBuilder │ │ ├── IQueryBuilder.cs │ │ ├── QueryBuilder.cs │ │ └── QueryBuilder{U,RB,R}.cs │ └── SqlBuilder │ │ ├── ISqlBuilder.cs │ │ ├── SqlBuilder.cs │ │ └── SqlBuilder{U,RB,R}.cs └── SqlParameters │ ├── ParametersDictionary.cs │ └── SqlParameterMapper.cs ├── InterpolatedSql.StrongName ├── InterpolatedSql.StrongName.csproj └── InterpolatedSql.StrongName.nuspec ├── InterpolatedSql.Tests ├── ExplicitStringTypesTests.cs ├── GlobalSuppressions.cs ├── Helpers │ ├── UnitTestsDbCommand.cs │ └── UnitTestsDbConnection.cs ├── InterpolatedSql.Tests.csproj └── SqlTests.cs ├── InterpolatedSql.sln ├── InterpolatedSql ├── GlobalSuppressions.cs ├── IInterpolatedSql.cs ├── ISqlCommand.cs ├── ImmutableInterpolatedSql.cs ├── InterpolatedSql.csproj ├── InterpolatedSql.nuspec ├── InterpolatedSqlParsers │ ├── IInterpolatedSqlParser.cs │ ├── InterpolatedSqlHandler.cs │ ├── InterpolatedSqlHandler{B}.cs │ └── InterpolatedSqlParser.cs ├── NuGetReadMe.md ├── SqlBuilders │ ├── Factories │ │ ├── ISqlBuilderFactory.cs │ │ ├── InterpolatedSqlBuilderFactory.cs │ │ └── SqlBuilderFactory.cs │ ├── FluentQueryBuilder │ │ ├── FluentQueryBuilder.cs │ │ ├── ICompleteBuilder.cs │ │ ├── IEmptyQueryBuilder.cs │ │ ├── IFluentQueryBuilder.cs │ │ ├── IFromBuilder.cs │ │ ├── IGroupByBuilder.cs │ │ ├── IGroupByHavingBuilder.cs │ │ ├── IOrderByBuilder.cs │ │ ├── ISelectBuilder.cs │ │ ├── ISelectDistinctBuilder.cs │ │ └── IWhereBuilder.cs │ ├── IBuildable.cs │ ├── IInterpolatedSqlBuilderBase.cs │ ├── ISqlEnricher.cs │ ├── InsertUpdateBuilder │ │ ├── IInsertUpdateBuilder.cs │ │ ├── InsertUpdateBuilder.cs │ │ └── InsertUpdateBuilder{U,RB,R}.cs │ ├── InterpolatedSqlBuilderBase.cs │ ├── InterpolatedSqlBuilderBase{U,R}.cs │ ├── InterpolatedSqlBuilderOptions.cs │ ├── QueryBuilder │ │ ├── Filter.cs │ │ ├── Filters.cs │ │ ├── IFilter.cs │ │ ├── IQueryBuilder.cs │ │ ├── QueryBuilder.cs │ │ └── QueryBuilder{U,RB,R}.cs │ └── SqlBuilder │ │ ├── ISqlBuilder.cs │ │ ├── ISqlBuilder{U,R}.cs │ │ ├── SqlBuilder.cs │ │ ├── SqlBuilder{U,R}.cs │ │ └── SqlBuilder{U}.cs └── SqlParameters │ ├── DbTypeParameterInfo.cs │ ├── InterpolatedSqlParameter.cs │ ├── InterpolatedSqlParameterComparer.cs │ ├── SqlParameterInfo.cs │ └── StringParameterInfo.cs ├── build.ps1 └── debug.snk /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: [Drizin] # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2] 4 | patreon: # Replace with a single Patreon username 5 | open_collective: # Replace with a single Open Collective username 6 | ko_fi: # Replace with a single Ko-fi username 7 | tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel 8 | community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry 9 | liberapay: # Replace with a single Liberapay username 10 | issuehunt: # Replace with a single IssueHunt username 11 | lfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry 12 | polar: # Replace with a single Polar username 13 | buy_me_a_coffee: # Replace with a single Buy Me a Coffee username 14 | custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2'] 15 | -------------------------------------------------------------------------------- /Advanced.md: -------------------------------------------------------------------------------- 1 | # Advanced Notes 2 | 3 | Technical notes about the library design. For contributors or experienced developers that need to extend the library behavior. 4 | 5 | ## About Generics 6 | 7 | This library provides different types of SQL builders for different purposes. Most builders (and some related classes) make heavy use of Generics. 8 | 9 | Basically you'll see the following generic types: 10 | 11 | - `R` represents the **return type** that is built by a SQL builder. For most cases you will want to use `IInterpolatedSql` (which contains `Sql`, `SqlParameters`) or (for Dapper) `IDapperSqlCommand` (which also includes a `DbConnection`). 12 | - `U`: represents the underlying type of a builder. This is used by the **Fluent Builder with Recursive Generics** (more about this below) 13 | 14 | Example: if you create a class `MyCustomBuilder` that inherits from `InterpolatedSqlBuilderBase` and builds `IInterpolatedSql`, you would define your class like this: 15 | ```cs 16 | public class MyCustomBuilder : InterpolatedSqlBuilderBase 17 | { 18 | // ctors... 19 | 20 | public override IInterpolatedSql Build() 21 | { 22 | return this.AsSql(); 23 | } 24 | } 25 | ``` 26 | 27 | On top of that, some builders (those like `QueryBuilder` that "enrich" the output, by using some custom logic to create or modify the generated SQL statement) also use a `RB` type. `RB` is the type of builder that is used to create the return `R`. (In other words, type `RB` should implement `IBuildable`, which means that it contains a method `Build()` returning a type `B`). 28 | 29 | ## Fluent Builder with Recursive Generics 30 | 31 | Most reusable builders implement the **"Fluent Builder with Recursive Generics" design pattern**, which is a cool way of letting developers extend those classes (inherit) while still using the "Fluent API" methods from the parent classes. 32 | 33 | In the previous example, `MyCustomBuilder` provides to the parent class (`InterpolatedSqlBuilderBase`) its own type, so all methods of the parent class that return the generic type `U` (underlying builder type) will in this case return `MyCustomBuilder`. 34 | 35 | So basically the subclass automatically gets a lot of "Fluent" methods (method-chaining) that will always return the same type, and therefore will offer custom methods of this subclass. 36 | 37 | Example 38 | ```cs 39 | // InterpolatedSqlBuilderBase 40 | // so U (underlying type) is MyCustomBuilder 41 | // and R (return type) is IInterpolatedSql 42 | public class MyCustomBuilder : InterpolatedSqlBuilderBase 43 | { 44 | //...ctors, etc. 45 | 46 | // Custom Methods 47 | public MyCustomBuilder MyCustomFluentMethod(FormattableString value) 48 | { 49 | // ... 50 | return this; 51 | } 52 | 53 | public IInterpolatedSql Build() // parent class defines/requires abstract R Build() 54 | { 55 | // AsSql() returns the underlying bags (stringbuilder and dictionary of parameters) 56 | // But you can also write your own logic here 57 | return base.AsSql(); 58 | } 59 | } 60 | 61 | // Usage 62 | var myBuilder = new MyCustomBuilder(); 63 | myBuilder 64 | .Append($"...") // this is defined in parent class InterpolatedSqlBuilderBase, and will return type U (MyCustomBuilder) 65 | .MyCustomFluentMethod($"something") // since previous call returned MyCustomBuilder, we can call our custom method 66 | .Append($"...") // and we can still call all other methods inherited from base class 67 | .Build() 68 | ``` 69 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Rick Drizin 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /src/.editorconfig: -------------------------------------------------------------------------------- 1 | # You can learn more about editorconfig here: https://docs.microsoft.com/en-us/visualstudio/ide/editorconfig-code-style-settings-reference 2 | [*.cs] 3 | 4 | 5 | #Core editorconfig formatting - indentation 6 | 7 | #use soft tabs (spaces) for indentation 8 | indent_style = space 9 | indent_size = 4 10 | tab_width = 4 11 | 12 | #Formatting - new line options 13 | end_of_line = crlf 14 | 15 | #place catch statements on a new line 16 | csharp_new_line_before_catch = true 17 | #place else statements on a new line 18 | csharp_new_line_before_else = true 19 | #require members of object intializers to be on separate lines 20 | csharp_new_line_before_members_in_object_initializers = true 21 | #require braces to be on a new line for object_collection_array_initializers, methods, types, control_blocks, properties, and accessors (also known as "Allman" style) 22 | csharp_new_line_before_open_brace = object_collection_array_initializers, methods, types, control_blocks, properties, accessors 23 | 24 | #Formatting - organize using options 25 | 26 | #do not place System.* using directives before other using directives 27 | dotnet_sort_system_directives_first = false 28 | 29 | #Formatting - spacing options 30 | 31 | #require NO space between a cast and the value 32 | csharp_space_after_cast = false 33 | #require a space before the colon for bases or interfaces in a type declaration 34 | csharp_space_after_colon_in_inheritance_clause = true 35 | #require a space after a keyword in a control flow statement such as a for loop 36 | csharp_space_after_keywords_in_control_flow_statements = true 37 | #require a space before the colon for bases or interfaces in a type declaration 38 | csharp_space_before_colon_in_inheritance_clause = true 39 | #remove space within empty argument list parentheses 40 | csharp_space_between_method_call_empty_parameter_list_parentheses = false 41 | #remove space between method call name and opening parenthesis 42 | csharp_space_between_method_call_name_and_opening_parenthesis = false 43 | #do not place space characters after the opening parenthesis and before the closing parenthesis of a method call 44 | csharp_space_between_method_call_parameter_list_parentheses = false 45 | #remove space within empty parameter list parentheses for a method declaration 46 | csharp_space_between_method_declaration_empty_parameter_list_parentheses = false 47 | #place a space character after the opening parenthesis and before the closing parenthesis of a method declaration parameter list. 48 | csharp_space_between_method_declaration_parameter_list_parentheses = false 49 | 50 | #Formatting - wrapping options 51 | 52 | #leave code block on single line 53 | csharp_preserve_single_line_blocks = true 54 | 55 | #Style - Code block preferences 56 | 57 | #prefer no curly braces if allowed 58 | csharp_prefer_braces = false:suggestion 59 | 60 | #Style - expression bodied member options 61 | 62 | #prefer expression-bodied members for accessors 63 | csharp_style_expression_bodied_accessors = true:suggestion 64 | #prefer block bodies for constructors 65 | csharp_style_expression_bodied_constructors = false:suggestion 66 | #prefer block bodies for methods 67 | csharp_style_expression_bodied_methods = false:suggestion 68 | #prefer expression-bodied members for properties 69 | csharp_style_expression_bodied_properties = true:suggestion 70 | 71 | #Style - expression level options 72 | 73 | #prefer out variables to be declared before the method call 74 | csharp_style_inlined_variable_declaration = false:suggestion 75 | #prefer the language keyword for member access expressions, instead of the type name, for types that have a keyword to represent them 76 | dotnet_style_predefined_type_for_member_access = true:suggestion 77 | 78 | #Style - Expression-level preferences 79 | 80 | #prefer objects to be initialized using object initializers when possible 81 | dotnet_style_object_initializer = true:suggestion 82 | 83 | #Style - implicit and explicit types 84 | 85 | #prefer explicit type over var in all cases, unless overridden by another code style rule 86 | csharp_style_var_elsewhere = false:suggestion 87 | #prefer explicit type over var to declare variables with built-in system types such as int 88 | csharp_style_var_for_built_in_types = false:suggestion 89 | #prefer var when the type is already mentioned on the right-hand side of a declaration expression 90 | csharp_style_var_when_type_is_apparent = true:suggestion 91 | 92 | #Style - language keyword and framework type options 93 | 94 | #prefer the language keyword for local variables, method parameters, and class members, instead of the type name, for types that have a keyword to represent them 95 | dotnet_style_predefined_type_for_locals_parameters_members = true:suggestion 96 | 97 | #Style - Miscellaneous preferences 98 | 99 | #prefer anonymous functions over local functions 100 | csharp_style_pattern_local_over_anonymous_function = false:suggestion 101 | 102 | #Style - modifier options 103 | 104 | #prefer accessibility modifiers to be declared except for public interface members. This will currently not differ from always and will act as future proofing for if C# adds default interface methods. 105 | dotnet_style_require_accessibility_modifiers = for_non_interface_members:suggestion 106 | 107 | #Style - Modifier preferences 108 | 109 | #when this rule is set to a list of modifiers, prefer the specified ordering. 110 | csharp_preferred_modifier_order = public,private,protected,internal,static,readonly,virtual:suggestion 111 | 112 | #Style - Pattern matching 113 | 114 | #prefer pattern matching instead of is expression with type casts 115 | csharp_style_pattern_matching_over_as_with_null_check = true:suggestion 116 | 117 | #Style - qualification options 118 | 119 | #prefer fields not to be prefaced with this. or Me. in Visual Basic 120 | dotnet_style_qualification_for_field = false:suggestion 121 | #prefer methods not to be prefaced with this. or Me. in Visual Basic 122 | dotnet_style_qualification_for_method = false:suggestion 123 | #prefer properties not to be prefaced with this. or Me. in Visual Basic 124 | dotnet_style_qualification_for_property = false:suggestion 125 | -------------------------------------------------------------------------------- /src/InterpolatedSql.Dapper.StrongName/InterpolatedSql.Dapper.StrongName.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | netstandard2.0;net462;net472;net5.0;net6.0;net7.0 5 | Rick Drizin 6 | MIT 7 | https://github.com/Drizin/InterpolatedSql/ 8 | Dapper Query Builder using Fluent API and String Interpolation 9 | Rick Drizin 10 | Rick Drizin 11 | 2.4.0 12 | false 13 | InterpolatedSql.Dapper (Strong Named) 14 | InterpolatedSql.Dapper.StrongName 15 | InterpolatedSql.Dapper.StrongName.xml 16 | interpolated sql;dapper;query-builder;query builder;dapperquerybuilder;dapper-query-builder;dapper-interpolation;dapper-interpolated-string 17 | true 18 | true 19 | 20 | NuGetReadMe.md 21 | InterpolatedSql.Dapper.StrongName 22 | enable 23 | 9.0 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | all 32 | runtime; build; native; contentfiles; analyzers; buildtransitive 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | True 45 | ..\debug.snk 46 | 47 | 48 | 49 | 50 | True 51 | ..\release.snk 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | -------------------------------------------------------------------------------- /src/InterpolatedSql.Dapper.StrongName/InterpolatedSql.Dapper.StrongName.nuspec: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | InterpolatedSql.Dapper.StrongName 5 | InterpolatedSql.Dapper (Strong Named) 6 | Rick Drizin 7 | Rick Drizin 8 | MIT 9 | https://github.com/Drizin/InterpolatedSql 10 | false 11 | InterpolatedSql.Dapper: Dapper Query Builder using String Interpolation and Fluent API 12 | Copyright Rick Drizin 2020 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /src/InterpolatedSql.Dapper.Tests/DynamicCRUDTests/DynamicCRUDTests.cs: -------------------------------------------------------------------------------- 1 | using Dapper; 2 | using NUnit.Framework; 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Data; 6 | using System.Data.SqlClient; 7 | using System.Linq; 8 | 9 | namespace InterpolatedSql.Dapper.Tests.DynamicCRUDTests 10 | { 11 | public class DynamicCRUDTests 12 | { 13 | IDbConnection cn; 14 | 15 | #region Setup 16 | [SetUp] 17 | public void Setup() 18 | { 19 | cn = new SqlConnection(TestHelper.GetMSSQLConnectionString()); 20 | } 21 | #endregion 22 | 23 | int businessEntityID = 1; 24 | string nationalIDNumber = "295847284"; 25 | DateTime birthDate = new DateTime(1969, 01, 29); 26 | string maritalStatus = "S"; // single 27 | string gender = "M"; 28 | 29 | 30 | [Test] 31 | public void TestFullInsert() 32 | { 33 | var product = new Product() { Name = "ProductName", ProductNumber = "1234", SellStartDate = DateTime.Now, ModifiedDate = DateTime.Now, SafetyStockLevel = 5, ReorderPoint = 700 }; 34 | 35 | int deleted = cn.Execute($@" 36 | DELETE r FROM [Production].[Product] p INNER JOIN [Production].[ProductReview] r ON p.[ProductId]=r.[ProductId] WHERE (p.[ProductNumber]='1234' OR p.[ProductNumber]='12345'); 37 | DELETE p FROM [Production].[Product] p WHERE (p.[ProductNumber]='1234' OR p.[ProductNumber]='12345'); 38 | "); 39 | 40 | cn.Save(product); 41 | 42 | product.Name = "Name2"; 43 | product.ProductNumber = "12345"; 44 | cn.Update(product, product.ChangedProperties); 45 | } 46 | 47 | 48 | 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/InterpolatedSql.Dapper.Tests/DynamicCRUDTests/POCO_CRUD_Extensions.cs: -------------------------------------------------------------------------------- 1 | using Dapper; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Data; 5 | using System.Linq; 6 | using System.Runtime.CompilerServices; 7 | 8 | namespace InterpolatedSql.Dapper.Tests.DynamicCRUDTests 9 | { 10 | public static class POCO_CRUD_Extensions 11 | { 12 | /// 13 | /// Saves (if new) or Updates (if existing) 14 | /// 15 | public static void Save(this IDbConnection conn, Product p) 16 | { 17 | if (p.ProductId == default(int)) 18 | conn.Insert(p); 19 | else 20 | conn.Update(p); 21 | 22 | p.MarkAsClean(); 23 | } 24 | 25 | public static void Insert(this IDbConnection conn, Product p) 26 | { 27 | string cmd = @" 28 | INSERT INTO [Production].[Product] 29 | ( 30 | [Class], 31 | [Color], 32 | [DaysToManufacture], 33 | [DiscontinuedDate], 34 | [FinishedGoodsFlag], 35 | [ListPrice], 36 | [MakeFlag], 37 | [ModifiedDate], 38 | [Name], 39 | [ProductLine], 40 | [ProductModelID], 41 | [ProductNumber], 42 | [ProductSubcategoryID], 43 | [ReorderPoint], 44 | [SafetyStockLevel], 45 | [SellEndDate], 46 | [SellStartDate], 47 | [Size], 48 | [SizeUnitMeasureCode], 49 | [StandardCost], 50 | [Style], 51 | [Weight], 52 | [WeightUnitMeasureCode] 53 | ) 54 | VALUES 55 | ( 56 | @Class, 57 | @Color, 58 | @DaysToManufacture, 59 | @DiscontinuedDate, 60 | @FinishedGoodsFlag, 61 | @ListPrice, 62 | @MakeFlag, 63 | @ModifiedDate, 64 | @Name, 65 | @ProductLine, 66 | @ProductModelId, 67 | @ProductNumber, 68 | @ProductSubcategoryId, 69 | @ReorderPoint, 70 | @SafetyStockLevel, 71 | @SellEndDate, 72 | @SellStartDate, 73 | @Size, 74 | @SizeUnitMeasureCode, 75 | @StandardCost, 76 | @Style, 77 | @Weight, 78 | @WeightUnitMeasureCode 79 | )"; 80 | 81 | p.ProductId = conn.QuerySingle(cmd + "SELECT SCOPE_IDENTITY();", p); 82 | } 83 | 84 | public static void Update(this IDbConnection conn, Product p) 85 | { 86 | string cmd = @" 87 | UPDATE [Production].[Product] SET 88 | [Class] = @Class, 89 | [Color] = @Color, 90 | [DaysToManufacture] = @DaysToManufacture, 91 | [DiscontinuedDate] = @DiscontinuedDate, 92 | [FinishedGoodsFlag] = @FinishedGoodsFlag, 93 | [ListPrice] = @ListPrice, 94 | [MakeFlag] = @MakeFlag, 95 | [ModifiedDate] = @ModifiedDate, 96 | [Name] = @Name, 97 | [ProductLine] = @ProductLine, 98 | [ProductModelID] = @ProductModelId, 99 | [ProductNumber] = @ProductNumber, 100 | [ProductSubcategoryID] = @ProductSubcategoryId, 101 | [ReorderPoint] = @ReorderPoint, 102 | [SafetyStockLevel] = @SafetyStockLevel, 103 | [SellEndDate] = @SellEndDate, 104 | [SellStartDate] = @SellStartDate, 105 | [Size] = @Size, 106 | [SizeUnitMeasureCode] = @SizeUnitMeasureCode, 107 | [StandardCost] = @StandardCost, 108 | [Style] = @Style, 109 | [Weight] = @Weight, 110 | [WeightUnitMeasureCode] = @WeightUnitMeasureCode 111 | WHERE 112 | [ProductID] = @ProductId"; 113 | 114 | conn.Execute(cmd, p); 115 | } 116 | 117 | public static void Update(this IDbConnection conn, Product p, HashSet changedProperties) 118 | { 119 | if (!changedProperties.Any()) 120 | return; 121 | 122 | var cmd = conn.SqlBuilder($@" 123 | UPDATE [Production].[Product] SET 124 | /**sets**/ 125 | WHERE 126 | [ProductID] = @ProductId"); 127 | cmd.Replace("/**sets**/", FormattableStringFactory.Create(string.Join("," + Environment.NewLine, changedProperties.Select(prop => $"[{prop}] = @{prop}")))); 128 | 129 | //Assert.AreEqual // "UPDATE [Production].[Product] SET\r\n[Name] = @Name,\r\n[ProductNumber] = @ProductNumber\r\nWHERE\r\n [ProductID] = @ProductId" 130 | 131 | cmd.AddObjectProperties(p); 132 | cmd.Build().Execute(); 133 | } 134 | } 135 | } 136 | -------------------------------------------------------------------------------- /src/InterpolatedSql.Dapper.Tests/ExtensibilityTests.cs: -------------------------------------------------------------------------------- 1 | using NUnit.Framework; 2 | using System.Data; 3 | using System; 4 | using InterpolatedSql.Dapper.SqlBuilders; 5 | using System.Linq; 6 | 7 | namespace InterpolatedSql.Dapper.Tests 8 | { 9 | /// 10 | public class MyQueryBuilder : InterpolatedSql.Dapper.SqlBuilders.QueryBuilder 11 | { 12 | public MyQueryBuilder(IDbConnection connection, FormattableString query) : base(opts => new SqlBuilder(connection, opts), (opts, format, arguments) => new SqlBuilder(connection, opts, format, arguments), connection, query) 13 | { 14 | } 15 | public MyQueryBuilder AppendCustomObject(FormattableString value, string key, string separator, string prefixBeforeFirstItem = "") 16 | { 17 | if (!ObjectBag!.ContainsKey(key)) 18 | ObjectBag[key] = new InterpolatedSql.SqlBuilders.SqlBuilder(); 19 | var builder = (InterpolatedSql.SqlBuilders.SqlBuilder)base.ObjectBag[key]; 20 | builder.AppendRaw(builder.IsEmpty ? prefixBeforeFirstItem : separator); 21 | builder.AppendFormattableString(value); 22 | return this; 23 | } 24 | public MyQueryBuilder From(FormattableString value, string key) => AppendCustomObject(value, "from:" + key, "\nINNER JOIN "); 25 | public MyQueryBuilder Select(FormattableString value, string key) => AppendCustomObject(value, "select:" + key, ", "); 26 | public MyQueryBuilder Where(FormattableString value, string key) => AppendCustomObject(value, "filters:" + key, " AND "); 27 | public MyQueryBuilder Add(string key, FormattableString value) => AppendCustomObject(value, key, ""); 28 | public void ReplaceCustomObjects() 29 | { 30 | foreach(var key in base.ObjectBag!.Keys) 31 | this.Replace("/**" + key + "**/", ((InterpolatedSql.SqlBuilders.SqlBuilder)base.ObjectBag![key]).Build()); 32 | } 33 | } 34 | 35 | public class ExtensibilityTests 36 | { 37 | 38 | [Test] 39 | public void Test1() 40 | { 41 | IDbConnection cn = null!; 42 | string someValue = "hope it"; 43 | string otherValue = "works"; 44 | 45 | var q = new MyQueryBuilder(cn, $$""" 46 | select 47 | MyInnerTable.* 48 | /**selects**/ 49 | from 50 | ( 51 | select 52 | Id 53 | /**select:somekeysuffix**/ 54 | from 55 | SomeTable 56 | /**from:somekeysuffix**/ 57 | where 58 | 1=1 59 | /**filters:somekeysuffix**/ 60 | ) MyInnerTable 61 | where 62 | 1=1 63 | /**filters**/ 64 | group by 65 | /**groupby**/ 66 | order by 67 | /**orderby**/ 68 | 69 | /**myCustomUnion**/ 70 | """) 71 | .From($"join OtherTable t on SomeTable.OtherTableId=t.Id", "somekeysuffix") 72 | .Select($"morecolumn") 73 | .Select($"SomeColumn", "somekeysuffix") 74 | .Where($"SomeColumn={someValue}", "somekeysuffix") 75 | .Where($"OtherColumn={otherValue}") 76 | .GroupBy($"SomeColumn") 77 | .OrderBy($"SomeColumn") 78 | .Replace("/**myCustomUnion**/", $"SomeArbitrarySql"); 79 | ; 80 | q.ReplaceCustomObjects(); 81 | var cmd = q.Build(); 82 | 83 | Assert.AreEqual(""" 84 | select 85 | MyInnerTable.* 86 | , morecolumn 87 | from 88 | ( 89 | select 90 | Id 91 | SomeColumn 92 | from 93 | SomeTable 94 | join OtherTable t on SomeTable.OtherTableId=t.Id 95 | where 96 | 1=1 97 | SomeColumn=@p0 98 | ) MyInnerTable 99 | where 100 | 1=1 101 | AND OtherColumn=@p1 102 | group by 103 | GROUP BY SomeColumn 104 | order by 105 | ORDER BY SomeColumn 106 | 107 | SomeArbitrarySql 108 | """, cmd.Sql); 109 | 110 | Assert.That(cmd.DapperParameters.ParameterNames.Contains("p0")); 111 | Assert.That(cmd.DapperParameters.ParameterNames.Contains("p1")); 112 | Assert.AreEqual(someValue, cmd.DapperParameters["p0"].Value); 113 | Assert.AreEqual(otherValue, cmd.DapperParameters["p1"].Value); 114 | } 115 | 116 | public class MyCustomSqlParameterMapper : SqlParameterMapper 117 | { 118 | public override string CalculateAutoParameterName(InterpolatedSqlParameter parameter, int pos, InterpolatedSql.SqlBuilders.InterpolatedSqlBuilderOptions options) 119 | { 120 | // InterpolatedSql.Dapper.SqlParameterMapper returns this 121 | //return 122 | // //options.AutoGeneratedParameterPrefix + 123 | // parameter.Format! + 124 | // (base.IsEnumerable(parameter.Argument) ? options.ParameterArrayNameSuffix : "") + 125 | // pos.ToString(); 126 | 127 | // But if we want to give explicit names: 128 | return parameter.Format!.Split( ':' )[ 0 ] + (base.IsEnumerable(parameter.Argument) ? options.ParameterArrayNameSuffix : ""); 129 | } 130 | } 131 | 132 | [Test] 133 | [NonParallelizable] 134 | public void Test2() 135 | { 136 | DateTime asOfDate = DateTime.Now; 137 | IDbConnection cn = null!; 138 | 139 | // This would work with pure InterpolatedSqlL 140 | //InterpolatedSql.SqlBuilders.InterpolatedSqlBuilderOptions.DefaultOptions.CalculateAutoParameterName = (p, pos) => p.Format!; 141 | 142 | // But InterpolatedSql.Dapper builders will override CalculateAutoParameterName using InterpolatedSqlDapperOptions.InterpolatedSqlParameterParser, so we can subclass that 143 | InterpolatedSqlDapperOptions.InterpolatedSqlParameterParser = new MyCustomSqlParameterMapper(); 144 | 145 | var query = cn.QueryBuilder($"Select * from Users Where ModifiedDate >= {asOfDate:asOfDate}").Build(); 146 | Assert.AreEqual("Select * from Users Where ModifiedDate >= @asOfDate", query.Sql); 147 | 148 | var name = "John"; 149 | query = cn.QueryBuilder($"Select * from Users Where Name = {name:userName:varchar(200)}").Build(); 150 | Assert.AreEqual("Select * from Users Where Name = @userName", query.Sql); 151 | var parameterInfo = (query.SqlParameters[0].Argument as StringParameterInfo)!; 152 | 153 | Assert.NotNull(parameterInfo, "Argument is not StringParameterInfo."); 154 | Assert.AreEqual(name, parameterInfo.Value, "Parameter value mismatch."); 155 | Assert.AreEqual(true, parameterInfo.IsAnsi, "IsAnsi mismatch."); 156 | Assert.AreEqual(false, parameterInfo.IsFixedLength, "IsFixedLength mismatch."); 157 | Assert.AreEqual(200, parameterInfo.Length, "Argument length mismatch."); 158 | 159 | InterpolatedSqlDapperOptions.InterpolatedSqlParameterParser = new SqlParameterMapper(); 160 | } 161 | 162 | } 163 | } 164 | -------------------------------------------------------------------------------- /src/InterpolatedSql.Dapper.Tests/GlobalSuppressions.cs: -------------------------------------------------------------------------------- 1 | // This file is used by Code Analysis to maintain SuppressMessage 2 | // attributes that are applied to this project. 3 | // Project-level suppressions either have no target or are given 4 | // a specific target and scoped to a namespace, type, member, etc. 5 | 6 | using System.Diagnostics.CodeAnalysis; 7 | 8 | [assembly: SuppressMessage("Style", "IDE0008:Use explicit type", Justification = "var avoids noisy code")] 9 | -------------------------------------------------------------------------------- /src/InterpolatedSql.Dapper.Tests/InsertUpdateTests.cs: -------------------------------------------------------------------------------- 1 | using NUnit.Framework; 2 | using System.Data; 3 | using System.Data.SqlClient; 4 | using InterpolatedSql.Dapper.SqlBuilders.InsertUpdateBuilder; 5 | 6 | namespace InterpolatedSql.Dapper.Tests 7 | { 8 | 9 | [TestFixture] 10 | public class InsertUpdateTests 11 | { 12 | IDbConnection cn; 13 | 14 | public InsertUpdateTests() { } // nunit requires parameterless constructor 15 | 16 | [SetUp] 17 | public void Setup() 18 | { 19 | cn = new SqlConnection(TestHelper.GetMSSQLConnectionString()); 20 | } 21 | 22 | [Test] 23 | public void TestInsert() 24 | { 25 | var insert = cn 26 | .InsertUpdateBuilder("[Production].[Product]") 27 | .AddColumn("ProductID", 1, includeInInsert: false, includeInUpdate: false) 28 | .AddColumn("Name", "My Product") 29 | .AddColumn("ProductNumber", "AR-5381") 30 | .AddColumn("ListPrice", 10.20) 31 | .GetInsertSql(); 32 | 33 | Assert.AreEqual("INSERT INTO [Production].[Product] (Name, ProductNumber, ListPrice) VALUES (@p0, @p1, @p2);", insert.Sql); 34 | Assert.AreEqual(3, insert.SqlParameters.Count); 35 | Assert.AreEqual("My Product", insert.DapperParameters["p0"].Value); 36 | Assert.AreEqual("AR-5381", insert.DapperParameters["p1"].Value); 37 | Assert.AreEqual(10.20, insert.DapperParameters["p2"].Value); 38 | } 39 | 40 | 41 | [Test] 42 | public void TestUpdate() 43 | { 44 | int productId = 1; 45 | 46 | var update = cn 47 | .InsertUpdateBuilder("[Production].[Product]") 48 | .AddColumn("ProductID", 1, includeInInsert: false, includeInUpdate: false) 49 | .AddColumn("Name", "My Product") 50 | .AddColumn("ProductNumber", "AR-5381") 51 | .AddColumn("ListPrice", 10.20) 52 | .GetUpdateSql($"ProductID={productId}"); 53 | 54 | Assert.AreEqual("UPDATE [Production].[Product] SET Name=@p0, ProductNumber=@p1, ListPrice=@p2 WHERE ProductID=@p3", update.Sql); 55 | Assert.AreEqual(4, update.SqlParameters.Count); 56 | Assert.AreEqual("My Product", update.DapperParameters["p0"].Value); 57 | Assert.AreEqual("AR-5381", update.DapperParameters["p1"].Value); 58 | Assert.AreEqual(10.20, update.DapperParameters["p2"].Value); 59 | Assert.AreEqual(productId, update.DapperParameters["p3"].Value); 60 | } 61 | 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /src/InterpolatedSql.Dapper.Tests/InterpolatedSql.Dapper.Tests.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | net462;net5.0;net6.0;net7.0 5 | enable 6 | 7 | false 8 | 9 | Rick Drizin 10 | 11 | Rick Drizin 12 | 13 | MIT 14 | 15 | https://github.com/Drizin/InterpolatedSql/ 16 | 11.0 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | Always 41 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /src/InterpolatedSql.Dapper.Tests/InterpolatedSqlBuilderOptionsTests.cs: -------------------------------------------------------------------------------- 1 | using NUnit.Framework; 2 | using InterpolatedSql.SqlBuilders; 3 | using System.Data.SqlClient; 4 | 5 | namespace InterpolatedSql.Dapper.Tests; 6 | 7 | public class InterpolatedSqlBuilderOptionsTests 8 | { 9 | [Test] 10 | public void SqlBuilderWithOptions() 11 | { 12 | var cn = new SqlConnection(); 13 | var qb = cn.SqlBuilder(new InterpolatedSqlBuilderOptions { DatabaseParameterSymbol = ":" }, $"{"A"} {1}"); 14 | var sql = qb.Build().Sql; 15 | Assert.AreEqual(":p0 :p1", sql); 16 | } 17 | 18 | [Test] 19 | public void QueryBuilderWithOptions() 20 | { 21 | var cn = new SqlConnection(); 22 | var qb = cn.SqlBuilder(new InterpolatedSqlBuilderOptions { DatabaseParameterSymbol = ":" }, $"{"A"} {1}"); 23 | var sql = qb.Build().Sql; 24 | Assert.AreEqual(":p0 :p1", sql); 25 | } 26 | } -------------------------------------------------------------------------------- /src/InterpolatedSql.Dapper.Tests/MySQLTests.cs: -------------------------------------------------------------------------------- 1 | using global::Dapper; 2 | using InterpolatedSql.Tests; 3 | using MySql.Data.MySqlClient; 4 | using NUnit.Framework; 5 | using System; 6 | using System.Collections.Generic; 7 | using System.Data; 8 | using System.Data.OleDb; 9 | using System.Linq; 10 | using System.Text; 11 | 12 | namespace InterpolatedSql.Dapper.Tests 13 | { 14 | [TestFixture(Ignore = "true")] 15 | public class MySQLTests 16 | { 17 | UnitTestsDbConnection cn; 18 | 19 | #region Setup 20 | [SetUp] 21 | public void Setup() 22 | { 23 | cn = new UnitTestsDbConnection(new MySqlConnection(TestHelper.GetMySQLConnectionString())); 24 | } 25 | #endregion 26 | 27 | public class Author 28 | { 29 | public int author_id { get; set; } 30 | public string name_last { get; set; } 31 | public string name_first { get; set; } 32 | public string country { get; set; } 33 | } 34 | 35 | [Test] 36 | public void TestConnection() 37 | { 38 | var authors = SqlMapper.Query(cn, "SELECT * FROM authors"); 39 | Assert.That(authors.Any()); 40 | } 41 | 42 | [Test] 43 | public void TestParameters() 44 | { 45 | string search = "%as%"; 46 | var authors = cn.QueryBuilder($"SELECT * FROM authors WHERE name_last like {search}").Build().Query(); 47 | Assert.That(authors.Any()); 48 | Assert.AreEqual(cn.PreviousCommands.Last().CommandText, "SELECT * FROM authors WHERE name_last like @p0"); 49 | } 50 | 51 | [Test] 52 | public void TestArrays() 53 | { 54 | List lastNames = new List() 55 | { 56 | "Kafka", 57 | "de Assis", 58 | }; 59 | 60 | var authors = cn.QueryBuilder($"SELECT * FROM authors WHERE name_last IN {lastNames}").Build().Query(); 61 | Assert.That(authors.Any()); 62 | Assert.AreEqual(cn.PreviousCommands.Last().CommandText, "SELECT * FROM authors WHERE name_last IN (@parray01,@parray02)"); 63 | } 64 | 65 | [Test] 66 | public void TestNullableArrays() 67 | { 68 | int[]? ids = new[] { 1, 2 }; 69 | 70 | var authors = cn.QueryBuilder($"SELECT * FROM authors WHERE author_id IN {ids}").Build().Query(); 71 | Assert.That(authors.Any()); 72 | 73 | string[]? lastNames = new string[] 74 | { 75 | "Kafka", 76 | "de Assis", 77 | }; 78 | 79 | authors = cn.QueryBuilder($"SELECT * FROM authors WHERE name_last IN {lastNames}").Build().Query(); 80 | Assert.AreEqual(cn.PreviousCommands.Last().CommandText, "SELECT * FROM authors WHERE name_last IN (@parray01,@parray02)"); 81 | 82 | AuthorsEnum[]? authorIds = new AuthorsEnum[] { AuthorsEnum.Kafka, AuthorsEnum.MachadoDeAssis }; 83 | authors = cn.QueryBuilder($"SELECT * FROM authors WHERE author_id IN {authorIds}").Build().Query(); 84 | Assert.That(authors.Any()); 85 | Assert.AreEqual(cn.PreviousCommands.Last().CommandText, "SELECT * FROM authors WHERE author_id IN (@parray01,@parray02)"); 86 | 87 | } 88 | 89 | enum AuthorsEnum 90 | { 91 | Kafka = 1, 92 | MachadoDeAssis = 2, 93 | } 94 | 95 | 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /src/InterpolatedSql.Dapper.Tests/PostgreSQLTests.cs: -------------------------------------------------------------------------------- 1 | using global::Dapper; 2 | using InterpolatedSql.SqlBuilders; 3 | using Npgsql; 4 | using NUnit.Framework; 5 | using System.Data; 6 | using System.Linq; 7 | 8 | namespace InterpolatedSql.Dapper.Tests 9 | { 10 | [TestFixture(Ignore = "true")] 11 | public class PostgreSQLTests 12 | { 13 | IDbConnection cn; 14 | 15 | #region Setup 16 | [SetUp] 17 | public void Setup() 18 | { 19 | cn = new NpgsqlConnection(TestHelper.GetPostgreSQLConnectionString()); 20 | } 21 | [TearDown] 22 | public void TearDown() 23 | { 24 | // reverting back for next unit tests 25 | InterpolatedSqlBuilderOptions.DefaultOptions.DatabaseParameterSymbol = "@"; 26 | InterpolatedSqlBuilderOptions.DefaultOptions.AutoGeneratedParameterPrefix = "p"; 27 | } 28 | #endregion 29 | 30 | public class TitleRecord 31 | { 32 | public int Film_ID { get; set; } 33 | public string Title { get; set; } 34 | public string Description { get; set; } 35 | public int Release_Year { get; set; } 36 | } 37 | 38 | [Test] 39 | public void TestConnection() 40 | { 41 | var titles = SqlMapper.Query(cn, "SELECT * FROM film"); 42 | Assert.That(titles.Any()); 43 | } 44 | 45 | [Test] 46 | public void TestParameters() 47 | { 48 | // Npgsql will rewrite SQL query and convert @p0, @p1, etc to positional placeholders ($1, $2)... https://stackoverflow.com/a/49544098/3606250 49 | string search = "%Dinosaur%"; 50 | var titles = cn.QueryBuilder($"SELECT * FROM film WHERE title like {search}").Build().Query(); 51 | Assert.That(titles.Any()); 52 | } 53 | 54 | [Test] 55 | public void TestAutoGeneratedParameterPrefix() 56 | { 57 | // Npgsql does NOT require this, but it may be required in some databases/drivers which do not accept "at-parameters" (@p0, @p1, etc). 58 | InterpolatedSqlBuilderOptions.DefaultOptions.DatabaseParameterSymbol = ":"; 59 | InterpolatedSqlBuilderOptions.DefaultOptions.AutoGeneratedParameterPrefix = "parm"; 60 | 61 | string search = "%Dinosaur%"; 62 | var cmd = cn.QueryBuilder($"SELECT * FROM film WHERE title like {search}"); 63 | 64 | Assert.AreEqual("SELECT * FROM film WHERE title like :parm0", cmd.Build().Sql); 65 | } 66 | 67 | 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /src/InterpolatedSql.Dapper.Tests/Setup-MSSQL.sql: -------------------------------------------------------------------------------- 1 | /*********** How to setup a MSSQL for tests ***********/ 2 | 3 | -- 1) Download https://github.com/Microsoft/sql-server-samples/releases/download/adventureworks/AdventureWorks2019.bak 4 | -- 2) Find your DATA FOLDER (if it's not C:\Program Files\Microsoft SQL Server\MSSQL15.MSSQLSERVER\MSSQL\DATA\) using this query: 5 | /* 6 | SELECT TOP 1 7 | substring(f.physical_name, 1, len(f.physical_name)-charindex('\', reverse(f.physical_name))+1) 8 | from sys.master_files f INNER JOIN sys.databases db ON f.database_id=db.database_id 9 | order by len ( substring(f.physical_name, 1, len(f.physical_name)-charindex('\', reverse(f.physical_name))+1) ) 10 | */ 11 | -- Replace folders below with the correct locations. 12 | 13 | RESTORE DATABASE [AdventureWorks2019] FROM DISK = N'C:\Users\youruser\Downloads\AdventureWorks2019.bak' WITH FILE=1, 14 | MOVE 'AdventureWorks2017' TO 'C:\Program Files\Microsoft SQL Server\MSSQL15.MSSQLSERVER\MSSQL\DATA\AdventureWorks2019.mdf', 15 | MOVE 'AdventureWorks2017_log' TO 'C:\Program Files\Microsoft SQL Server\MSSQL15.MSSQLSERVER\MSSQL\DATA\AdventureWorks2019.ldf' 16 | GO 17 | 18 | -- 3) Adjust Connection string in TestSettings.json 19 | -- e.g. remove "\SQLEXPRESS", and/or change "(local)" by some hostname 20 | -- e.g. replace "Integrated Security=True" by "User Id=username;Password=password" 21 | 22 | -- 4) Create some extra objects (AdventureWorks does not have everything we need): 23 | USE [AdventureWorks2019] 24 | GO 25 | 26 | CREATE PROCEDURE [sp_TestOutput] 27 | @Input1 [int], 28 | @Output1 [int] OUTPUT 29 | AS 30 | BEGIN 31 | SET @Output1 = 2 32 | END; 33 | GO 34 | -------------------------------------------------------------------------------- /src/InterpolatedSql.Dapper.Tests/Setup-MySQL.sql: -------------------------------------------------------------------------------- 1 | /*********** How to setup a MySQL for tests ***********/ 2 | 3 | -- 1) Download MariaDB for Windows https://dlm.mariadb.com/browse/mariadb_server/200/1374/winx64-packages/ 4 | 5 | CREATE DATABASE DapperQueryBuilderTests; 6 | 7 | USE DapperQueryBuilderTests; 8 | 9 | CREATE TABLE authors 10 | (author_id INT AUTO_INCREMENT PRIMARY KEY, 11 | name_last VARCHAR(50), 12 | name_first VARCHAR(50), 13 | country VARCHAR(50) ); 14 | 15 | INSERT INTO authors 16 | (name_last, name_first, country) 17 | VALUES('Kafka', 'Franz', 'Czech Republic'); 18 | 19 | INSERT INTO authors 20 | (name_last, name_first, country) 21 | VALUES('de Assis', 'Machado', 'Brazil'); 22 | 23 | 24 | CREATE TABLE books ( 25 | isbn CHAR(20) PRIMARY KEY, 26 | title VARCHAR(50), 27 | author_id INT, 28 | publisher_id INT, 29 | year_pub CHAR(4), 30 | description TEXT ); 31 | 32 | INSERT INTO books 33 | (title, author_id, isbn, year_pub) 34 | VALUES('The Castle', '1', '0805211063', '1998'); 35 | 36 | INSERT INTO books 37 | (title, author_id, isbn, year_pub) 38 | VALUES('The Trial', '1', '0805210407', '1995'), 39 | ('The Metamorphosis', '1', '0553213695', '1995'), 40 | ('America', '1', '0805210644', '1995'); 41 | -------------------------------------------------------------------------------- /src/InterpolatedSql.Dapper.Tests/TestHelper.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.Configuration; 2 | 3 | namespace InterpolatedSql.Dapper.Tests 4 | { 5 | public class TestHelper 6 | { 7 | public static IConfiguration Configuration { get; set; } 8 | static TestHelper() 9 | { 10 | Configuration = new ConfigurationBuilder() 11 | .AddJsonFile("TestSettings.json") 12 | .Build(); 13 | } 14 | public static string GetMSSQLConnectionString() => Configuration.GetConnectionString("MSSQLConnection"); 15 | public static string GetPostgreSQLConnectionString() => Configuration.GetConnectionString("PostgreSQLConnection"); 16 | public static string GetMySQLConnectionString() => Configuration.GetConnectionString("MySQLConnection"); 17 | 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/InterpolatedSql.Dapper.Tests/TestSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | // AdventureWorks 2019 - https://docs.microsoft.com/en-us/sql/samples/adventureworks-install-configure?view=sql-server-ver15) 3 | "ConnectionStrings": { 4 | "MSSQLConnection": "Data Source=(local);Initial Catalog=AdventureWorks2019;Integrated Security=True;", 5 | "PostgreSQLConnection": "User ID=postgres;Password=myPassword;Host=localhost;Port=5432;Database=dvdrental;", 6 | "MySQLConnection": "Server=localhost;Database=DapperQueryBuilderTests;Uid=root;Pwd=myPassword;" 7 | } 8 | } -------------------------------------------------------------------------------- /src/InterpolatedSql.Dapper.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 17 4 | VisualStudioVersion = 17.2.32505.173 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{88CCCDA2-06FC-42EF-8AAF-D92250C285AD}" 7 | ProjectSection(SolutionItems) = preProject 8 | .editorconfig = .editorconfig 9 | EndProjectSection 10 | EndProject 11 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "InterpolatedSql", "InterpolatedSql\InterpolatedSql.csproj", "{4C89CBC9-A8DD-442A-8273-3521653D3600}" 12 | EndProject 13 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "InterpolatedSql.Tests", "InterpolatedSql.Tests\InterpolatedSql.Tests.csproj", "{1D3449CF-A1D8-483E-91D3-0C4828E0BF0B}" 14 | EndProject 15 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "InterpolatedSql.Dapper", "InterpolatedSql.Dapper\InterpolatedSql.Dapper.csproj", "{3BB589A9-7775-44C2-A8AD-4034DB4B8FA4}" 16 | EndProject 17 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "InterpolatedSql.Dapper.Tests", "InterpolatedSql.Dapper.Tests\InterpolatedSql.Dapper.Tests.csproj", "{533C1C14-D489-463F-96DC-21634F7D744F}" 18 | EndProject 19 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "InterpolatedSql", "InterpolatedSql", "{0425816F-5240-49FB-A774-CD0E736A15E7}" 20 | EndProject 21 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "InterpolatedSql.Dapper", "InterpolatedSql.Dapper", "{07E20BA2-09BE-416C-AD43-92BBD89D9A70}" 22 | EndProject 23 | Global 24 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 25 | Debug|Any CPU = Debug|Any CPU 26 | Release|Any CPU = Release|Any CPU 27 | EndGlobalSection 28 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 29 | {4C89CBC9-A8DD-442A-8273-3521653D3600}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 30 | {4C89CBC9-A8DD-442A-8273-3521653D3600}.Debug|Any CPU.Build.0 = Debug|Any CPU 31 | {4C89CBC9-A8DD-442A-8273-3521653D3600}.Release|Any CPU.ActiveCfg = Release|Any CPU 32 | {4C89CBC9-A8DD-442A-8273-3521653D3600}.Release|Any CPU.Build.0 = Release|Any CPU 33 | {1D3449CF-A1D8-483E-91D3-0C4828E0BF0B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 34 | {1D3449CF-A1D8-483E-91D3-0C4828E0BF0B}.Debug|Any CPU.Build.0 = Debug|Any CPU 35 | {1D3449CF-A1D8-483E-91D3-0C4828E0BF0B}.Release|Any CPU.ActiveCfg = Release|Any CPU 36 | {1D3449CF-A1D8-483E-91D3-0C4828E0BF0B}.Release|Any CPU.Build.0 = Release|Any CPU 37 | {3BB589A9-7775-44C2-A8AD-4034DB4B8FA4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 38 | {3BB589A9-7775-44C2-A8AD-4034DB4B8FA4}.Debug|Any CPU.Build.0 = Debug|Any CPU 39 | {3BB589A9-7775-44C2-A8AD-4034DB4B8FA4}.Release|Any CPU.ActiveCfg = Release|Any CPU 40 | {3BB589A9-7775-44C2-A8AD-4034DB4B8FA4}.Release|Any CPU.Build.0 = Release|Any CPU 41 | {533C1C14-D489-463F-96DC-21634F7D744F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 42 | {533C1C14-D489-463F-96DC-21634F7D744F}.Debug|Any CPU.Build.0 = Debug|Any CPU 43 | {533C1C14-D489-463F-96DC-21634F7D744F}.Release|Any CPU.ActiveCfg = Release|Any CPU 44 | {533C1C14-D489-463F-96DC-21634F7D744F}.Release|Any CPU.Build.0 = Release|Any CPU 45 | EndGlobalSection 46 | GlobalSection(SolutionProperties) = preSolution 47 | HideSolutionNode = FALSE 48 | EndGlobalSection 49 | GlobalSection(NestedProjects) = preSolution 50 | {4C89CBC9-A8DD-442A-8273-3521653D3600} = {0425816F-5240-49FB-A774-CD0E736A15E7} 51 | {1D3449CF-A1D8-483E-91D3-0C4828E0BF0B} = {0425816F-5240-49FB-A774-CD0E736A15E7} 52 | {3BB589A9-7775-44C2-A8AD-4034DB4B8FA4} = {07E20BA2-09BE-416C-AD43-92BBD89D9A70} 53 | {533C1C14-D489-463F-96DC-21634F7D744F} = {07E20BA2-09BE-416C-AD43-92BBD89D9A70} 54 | EndGlobalSection 55 | GlobalSection(ExtensibilityGlobals) = postSolution 56 | SolutionGuid = {AB589EF0-8B1A-4791-A8B5-F3ED178AF34F} 57 | EndGlobalSection 58 | EndGlobal 59 | -------------------------------------------------------------------------------- /src/InterpolatedSql.Dapper/GlobalSuppressions.cs: -------------------------------------------------------------------------------- 1 | // This file is used by Code Analysis to maintain SuppressMessage 2 | // attributes that are applied to this project. 3 | // Project-level suppressions either have no target or are given 4 | // a specific target and scoped to a namespace, type, member, etc. 5 | 6 | using System.Diagnostics.CodeAnalysis; 7 | 8 | [assembly: SuppressMessage("Style", "IDE0008:Use explicit type", Justification = "var avoids noisy code")] 9 | -------------------------------------------------------------------------------- /src/InterpolatedSql.Dapper/IDapperSqlCommand.cs: -------------------------------------------------------------------------------- 1 | namespace InterpolatedSql.Dapper 2 | { 3 | /// 4 | /// Dapper Sql Command that can be executed 5 | /// 6 | public interface IDapperSqlCommand : ISqlCommand 7 | { 8 | /// Sql Parameters converted into Dapper format 9 | ParametersDictionary DapperParameters { get; } 10 | } 11 | 12 | /// 13 | /// Dapper Sql Command that can be executed 14 | /// 15 | public interface IDapperSqlCommand : ISqlCommand, IDapperSqlCommand 16 | where T : IDapperSqlCommand, ISqlCommand 17 | { 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/InterpolatedSql.Dapper/IDbConnectionExtensions.cs: -------------------------------------------------------------------------------- 1 | using InterpolatedSql.SqlBuilders; 2 | using System; 3 | using System.Data; 4 | using SqlBuilder = InterpolatedSql.Dapper.SqlBuilders.SqlBuilder; 5 | using QueryBuilder = InterpolatedSql.Dapper.SqlBuilders.QueryBuilder; 6 | using InterpolatedSql.Dapper.SqlBuilders; 7 | 8 | namespace InterpolatedSql.Dapper 9 | { 10 | /// 11 | /// Extends IDbConnection to easily build QueryBuilder or SqlBuilder 12 | /// 13 | public static partial class IDbConnectionExtensions 14 | { 15 | public static SqlBuilderFactory SqlBuilderFactory { get; set; } = SqlBuilderFactory.Default; 16 | 17 | #region SqlBuilder 18 | /// 19 | /// Creates a new IInterpolatedSqlBuilder of type B over current connection 20 | /// 21 | public static B SqlBuilder(this IDbConnection cnn) 22 | where B : IDapperSqlBuilder 23 | { 24 | return SqlBuilderFactory.Create(cnn); 25 | } 26 | 27 | #if NET6_0_OR_GREATER 28 | /// 29 | /// Creates a new IInterpolatedSqlBuilder of type B over current connection 30 | /// 31 | /// SQL command 32 | public static B SqlBuilder(this IDbConnection cnn, ref InterpolatedSqlHandler command) 33 | where B : IDapperSqlBuilder 34 | { 35 | if (command.InterpolatedSqlBuilder.Options.AutoAdjustMultilineString) 36 | command.AdjustMultilineString(); 37 | return SqlBuilderFactory.Create(cnn, command.InterpolatedSqlBuilder.AsFormattableString()); 38 | } 39 | 40 | /// 41 | /// Creates a new SqlBuilder over current connection 42 | /// 43 | /// SQL command 44 | public static SqlBuilder SqlBuilder(this IDbConnection cnn, ref InterpolatedSqlHandler command) 45 | { 46 | if (command.InterpolatedSqlBuilder.Options.AutoAdjustMultilineString) 47 | command.AdjustMultilineString(); 48 | return new SqlBuilder(cnn, command.InterpolatedSqlBuilder.AsFormattableString()); 49 | } 50 | 51 | /// 52 | /// Creates a new IInterpolatedSqlBuilder of type B over current connection 53 | /// 54 | /// SQL command 55 | public static B SqlBuilder(this IDbConnection cnn, InterpolatedSqlBuilderOptions options, [System.Runtime.CompilerServices.InterpolatedStringHandlerArgument("options")] ref InterpolatedSqlHandler command) 56 | where B : IDapperSqlBuilder 57 | { 58 | if (command.InterpolatedSqlBuilder.Options.AutoAdjustMultilineString) 59 | command.AdjustMultilineString(); 60 | return SqlBuilderFactory.Create(cnn, command.InterpolatedSqlBuilder.AsFormattableString(), options); 61 | } 62 | 63 | /// 64 | /// Creates a new SqlBuilder over current connection 65 | /// 66 | /// SQL command 67 | public static SqlBuilder SqlBuilder(this IDbConnection cnn, InterpolatedSqlBuilderOptions options, [System.Runtime.CompilerServices.InterpolatedStringHandlerArgument("options")] ref InterpolatedSqlHandler command) 68 | { 69 | if (command.InterpolatedSqlBuilder.Options.AutoAdjustMultilineString) 70 | command.AdjustMultilineString(); 71 | return new SqlBuilder(cnn, command.InterpolatedSqlBuilder.AsFormattableString(), options); 72 | } 73 | 74 | #else 75 | /// 76 | /// Creates a new IInterpolatedSqlBuilder of type B over current connection 77 | /// 78 | /// SQL command 79 | public static B SqlBuilder(this IDbConnection cnn, FormattableString command) 80 | where B : IDapperSqlBuilder 81 | { 82 | return SqlBuilderFactory.Create(cnn, command); 83 | } 84 | 85 | /// 86 | /// Creates a new SqlBuilder over current connection 87 | /// 88 | /// SQL command 89 | public static SqlBuilder SqlBuilder(this IDbConnection cnn, FormattableString command) 90 | { 91 | return new SqlBuilder(cnn, command); 92 | } 93 | 94 | /// 95 | /// Creates a new IInterpolatedSqlBuilder of type B over current connection 96 | /// 97 | /// SQL command 98 | public static B SqlBuilder(this IDbConnection cnn, InterpolatedSqlBuilderOptions options, FormattableString command) 99 | where B : IDapperSqlBuilder 100 | { 101 | return SqlBuilderFactory.Create(cnn, command, options); 102 | } 103 | 104 | /// 105 | /// Creates a new SqlBuilder over current connection 106 | /// 107 | /// SQL command 108 | public static SqlBuilder SqlBuilder(this IDbConnection cnn, InterpolatedSqlBuilderOptions options, FormattableString command) 109 | { 110 | return new SqlBuilder(cnn, command, options); 111 | } 112 | #endif 113 | 114 | /// 115 | /// Creates a new empty SqlBuilder over current connection 116 | /// 117 | public static SqlBuilder SqlBuilder(this IDbConnection cnn, InterpolatedSqlBuilderOptions? options = null) 118 | { 119 | return new SqlBuilder(cnn, options); 120 | } 121 | #endregion 122 | 123 | #region QueryBuilder 124 | /// 125 | /// Creates a new QueryBuilder over current connection 126 | /// 127 | /// You can use "{where}" or "/**where**/" in your query, and it will be replaced by "WHERE + filters" (if any filter is defined).
128 | /// You can use "{filters}" or "/**filters**/" in your query, and it will be replaced by "filters" (without where) (if any filter is defined). 129 | /// 130 | public static QueryBuilder QueryBuilder(this IDbConnection cnn, FormattableString query) 131 | { 132 | return new QueryBuilder(cnn, query); 133 | } 134 | 135 | /// 136 | /// Creates a new empty QueryBuilder over current connection 137 | /// 138 | public static QueryBuilder QueryBuilder(this IDbConnection cnn) 139 | { 140 | return new QueryBuilder(cnn); 141 | } 142 | #endregion 143 | } 144 | } 145 | -------------------------------------------------------------------------------- /src/InterpolatedSql.Dapper/ImmutableDapperCommand.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Data; 3 | 4 | namespace InterpolatedSql.Dapper 5 | { 6 | /// 7 | /// Immutable implementation of . 8 | /// 9 | 10 | public class ImmutableDapperCommand : ImmutableInterpolatedSql, IDapperSqlCommand 11 | { 12 | /// 13 | /// Database connection associated to the command 14 | /// 15 | public IDbConnection DbConnection { get; set; } 16 | 17 | /// Sql Parameters converted into Dapper format 18 | public ParametersDictionary DapperParameters { get; } 19 | 20 | /// 21 | public ImmutableDapperCommand(IDbConnection connection, 22 | string sql, string format, IReadOnlyList sqlParameters, IReadOnlyList explicitParameters) : base(sql, format, sqlParameters, explicitParameters) 23 | { 24 | DbConnection = connection; 25 | DapperParameters = ParametersDictionary.LoadFrom(this); 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/InterpolatedSql.Dapper/InterpolatedSql.Dapper.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | netstandard2.0;net462;net472;net5.0;net6.0;net7.0 5 | Rick Drizin 6 | MIT 7 | https://github.com/Drizin/InterpolatedSql/ 8 | Dapper Query Builder using Fluent API and String Interpolation 9 | Rick Drizin 10 | Rick Drizin 11 | 2.4.0 12 | false 13 | InterpolatedSql.Dapper 14 | InterpolatedSql.Dapper 15 | InterpolatedSql.Dapper.xml 16 | interpolated sql;dapper;query-builder;query builder;dapperquerybuilder;dapper-query-builder;dapper-interpolation;dapper-interpolated-string 17 | true 18 | true 19 | 20 | NuGetReadMe.md 21 | InterpolatedSql.Dapper 22 | enable 23 | 9.0 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | all 32 | runtime; build; native; contentfiles; analyzers; buildtransitive 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | -------------------------------------------------------------------------------- /src/InterpolatedSql.Dapper/InterpolatedSql.Dapper.nuspec: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | InterpolatedSql.Dapper 5 | InterpolatedSql.Dapper 6 | Rick Drizin 7 | Rick Drizin 8 | MIT 9 | https://github.com/Drizin/InterpolatedSql 10 | false 11 | InterpolatedSql.Dapper: Dapper Query Builder using String Interpolation and Fluent API 12 | Copyright Rick Drizin 2020 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /src/InterpolatedSql.Dapper/InterpolatedSqlDapperOptions.cs: -------------------------------------------------------------------------------- 1 | using InterpolatedSql; 2 | 3 | namespace InterpolatedSql.Dapper 4 | { 5 | /// 6 | /// Global Options 7 | /// 8 | public class InterpolatedSqlDapperOptions 9 | { 10 | /// 11 | /// Responsible for parsing SqlParameters (see ) 12 | /// into a list of SqlParameterInfo that 13 | /// 14 | public static SqlParameterMapper InterpolatedSqlParameterParser = new SqlParameterMapper(); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/InterpolatedSql.Dapper/SqlBuilderFactory.cs: -------------------------------------------------------------------------------- 1 | using InterpolatedSql.Dapper.SqlBuilders; 2 | using InterpolatedSql.SqlBuilders; 3 | using System; 4 | using System.Data; 5 | using SqlBuilder = InterpolatedSql.Dapper.SqlBuilders.SqlBuilder; 6 | 7 | namespace InterpolatedSql.Dapper 8 | { 9 | /// 10 | /// Creates 11 | /// 12 | public class SqlBuilderFactory 13 | { 14 | /// 15 | /// Creates a new IInterpolatedSqlBuilderBase of type B 16 | /// 17 | public virtual B Create(IDbConnection connection) 18 | where B : IDapperSqlBuilder 19 | { 20 | var ctor = typeof(B).GetConstructor(new Type[] { typeof(IDbConnection) })!; 21 | B builder = (B)ctor.Invoke(new object[] { connection }); 22 | return builder; 23 | } 24 | 25 | /// 26 | /// Creates a new IInterpolatedSqlBuilderBase of type B 27 | /// 28 | public virtual B Create(IDbConnection connection, InterpolatedSqlBuilderOptions options) 29 | where B : IDapperSqlBuilder 30 | { 31 | var ctor = typeof(B).GetConstructor(new Type[] { typeof(IDbConnection), typeof(InterpolatedSqlBuilderOptions) })!; 32 | B builder = (B)ctor.Invoke(new object[] { connection, options }); 33 | return builder; 34 | } 35 | 36 | /// 37 | /// Creates the default IInterpolatedSqlBuilder, which by default is SqlBuilder 38 | /// 39 | public virtual SqlBuilder Create(IDbConnection connection, InterpolatedSqlBuilderOptions? options = null) 40 | { 41 | SqlBuilder builder = new SqlBuilder(connection, options); 42 | return builder; 43 | } 44 | 45 | /// 46 | /// Creates a new IInterpolatedSqlBuilderBase of type B 47 | /// 48 | public virtual B Create(IDbConnection connection, FormattableString command) 49 | where B : IDapperSqlBuilder 50 | { 51 | var ctor = typeof(B).GetConstructor(new Type[] { typeof(IDbConnection), typeof(FormattableString) }) 52 | ?? throw new MissingMemberException(typeof(B).FullName, ".ctor(IDbConnection, FormattableString)"); 53 | B builder = (B)ctor.Invoke(new object[] { connection, command }); 54 | return builder; 55 | } 56 | 57 | /// 58 | /// Creates a new IInterpolatedSqlBuilderBase of type B 59 | /// 60 | public virtual B Create(IDbConnection connection, FormattableString command, InterpolatedSqlBuilderOptions? options = null) 61 | where B : IDapperSqlBuilder 62 | { 63 | var ctor = typeof(B).GetConstructor(new Type[] { typeof(IDbConnection), typeof(FormattableString), typeof(InterpolatedSqlBuilderOptions) }) 64 | ?? throw new MissingMemberException(typeof(B).FullName, ".ctor(IDbConnection, FormattableString, InterpolatedSqlBuilderOptions)"); 65 | B builder = (B)ctor.Invoke(new object[] { connection, command, options! }); 66 | return builder; 67 | } 68 | 69 | #if NET6_0_OR_GREATER 70 | /// 71 | /// Creates a new IInterpolatedSqlBuilderBase of type B 72 | /// 73 | public virtual B Create(IDbConnection connection, int literalLength, int formattedCount) 74 | where B : IDapperSqlBuilder 75 | { 76 | var ctor = typeof(B).GetConstructor(new Type[] { typeof(IDbConnection), typeof(int), typeof(int) }) 77 | ?? throw new MissingMemberException(typeof(B).FullName, ".ctor(IDbConnection, int, int)"); 78 | B builder = (B)ctor.Invoke(new object[] { connection, literalLength, formattedCount }); 79 | return builder; 80 | } 81 | 82 | /// 83 | /// Creates a new IInterpolatedSqlBuilder of type B 84 | /// 85 | public virtual B Create(IDbConnection connection, int literalLength, int formattedCount, InterpolatedSqlBuilderOptions? options = null) 86 | where B : IDapperSqlBuilder 87 | { 88 | var ctor = typeof(B).GetConstructor(new Type[] { typeof(IDbConnection), typeof(int), typeof(int), typeof(InterpolatedSqlBuilderOptions) }) 89 | ?? throw new MissingMemberException(typeof(B).FullName, ".ctor(IDbConnection, int, int, InterpolatedSqlBuilderOptions)"); 90 | B builder = (B)ctor.Invoke(new object?[] { connection, literalLength, formattedCount, options }); 91 | return builder; 92 | } 93 | 94 | /// 95 | /// Creates new SqlBuilder 96 | /// 97 | public virtual SqlBuilder Create(IDbConnection connection, int literalLength, int formattedCount, InterpolatedSqlBuilderOptions? options = null) 98 | { 99 | SqlBuilder builder = new SqlBuilder(connection, literalLength, formattedCount, options); 100 | return builder; 101 | } 102 | #endif 103 | 104 | 105 | /// 106 | /// Default Factory 107 | /// 108 | public static SqlBuilderFactory Default = new SqlBuilderFactory(); 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /src/InterpolatedSql.Dapper/SqlBuilders/FluentQueryBuilder/FluentQueryBuilder.cs: -------------------------------------------------------------------------------- 1 | using InterpolatedSql.SqlBuilders; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Data; 5 | using System.Text; 6 | 7 | namespace InterpolatedSql.Dapper.SqlBuilders.FluentQueryBuilder 8 | { 9 | /// 10 | /// Exactly like 11 | /// (an injection-safe dynamic SQL builder with a Fluent API that helps to build the query step by step) 12 | /// but also wraps an underlying IDbConnection, and there are extensions to invoke Dapper methods 13 | /// 14 | public class FluentQueryBuilder : global::InterpolatedSql.SqlBuilders.FluentQueryBuilder.FluentQueryBuilder, 15 | IFluentQueryBuilder, 16 | IBuildable, 17 | IDapperSqlBuilder 18 | { 19 | #region ctors 20 | /// 21 | public FluentQueryBuilder( 22 | Func combinedBuilderFactory1, 23 | Func?, SqlBuilder> combinedBuilderFactory2, 24 | IDbConnection connection) : base(combinedBuilderFactory1, combinedBuilderFactory2, connection) 25 | { 26 | Options.CalculateAutoParameterName = (parameter, pos) => InterpolatedSqlDapperOptions.InterpolatedSqlParameterParser.CalculateAutoParameterName(parameter, pos, base.Options); 27 | } 28 | #endregion 29 | 30 | #region Overrides 31 | /// 32 | /// Associated DbConnection 33 | /// 34 | public new IDbConnection DbConnection 35 | { 36 | get => base.DbConnection!; 37 | set => base.DbConnection = value; 38 | } 39 | #endregion 40 | } 41 | 42 | } 43 | -------------------------------------------------------------------------------- /src/InterpolatedSql.Dapper/SqlBuilders/FluentQueryBuilder/IDbConnectionExtensions.cs: -------------------------------------------------------------------------------- 1 | using System.Data; 2 | using InterpolatedSql.SqlBuilders.FluentQueryBuilder; 3 | 4 | namespace InterpolatedSql.Dapper.SqlBuilders.FluentQueryBuilder 5 | { 6 | /// 7 | /// Extends IDbConnection to easily build FluentQueryBuilder 8 | /// 9 | public static partial class IDbConnectionExtensions 10 | { 11 | /// 12 | /// Creates a new empty FluentQueryBuilder over current connection 13 | /// 14 | /// 15 | public static IEmptyQueryBuilder< 16 | InterpolatedSql.Dapper.SqlBuilders.FluentQueryBuilder.IFluentQueryBuilder, 17 | SqlBuilder, 18 | IDapperSqlCommand 19 | > FluentQueryBuilder(this IDbConnection cnn) 20 | { 21 | return new FluentQueryBuilder((options) => new SqlBuilder(cnn, options), (opts, format, arguments) => new SqlBuilder(cnn, opts, format, arguments), cnn); 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/InterpolatedSql.Dapper/SqlBuilders/FluentQueryBuilder/IFluentQueryBuilder.cs: -------------------------------------------------------------------------------- 1 | using System.Data; 2 | 3 | namespace InterpolatedSql.Dapper.SqlBuilders.FluentQueryBuilder 4 | { 5 | public interface IFluentQueryBuilder 6 | : InterpolatedSql.SqlBuilders.FluentQueryBuilder.IFluentQueryBuilder, 7 | IQueryBuilder, 8 | InterpolatedSql.SqlBuilders.ISqlBuilder, 9 | IBuildable 10 | { 11 | //ParametersDictionary DapperParameters { get; } 12 | IDbConnection DbConnection { get; set; } 13 | 14 | IDapperSqlCommand Build(); 15 | } 16 | } -------------------------------------------------------------------------------- /src/InterpolatedSql.Dapper/SqlBuilders/IDapperSqlBuilder.cs: -------------------------------------------------------------------------------- 1 | namespace InterpolatedSql.Dapper.SqlBuilders 2 | { 3 | /// 4 | /// Any Builder that creates a 5 | /// 6 | public interface IDapperSqlBuilder : InterpolatedSql.SqlBuilders.IInterpolatedSqlBuilderBase 7 | { 8 | /// 9 | /// Builds the SQL statement 10 | /// 11 | IDapperSqlCommand Build(); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/InterpolatedSql.Dapper/SqlBuilders/InsertUpdateBuilder/IDbConnectionExtensions.cs: -------------------------------------------------------------------------------- 1 | using System.Data; 2 | 3 | namespace InterpolatedSql.Dapper.SqlBuilders.InsertUpdateBuilder 4 | { 5 | /// 6 | /// Extends IDbConnection to easily build InsertUpdateBuilder 7 | /// 8 | public static partial class IDbConnectionExtensions 9 | { 10 | /// 11 | /// Creates a new empty InsertUpdateBuilder over current connection 12 | /// 13 | public static InsertUpdateBuilder InsertUpdateBuilder(this IDbConnection cnn, string tableName) 14 | { 15 | return new InsertUpdateBuilder(tableName, cnn); 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/InterpolatedSql.Dapper/SqlBuilders/InsertUpdateBuilder/IInsertUpdateBuilder.cs: -------------------------------------------------------------------------------- 1 | namespace InterpolatedSql.Dapper.SqlBuilders.InsertUpdateBuilder 2 | { 3 | public interface IInsertUpdateBuilder : InterpolatedSql.SqlBuilders.InsertUpdateBuilder.IInsertUpdateBuilder 4 | where U : IInsertUpdateBuilder, IBuildable 5 | where RB : IDapperSqlBuilder, IBuildable 6 | where R : class, IInterpolatedSql, IDapperSqlCommand 7 | { 8 | } 9 | public interface IInsertUpdateBuilder : IInsertUpdateBuilder 10 | { 11 | 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/InterpolatedSql.Dapper/SqlBuilders/InsertUpdateBuilder/InsertUpdateBuilder.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Data; 3 | 4 | namespace InterpolatedSql.Dapper.SqlBuilders.InsertUpdateBuilder 5 | { 6 | /// 7 | public class InsertUpdateBuilder : InsertUpdateBuilder, IInsertUpdateBuilder, IDapperSqlBuilder 8 | { 9 | #region ctors 10 | /// 11 | public InsertUpdateBuilder(string tableName, IDbConnection connection) : base(tableName, opts => new SqlBuilder(connection, opts), connection) 12 | { 13 | } 14 | #endregion 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/InterpolatedSql.Dapper/SqlBuilders/InsertUpdateBuilder/InsertUpdateBuilder{U,RB,R}.cs: -------------------------------------------------------------------------------- 1 | using InterpolatedSql.SqlBuilders; 2 | using System; 3 | using System.Data; 4 | 5 | namespace InterpolatedSql.Dapper.SqlBuilders.InsertUpdateBuilder 6 | { 7 | /// 8 | /// Exactly like but also wraps a (required) underlying IDbConnection, 9 | /// has a "Filters" property which can track a list of filters which are later combined (by default with AND) and will replace the keyword /**where**/, 10 | /// provides facades (as extension-methods) to invoke Dapper extensions (see ), 11 | /// and maps and 12 | /// into Dapper type. 13 | /// 14 | public abstract class InsertUpdateBuilder : global::InterpolatedSql.SqlBuilders.InsertUpdateBuilder.InsertUpdateBuilder, IInsertUpdateBuilder 15 | where U : IInsertUpdateBuilder, ISqlBuilder, IBuildable 16 | where RB : IDapperSqlBuilder, IBuildable 17 | where R : class, IInterpolatedSql, IDapperSqlCommand 18 | { 19 | #region ctors 20 | /// 21 | protected InsertUpdateBuilder(string tableName, Func combinedBuilderFactory, IDbConnection connection) : base(tableName, combinedBuilderFactory) 22 | { 23 | DbConnection = connection; 24 | Options.CalculateAutoParameterName = (parameter, pos) => InterpolatedSqlDapperOptions.InterpolatedSqlParameterParser.CalculateAutoParameterName(parameter, pos, base.Options); 25 | } 26 | #endregion 27 | 28 | #region Overrides 29 | /// 30 | /// Associated DbConnection 31 | /// 32 | public new IDbConnection DbConnection 33 | { 34 | get => base.DbConnection!; 35 | set => base.DbConnection = value; 36 | } 37 | #endregion 38 | 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/InterpolatedSql.Dapper/SqlBuilders/QueryBuilder/IQueryBuilder.cs: -------------------------------------------------------------------------------- 1 | namespace InterpolatedSql.Dapper.SqlBuilders 2 | { 3 | public interface IQueryBuilder : InterpolatedSql.SqlBuilders.IQueryBuilder 4 | where U : IQueryBuilder, IBuildable 5 | where RB : IDapperSqlBuilder, IBuildable 6 | where R : class, IInterpolatedSql, IDapperSqlCommand 7 | { 8 | } 9 | public interface IQueryBuilder : IQueryBuilder 10 | { 11 | 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/InterpolatedSql.Dapper/SqlBuilders/QueryBuilder/QueryBuilder.cs: -------------------------------------------------------------------------------- 1 | using InterpolatedSql.SqlBuilders; 2 | using System; 3 | using System.Data; 4 | 5 | namespace InterpolatedSql.Dapper.SqlBuilders 6 | { 7 | /// 8 | public class QueryBuilder : QueryBuilder, IQueryBuilder, IDapperSqlBuilder 9 | { 10 | #region ctors 11 | /// 12 | public QueryBuilder(IDbConnection connection) : base(opts => new SqlBuilder(connection, opts), (opts, format, arguments) => new SqlBuilder(connection, opts, format, arguments), connection) 13 | { 14 | } 15 | 16 | /// 17 | public QueryBuilder(IDbConnection connection, FormattableString query) : base(opts => new SqlBuilder(connection, opts), (opts, format, arguments) => new SqlBuilder(connection, opts, format, arguments), connection, query) 18 | { 19 | } 20 | 21 | /// 22 | public QueryBuilder(IDbConnection connection, FormattableString query, InterpolatedSqlBuilderOptions? options = null) : base(_ => new SqlBuilder(connection, options), (_, format, arguments) => new SqlBuilder(connection, options, format, arguments), connection, query) 23 | { 24 | } 25 | #endregion 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/InterpolatedSql.Dapper/SqlBuilders/QueryBuilder/QueryBuilder{U,RB,R}.cs: -------------------------------------------------------------------------------- 1 | using InterpolatedSql.SqlBuilders; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Data; 5 | using System.Text; 6 | 7 | namespace InterpolatedSql.Dapper.SqlBuilders 8 | { 9 | /// 10 | /// Exactly like but also wraps a (required) underlying IDbConnection, 11 | /// has a "Filters" property which can track a list of filters which are later combined (by default with AND) and will replace the keyword /**where**/, 12 | /// provides facades (as extension-methods) to invoke Dapper extensions (see ), 13 | /// and maps and 14 | /// into Dapper type. 15 | /// 16 | public abstract class QueryBuilder : global::InterpolatedSql.SqlBuilders.QueryBuilder, IQueryBuilder 17 | where U : IQueryBuilder, ISqlBuilder, IBuildable 18 | where RB : IDapperSqlBuilder, IBuildable 19 | where R : class, IInterpolatedSql, IDapperSqlCommand 20 | { 21 | #region ctors 22 | /// 23 | protected QueryBuilder( 24 | Func combinedBuilderFactory1, 25 | Func?, RB> combinedBuilderFactory2, 26 | IDbConnection connection 27 | ) : base(combinedBuilderFactory1, combinedBuilderFactory2) 28 | { 29 | DbConnection = connection; 30 | Options.CalculateAutoParameterName = (parameter, pos) => InterpolatedSqlDapperOptions.InterpolatedSqlParameterParser.CalculateAutoParameterName(parameter, pos, base.Options); 31 | } 32 | 33 | /// 34 | protected QueryBuilder( 35 | Func combinedBuilderFactory1, 36 | Func?, RB> combinedBuilderFactory2, 37 | IDbConnection connection, 38 | FormattableString query 39 | ) : base(combinedBuilderFactory1, combinedBuilderFactory2, query) 40 | { 41 | DbConnection = connection; 42 | Options.CalculateAutoParameterName = (parameter, pos) => InterpolatedSqlDapperOptions.InterpolatedSqlParameterParser.CalculateAutoParameterName(parameter, pos, base.Options); 43 | } 44 | #endregion 45 | 46 | #region Overrides 47 | /// 48 | /// Associated DbConnection 49 | /// 50 | public new IDbConnection DbConnection 51 | { 52 | get => base.DbConnection!; 53 | set => base.DbConnection = value; 54 | } 55 | #endregion 56 | 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/InterpolatedSql.Dapper/SqlBuilders/SqlBuilder/ISqlBuilder.cs: -------------------------------------------------------------------------------- 1 | namespace InterpolatedSql.Dapper.SqlBuilders 2 | { 3 | public interface ISqlBuilder : InterpolatedSql.SqlBuilders.ISqlBuilder, IDapperSqlBuilder 4 | { 5 | 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /src/InterpolatedSql.Dapper/SqlBuilders/SqlBuilder/SqlBuilder.cs: -------------------------------------------------------------------------------- 1 | using InterpolatedSql.SqlBuilders; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Data; 5 | using System.Text; 6 | 7 | namespace InterpolatedSql.Dapper.SqlBuilders 8 | { 9 | /// 10 | public class SqlBuilder : SqlBuilder, ISqlBuilder, IDapperSqlBuilder 11 | { 12 | #region ctors 13 | /// 14 | protected internal SqlBuilder(IDbConnection connection, InterpolatedSqlBuilderOptions? options, StringBuilder? format, List? arguments) : base(connection, options, format, arguments) 15 | { 16 | DbConnection = connection; 17 | } 18 | 19 | /// 20 | public SqlBuilder(IDbConnection connection, InterpolatedSqlBuilderOptions? options = null) : base(connection, options) 21 | { 22 | DbConnection = connection; 23 | } 24 | 25 | 26 | /// 27 | public SqlBuilder(IDbConnection connection, FormattableString value, InterpolatedSqlBuilderOptions? options = null) : base(connection, value, options) 28 | { 29 | DbConnection = connection; 30 | } 31 | 32 | #if NET6_0_OR_GREATER 33 | /// 34 | public SqlBuilder(IDbConnection connection, int literalLength, int formattedCount, InterpolatedSqlBuilderOptions? options = null) : base(connection, literalLength, formattedCount, options) 35 | { 36 | DbConnection = connection; 37 | } 38 | #endif 39 | #endregion 40 | 41 | #region Overrides 42 | /// 43 | public override IDapperSqlCommand Build() 44 | { 45 | return this.ToDapperSqlCommand(); 46 | } 47 | 48 | /// 49 | /// Like 50 | /// 51 | /// 52 | public IDapperSqlCommand ToDapperSqlCommand() 53 | { 54 | string format = _format.ToString(); 55 | return new ImmutableDapperCommand(this.DbConnection, BuildSql(format, _sqlParameters), format, _sqlParameters, _explicitParameters); 56 | } 57 | 58 | 59 | #endregion 60 | 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /src/InterpolatedSql.Dapper/SqlBuilders/SqlBuilder/SqlBuilder{U,RB,R}.cs: -------------------------------------------------------------------------------- 1 | using InterpolatedSql.SqlBuilders; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Data; 5 | using System.Text; 6 | 7 | namespace InterpolatedSql.Dapper.SqlBuilders 8 | { 9 | /// 10 | /// Exactly like but also wraps a (required) underlying IDbConnection, 11 | /// has a "Filters" property which can track a list of filters which are later combined (by default with AND) and will replace the keyword /**where**/, 12 | /// provides facades (as extension-methods) to invoke Dapper extensions (see ), 13 | /// and maps and 14 | /// into Dapper type. 15 | /// 16 | public abstract class SqlBuilder : global::InterpolatedSql.SqlBuilders.SqlBuilder 17 | where U : ISqlBuilder 18 | where R : class, IInterpolatedSql, IDapperSqlCommand 19 | { 20 | 21 | #region ctors 22 | /// 23 | protected SqlBuilder(IDbConnection connection, InterpolatedSqlBuilderOptions? options, StringBuilder? format, List? arguments) : base(options, format, arguments) 24 | { 25 | DbConnection = connection; 26 | } 27 | 28 | /// 29 | public SqlBuilder(IDbConnection connection, InterpolatedSqlBuilderOptions? options = null) : base(options) 30 | { 31 | DbConnection = connection; 32 | } 33 | 34 | 35 | /// 36 | public SqlBuilder(IDbConnection connection, FormattableString value, InterpolatedSqlBuilderOptions? options = null) : base(value, options) 37 | { 38 | DbConnection = connection; 39 | } 40 | 41 | #if NET6_0_OR_GREATER 42 | /// 43 | public SqlBuilder(IDbConnection connection, int literalLength, int formattedCount, InterpolatedSqlBuilderOptions? options = null) : base(literalLength, formattedCount, options) 44 | { 45 | DbConnection = connection; 46 | } 47 | #endif 48 | #endregion 49 | 50 | #region Overrides 51 | /// 52 | /// Associated DbConnection 53 | /// 54 | public new IDbConnection DbConnection 55 | { 56 | get => base.DbConnection!; 57 | set => base.DbConnection = value; 58 | } 59 | #endregion 60 | 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /src/InterpolatedSql.Dapper/SqlParameters/ParametersDictionary.cs: -------------------------------------------------------------------------------- 1 | using Dapper; 2 | using InterpolatedSql.Dapper.SqlBuilders; 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Data; 6 | using System.Linq; 7 | 8 | namespace InterpolatedSql.Dapper 9 | { 10 | /// 11 | /// List of SQL Parameters that are passed to Dapper methods 12 | /// 13 | public class ParametersDictionary : Dictionary, SqlMapper.IDynamicParameters, SqlMapper.IParameterCallbacks 14 | { 15 | #region Members 16 | private DynamicParameters? _dynamicParameters = null; 17 | #endregion 18 | 19 | #region ctors 20 | /// 21 | public ParametersDictionary() : base(StringComparer.OrdinalIgnoreCase) 22 | { 23 | } 24 | 25 | /// Creates a built from Implicit Parameters (loaded from ) 26 | /// and Explicit Parameters (loaded from ) 27 | public static ParametersDictionary LoadFrom(IInterpolatedSql sql, Func? calculateAutoParameterName = null) 28 | { 29 | sql = (sql as ISqlEnricher)?.GetEnrichedSql() ?? sql; 30 | var parameters = new ParametersDictionary(); 31 | //HashSet parmNames = new HashSet(StringComparer.OrdinalIgnoreCase); //TODO: check for name clashes, rename as required 32 | 33 | calculateAutoParameterName ??= ((sql as IDapperSqlBuilder)?.Options?.CalculateAutoParameterName ?? InterpolatedSql.SqlBuilders.InterpolatedSqlBuilderOptions.DefaultOptions.CalculateAutoParameterName); 34 | 35 | for (int i = 0; i < sql.ExplicitParameters.Count; i++) 36 | { 37 | parameters.Add(sql.ExplicitParameters[i]); 38 | } 39 | 40 | for (int i = 0; i < sql.SqlParameters.Count; i++) 41 | { 42 | // ParseArgument usually just returns parmValue (dbType and direction are only extracted if explicitly defined using format specifiers) 43 | // Dapper will pick the right DbType even if you don't explicitly specify the DbType - and for most cases size don't need to be specified 44 | 45 | var parmName = calculateAutoParameterName(sql.SqlParameters[i], i); 46 | var parmValue = sql.SqlParameters[i].Argument; 47 | var format = sql.SqlParameters[i].Format; 48 | 49 | if (parmValue is SqlParameterInfo parm) 50 | { 51 | parm.Name = parmName; 52 | parameters[parmName] = parm; 53 | } 54 | else 55 | parameters.Add(new SqlParameterInfo(parmName, parmValue)); 56 | } 57 | return parameters; 58 | } 59 | #endregion 60 | 61 | #region Methods 62 | /// 63 | /// Convert the current parameters into Dapper Parameters, since Dapper will automagically set DbTypes, Sizes, etc, and map to target database 64 | /// 65 | /// 66 | public virtual DynamicParameters DynamicParameters 67 | { 68 | // Most Dapper extensions work fine with a Dictionary{string, object}, 69 | // but some methods like QueryMultiple (when used with Stored Procedures) may require DynamicParameters 70 | // TODO: should we just use DynamicParameters in all Dapper calls? 71 | get 72 | { 73 | if (_dynamicParameters == null) 74 | { 75 | _dynamicParameters = new DynamicParameters(); 76 | foreach (var parameter in this.Values) 77 | SqlParameterMapper.Default.AddToDynamicParameters(_dynamicParameters, parameter); 78 | } 79 | return _dynamicParameters; 80 | } 81 | } 82 | 83 | /// 84 | /// Add a explicit parameter to this dictionary 85 | /// 86 | public void Add(SqlParameterInfo parameter) 87 | { 88 | this[parameter.Name!] = parameter; 89 | } 90 | 91 | 92 | /// 93 | /// Get parameter value 94 | /// 95 | public T? Get(string key) => (T?)this[key].Value; 96 | 97 | /// 98 | /// Parameter Names 99 | /// 100 | public HashSet ParameterNames => new HashSet(this.Keys); 101 | 102 | void SqlMapper.IDynamicParameters.AddParameters(IDbCommand command, SqlMapper.Identity identity) 103 | { 104 | // IDynamicParameters is explicitly implemented (not public) - and it will add our dynamic paramaters to IDbCommand 105 | ((SqlMapper.IDynamicParameters)DynamicParameters).AddParameters(command, identity); 106 | } 107 | 108 | /// 109 | /// After Dapper command is executed, we should get output/return parameters back 110 | /// 111 | void SqlMapper.IParameterCallbacks.OnCompleted() 112 | { 113 | var dapperParameters = DynamicParameters; 114 | 115 | // Update output and return parameters back 116 | foreach (var oparm in this.Values.Where(p => p.ParameterDirection != ParameterDirection.Input && p.ParameterDirection != null)) 117 | { 118 | oparm.Value = dapperParameters.Get(oparm.Name!); 119 | oparm.OutputCallback?.Invoke(oparm.Value); 120 | } 121 | } 122 | #endregion 123 | 124 | /// 125 | /// Responsible for parsing SqlParameters (see ) 126 | /// into a list of SqlParameterInfo that 127 | /// 128 | public static SqlParameterMapper InterpolatedSqlParameterParser = new SqlParameterMapper(); 129 | } 130 | 131 | } 132 | -------------------------------------------------------------------------------- /src/InterpolatedSql.Dapper/SqlParameters/SqlParameterMapper.cs: -------------------------------------------------------------------------------- 1 | using Dapper; 2 | using InterpolatedSql.SqlBuilders; 3 | using System; 4 | using System.Collections; 5 | using System.Collections.Generic; 6 | using System.Data; 7 | using System.Linq; 8 | 9 | namespace InterpolatedSql.Dapper 10 | { 11 | /// 12 | /// Maps from to Dapper Parameters. 13 | /// 14 | public class SqlParameterMapper 15 | { 16 | /// 17 | /// Calculates the name automatically assigned to interpolated parameters 18 | /// 19 | public virtual string CalculateAutoParameterName(InterpolatedSqlParameter parameter, int pos, InterpolatedSqlBuilderOptions options) 20 | { 21 | return options.AutoGeneratedParameterPrefix + 22 | pos.ToString() + 23 | (IsEnumerable(parameter.Argument) ? options.ParameterArrayNameSuffix : ""); 24 | } 25 | 26 | protected bool IsEnumerable(object? value) 27 | { 28 | if (value == null || value is DBNull) //SqlMapper.GetDbType 29 | return false; 30 | Type t = value.GetType(); 31 | return t != typeof(string) && typeof(IEnumerable).IsAssignableFrom(t); 32 | //TODO: use Dapper SqlMapper.LookupDbType ? 33 | } 34 | 35 | /// 36 | /// Converts from to Dapper Parameters. 37 | /// 38 | public virtual void AddToDynamicParameters(DynamicParameters target, SqlParameterInfo parameter) 39 | { 40 | //TODO: do implicit parameters have names here?! 41 | if (parameter is DbTypeParameterInfo dbParm) 42 | target.Add(parameter.Name!, parameter.Value, dbParm.DbType, parameter.ParameterDirection ?? ParameterDirection.Input, dbParm.Size); 43 | else if (parameter is StringParameterInfo stringParm) 44 | target.Add(parameter.Name!, new DbString() { Value = (string?)stringParm.Value, IsAnsi = stringParm.IsAnsi, IsFixedLength = stringParm.IsFixedLength, Length = stringParm.Length }); 45 | else if (parameter is SqlParameterInfo parm && parm.Value is IEnumerable stringParms) 46 | { 47 | target.Add(parameter.Name!, stringParms.Select(stringParm => new DbString() { Value = (string?)stringParm.Value, IsAnsi = stringParm.IsAnsi, IsFixedLength = stringParm.IsFixedLength, Length = stringParm.Length })); 48 | } 49 | else 50 | target.Add(parameter.Name!, parameter.Value); 51 | } 52 | 53 | /// 54 | /// Default mapper. By inheriting/overriding it's possible to modify this behavior 55 | /// 56 | public static SqlParameterMapper Default = new SqlParameterMapper(); 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/InterpolatedSql.StrongName/InterpolatedSql.StrongName.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | netstandard2.0;net462;net472;net5.0;net6.0;net7.0 5 | Rick Drizin 6 | MIT 7 | https://github.com/Drizin/InterpolatedSql/ 8 | SQL Builder using Fluent API and String Interpolation 9 | Rick Drizin 10 | Rick Drizin 11 | 2.4.0 12 | false 13 | InterpolatedSql (Strong Named) 14 | InterpolatedSql.StrongName 15 | InterpolatedSql.StrongName.xml 16 | sql builder;interpolated strings;formattablestring;query builder;string interpolation; 17 | true 18 | true 19 | 20 | NuGetReadMe.md 21 | InterpolatedSql.StrongName 22 | enable 23 | 9.0 24 | 25 | 26 | 27 | 28 | 29 | 30 | all 31 | runtime; build; native; contentfiles; analyzers; buildtransitive 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | True 40 | ..\debug.snk 41 | 42 | 43 | 44 | 45 | True 46 | ..\release.snk 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | -------------------------------------------------------------------------------- /src/InterpolatedSql.StrongName/InterpolatedSql.StrongName.nuspec: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | InterpolatedSql.StrongName 5 | InterpolatedSql (Strong Named) 6 | Rick Drizin 7 | Rick Drizin 8 | MIT 9 | https://github.com/Drizin/InterpolatedSql 10 | false 11 | InterpolatedSqlBuilder: Sql Builder using String Interpolation (injection-safe!) 12 | Copyright Rick Drizin 2023 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /src/InterpolatedSql.Tests/GlobalSuppressions.cs: -------------------------------------------------------------------------------- 1 | // This file is used by Code Analysis to maintain SuppressMessage 2 | // attributes that are applied to this project. 3 | // Project-level suppressions either have no target or are given 4 | // a specific target and scoped to a namespace, type, member, etc. 5 | 6 | using System.Diagnostics.CodeAnalysis; 7 | 8 | [assembly: SuppressMessage("Style", "IDE0008:Use explicit type", Justification = "var avoids noisy code")] 9 | -------------------------------------------------------------------------------- /src/InterpolatedSql.Tests/Helpers/UnitTestsDbCommand.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Data; 4 | using System.Text; 5 | 6 | namespace InterpolatedSql.Tests 7 | { 8 | /// 9 | /// This is just a wrapper around IDbCommand, which allows us to inspect how Dapper is preparing our Commands 10 | /// 11 | public class UnitTestsDbCommand : IDbCommand 12 | { 13 | private readonly IDbCommand _cmd; 14 | private readonly UnitTestsDbConnection _ownerConnection; 15 | public UnitTestsDbCommand(IDbCommand command, UnitTestsDbConnection ownerConnection) 16 | { 17 | _cmd = command; 18 | _ownerConnection = ownerConnection; 19 | } 20 | 21 | public string CommandText { get => _cmd.CommandText; set => _cmd.CommandText = value; } 22 | public int CommandTimeout { get => _cmd.CommandTimeout; set => _cmd.CommandTimeout = value; } 23 | public CommandType CommandType { get => _cmd.CommandType; set => _cmd.CommandType = value; } 24 | public IDbConnection Connection { get => _cmd.Connection; set => _cmd.Connection = value; } 25 | 26 | public IDataParameterCollection Parameters => _cmd.Parameters; 27 | 28 | public IDbTransaction Transaction { get => _cmd.Transaction; set => _cmd.Transaction = value; } 29 | public UpdateRowSource UpdatedRowSource { get => _cmd.UpdatedRowSource; set => _cmd.UpdatedRowSource = value; } 30 | 31 | public void Cancel() 32 | { 33 | _cmd.Cancel(); 34 | } 35 | 36 | public IDbDataParameter CreateParameter() 37 | { 38 | return _cmd.CreateParameter(); 39 | } 40 | 41 | public void Dispose() 42 | { 43 | _cmd.Dispose(); 44 | } 45 | 46 | public int ExecuteNonQuery() 47 | { 48 | SaveCurrentCommand(); 49 | return _cmd.ExecuteNonQuery(); 50 | } 51 | 52 | public IDataReader ExecuteReader() 53 | { 54 | SaveCurrentCommand(); 55 | return _cmd.ExecuteReader(); 56 | } 57 | 58 | public IDataReader ExecuteReader(CommandBehavior behavior) 59 | { 60 | SaveCurrentCommand(); 61 | return _cmd.ExecuteReader(behavior); 62 | } 63 | 64 | public object ExecuteScalar() 65 | { 66 | SaveCurrentCommand(); 67 | return _cmd.ExecuteScalar(); 68 | } 69 | 70 | public void Prepare() 71 | { 72 | SaveCurrentCommand(); 73 | _cmd.Prepare(); 74 | } 75 | 76 | private void SaveCurrentCommand() 77 | { 78 | var cmdDetails = new UnitTestsDbConnection.ExecutedCommandDetails() { CommandText = _cmd.CommandText }; 79 | foreach (var parm in _cmd.Parameters) 80 | cmdDetails.Parameters.Add(((IDbDataParameter)parm).ParameterName, parm); 81 | _ownerConnection.PreviousCommands.Add(cmdDetails); 82 | } 83 | 84 | } 85 | 86 | } 87 | -------------------------------------------------------------------------------- /src/InterpolatedSql.Tests/Helpers/UnitTestsDbConnection.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Data; 3 | using System.Diagnostics; 4 | 5 | namespace InterpolatedSql.Tests 6 | { 7 | /// 8 | /// This is just a wrapper around IDbConnection, which allows us to inspect how Dapper is preparing our Commands 9 | /// 10 | public class UnitTestsDbConnection : IDbConnection 11 | { 12 | private readonly IDbConnection _conn; 13 | 14 | // Since Dapper clears the parameters of IDbCommands after their execution we have to store a copy of the information instead of storing the IDbCommand itself 15 | public List PreviousCommands { get; set; } = new List(); 16 | 17 | public UnitTestsDbConnection(IDbConnection connection) 18 | { 19 | _conn = connection; 20 | } 21 | 22 | public string ConnectionString { get => _conn.ConnectionString; set => _conn.ConnectionString = value; } 23 | 24 | public int ConnectionTimeout => _conn.ConnectionTimeout; 25 | 26 | public string Database => _conn.Database; 27 | 28 | public ConnectionState State => _conn.State; 29 | 30 | public IDbTransaction BeginTransaction() 31 | { 32 | return _conn.BeginTransaction(); 33 | } 34 | 35 | public IDbTransaction BeginTransaction(IsolationLevel il) 36 | { 37 | return _conn.BeginTransaction(il); 38 | } 39 | 40 | public void ChangeDatabase(string databaseName) 41 | { 42 | _conn.ChangeDatabase(databaseName); 43 | } 44 | 45 | public void Close() 46 | { 47 | _conn.Close(); 48 | } 49 | 50 | public IDbCommand CreateCommand() 51 | { 52 | return new UnitTestsDbCommand(_conn.CreateCommand(), this); 53 | } 54 | 55 | public void Dispose() 56 | { 57 | _conn.Dispose(); 58 | } 59 | 60 | public void Open() 61 | { 62 | _conn.Open(); 63 | } 64 | 65 | [DebuggerDisplay("{CommandText}")] 66 | public class ExecutedCommandDetails 67 | { 68 | public string CommandText { get; set; } 69 | public Dictionary Parameters { get; set; } = new Dictionary(); 70 | } 71 | } 72 | 73 | } 74 | -------------------------------------------------------------------------------- /src/InterpolatedSql.Tests/InterpolatedSql.Tests.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | net462;net5.0;net6.0 5 | false 6 | Rick Drizin 7 | Rick Drizin 8 | MIT 9 | https://github.com/Drizin/InterpolatedSql/ 10 | enable 11 | 10.0 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /src/InterpolatedSql.Tests/SqlTests.cs: -------------------------------------------------------------------------------- 1 | using InterpolatedSql.SqlBuilders; 2 | using NUnit.Framework; 3 | 4 | namespace InterpolatedSql.Tests 5 | { 6 | public class SqlTests 7 | { 8 | 9 | [Test] 10 | public void Test1() //TODO: move some tests from InterpolatedSql.Dapper.Tests to this project. 11 | { 12 | int val = 1; 13 | var s1 = new SqlBuilder($"INSERT INTO [Table] (col1) VALUES ({val});").Build(); 14 | var s2 = new SqlBuilder($"INSERT INTO [Table] (col1) VALUES ('{val}');").Build(); 15 | var s3 = new SqlBuilder($"INSERT INTO [Table] (col1, col2) VALUES ({val}, {val});").Build(); 16 | InterpolatedSqlBuilderOptions.DefaultOptions.ReuseIdenticalParameters = true; 17 | var s4 = new SqlBuilder($"INSERT INTO [Table] (col1, col2) VALUES ({val}, {val});").Build(); 18 | var s5 = SqlBuilderFactory.Default.Create($"INSERT INTO [Table] (col1, col2) VALUES ({val}, {val});").Build(); 19 | 20 | Assert.AreEqual("INSERT INTO [Table] (col1) VALUES (@p0);", s1.Sql); 21 | Assert.AreEqual(1, s1.SqlParameters.Count); 22 | Assert.AreEqual(val, s1.SqlParameters[0].Argument); 23 | 24 | Assert.AreEqual("INSERT INTO [Table] (col1) VALUES (@p0);", s2.Sql); 25 | Assert.AreEqual(1, s2.SqlParameters.Count); 26 | Assert.AreEqual(val, s2.SqlParameters[0].Argument); 27 | 28 | Assert.AreEqual("INSERT INTO [Table] (col1, col2) VALUES (@p0, @p1);", s3.Sql); 29 | Assert.AreEqual("INSERT INTO [Table] (col1, col2) VALUES (@p0, @p0);", s4.Sql); 30 | Assert.AreEqual("INSERT INTO [Table] (col1, col2) VALUES (@p0, @p0);", s5.Sql); 31 | } 32 | 33 | [Test] 34 | public void Test2() 35 | { 36 | var qb = new QueryBuilder(@$" 37 | SELECT COUNT(*) TotalCount 38 | FROM [File] f 39 | INNER JOIN Folder fo ON f.Folder = fo.UID 40 | WHERE f.Deleted != 1 /**filters**/ 41 | "); 42 | 43 | qb.Where($"Price>{1}"); 44 | qb.Where($"Price<{10}"); 45 | 46 | var b = qb.Build(); 47 | System.Diagnostics.Debug.Write(b.Sql); 48 | Assert.AreEqual(@$" 49 | SELECT COUNT(*) TotalCount 50 | FROM [File] f 51 | INNER JOIN Folder fo ON f.Folder = fo.UID 52 | WHERE f.Deleted != 1 AND Price>@p0 AND Price<@p1 53 | ", b.Sql); 54 | Assert.AreEqual(2, b.SqlParameters.Count); 55 | Assert.AreEqual(1, b.SqlParameters[0].Argument); 56 | Assert.AreEqual(10, b.SqlParameters[1].Argument); 57 | } 58 | 59 | [Test] 60 | public void TestMultipleFilterExtensions() 61 | { 62 | InterpolatedSqlBuilderOptions.DefaultOptions.ReuseIdenticalParameters = false; 63 | var storageFolder = "_CALCENGINES"; 64 | var sqlTestNamePattern = "'%~_Test.%' ESCAPE '~'"; 65 | var regularNames = new[] { "Conduent_1_CE" }; 66 | 67 | var qb = new QueryBuilder(@$" 68 | SELECT COUNT(*) TotalCount 69 | FROM [File] f 70 | INNER JOIN Folder fo ON f.Folder = fo.UID 71 | WHERE fo.Name = {storageFolder:varchar(200)} AND f.Name NOT LIKE {sqlTestNamePattern:raw} AND f.Deleted != 1 /**filters**/ 72 | 73 | SELECT COUNT(*) TotalCount 74 | FROM [File] f 75 | INNER JOIN Folder fo ON f.Folder = fo.UID 76 | WHERE fo.Name = {storageFolder:varchar(200)} AND f.Name NOT LIKE {sqlTestNamePattern:raw} AND f.Deleted != 1 /**filters**/ 77 | "); 78 | 79 | var filters = new Filters(Filters.FiltersType.AND) 80 | { 81 | new Filter($"f.Name IN {regularNames:varchar(200)}") 82 | }; 83 | 84 | qb.Where(filters); 85 | 86 | var sql = qb.Build(); 87 | 88 | Assert.AreEqual(@" 89 | SELECT COUNT(*) TotalCount 90 | FROM [File] f 91 | INNER JOIN Folder fo ON f.Folder = fo.UID 92 | WHERE fo.Name = @p0 AND f.Name NOT LIKE '%~_Test.%' ESCAPE '~' AND f.Deleted != 1 AND f.Name IN @p2 93 | 94 | SELECT COUNT(*) TotalCount 95 | FROM [File] f 96 | INNER JOIN Folder fo ON f.Folder = fo.UID 97 | WHERE fo.Name = @p1 AND f.Name NOT LIKE '%~_Test.%' ESCAPE '~' AND f.Deleted != 1 AND f.Name IN @p3 98 | ", sql.Sql); 99 | } 100 | 101 | } 102 | } -------------------------------------------------------------------------------- /src/InterpolatedSql.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 17 4 | VisualStudioVersion = 17.2.32505.173 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{88CCCDA2-06FC-42EF-8AAF-D92250C285AD}" 7 | ProjectSection(SolutionItems) = preProject 8 | .editorconfig = .editorconfig 9 | EndProjectSection 10 | EndProject 11 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "InterpolatedSql", "InterpolatedSql\InterpolatedSql.csproj", "{4C89CBC9-A8DD-442A-8273-3521653D3600}" 12 | EndProject 13 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "InterpolatedSql.Tests", "InterpolatedSql.Tests\InterpolatedSql.Tests.csproj", "{1D3449CF-A1D8-483E-91D3-0C4828E0BF0B}" 14 | EndProject 15 | Global 16 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 17 | Debug|Any CPU = Debug|Any CPU 18 | Release|Any CPU = Release|Any CPU 19 | EndGlobalSection 20 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 21 | {4C89CBC9-A8DD-442A-8273-3521653D3600}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 22 | {4C89CBC9-A8DD-442A-8273-3521653D3600}.Debug|Any CPU.Build.0 = Debug|Any CPU 23 | {4C89CBC9-A8DD-442A-8273-3521653D3600}.Release|Any CPU.ActiveCfg = Release|Any CPU 24 | {4C89CBC9-A8DD-442A-8273-3521653D3600}.Release|Any CPU.Build.0 = Release|Any CPU 25 | {1D3449CF-A1D8-483E-91D3-0C4828E0BF0B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 26 | {1D3449CF-A1D8-483E-91D3-0C4828E0BF0B}.Debug|Any CPU.Build.0 = Debug|Any CPU 27 | {1D3449CF-A1D8-483E-91D3-0C4828E0BF0B}.Release|Any CPU.ActiveCfg = Release|Any CPU 28 | {1D3449CF-A1D8-483E-91D3-0C4828E0BF0B}.Release|Any CPU.Build.0 = Release|Any CPU 29 | EndGlobalSection 30 | GlobalSection(SolutionProperties) = preSolution 31 | HideSolutionNode = FALSE 32 | EndGlobalSection 33 | GlobalSection(ExtensibilityGlobals) = postSolution 34 | SolutionGuid = {AB589EF0-8B1A-4791-A8B5-F3ED178AF34F} 35 | EndGlobalSection 36 | EndGlobal 37 | -------------------------------------------------------------------------------- /src/InterpolatedSql/GlobalSuppressions.cs: -------------------------------------------------------------------------------- 1 | // This file is used by Code Analysis to maintain SuppressMessage 2 | // attributes that are applied to this project. 3 | // Project-level suppressions either have no target or are given 4 | // a specific target and scoped to a namespace, type, member, etc. 5 | 6 | using System.Diagnostics.CodeAnalysis; 7 | 8 | [assembly: SuppressMessage("Style", "IDE0008:Use explicit type", Justification = "var avoids noisy code")] 9 | -------------------------------------------------------------------------------- /src/InterpolatedSql/IInterpolatedSql.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | 4 | namespace InterpolatedSql 5 | { 6 | /// 7 | /// SQL Statement (text and parameters) written using string interpolation. 8 | /// 9 | public interface IInterpolatedSql 10 | { 11 | /// 12 | /// SQL Statement (text). 13 | /// The statement may refer to parameters (usually named like @p0, @p1, etc) that were captured using string interpolation - like "SELECT * FROM Table WHERE column >= @p0" 14 | /// Those associated parameters are stored in . 15 | /// It may (or may not) be a a complete and valid Sql statement (it can be a partial statement to be used/combined inside a more complex statement) 16 | /// 17 | string Sql { get; } 18 | 19 | /// 20 | /// This is the underlying format of the interpolated SQL statement. 21 | /// Exactly like , but instead of rendering names to each parameter (like @p0, @p1, etc) this uses composite format 22 | /// (, 23 | /// which means that the parameters (captured using string interpolation) are refered by numbered placeholders, 24 | /// like "SELECT * FROM Table WHERE column >= {0}". 25 | /// In other words, this is like . 26 | /// 27 | string Format { get; } 28 | 29 | /// 30 | /// Parameters that were captured using string interpolation. 31 | /// E.g. if you create a statement like "SELECT * FROM Table WHERE column >= {variable}", then the first SqlParameter will be a reference to the variable. 32 | /// In other words, this is similar to . 33 | /// Can be of any type (like int/string or any other primitive, or or even System.Data.SqlParameter) 34 | /// 35 | IReadOnlyList SqlParameters { get; } 36 | 37 | /// 38 | /// Explicit parameters (named, and added explicitly - not using interpolated arguments) 39 | /// 40 | IReadOnlyList ExplicitParameters { get; } 41 | } 42 | 43 | /// 44 | /// Use this generic version if you have extensions depending on type 45 | /// The underlying concrete type (or one of its interfaces) that implements this interface. 46 | public interface IInterpolatedSql : IInterpolatedSql 47 | { 48 | } 49 | 50 | } 51 | -------------------------------------------------------------------------------- /src/InterpolatedSql/ISqlCommand.cs: -------------------------------------------------------------------------------- 1 | using System.Data; 2 | 3 | namespace InterpolatedSql 4 | { 5 | /// 6 | /// Any IInterpolatedSql that can be executed. 7 | /// Should contain DbConnection, Sql statement (complete and valid), and Parameters. 8 | /// Extension methods can be used to provide query/execution facades over this interface. 9 | /// Fluent Query Builders can use this interface to define which states are valid SQL statements. 10 | /// 11 | public interface ISqlCommand : IInterpolatedSql 12 | { 13 | /// 14 | /// Database connection associated to the command 15 | /// 16 | IDbConnection DbConnection { get; set; } 17 | } 18 | 19 | /// 20 | /// The underlying concrete type (or one of its interfaces) that implements this interface. Useful for Fluent APIs 21 | public interface ISqlCommand : ISqlCommand, IInterpolatedSql 22 | { 23 | } 24 | 25 | } 26 | -------------------------------------------------------------------------------- /src/InterpolatedSql/ImmutableInterpolatedSql.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | namespace InterpolatedSql 4 | { 5 | /// 6 | /// Immutable implementation of . 7 | /// If you want a mutable class (to dynamically append statements) you probably need or some builder 8 | /// 9 | public class ImmutableInterpolatedSql : IInterpolatedSql 10 | { 11 | /// 12 | public ImmutableInterpolatedSql(string sql, string format, IReadOnlyList sqlParameters, IReadOnlyList explicitParameters) 13 | { 14 | Sql = sql; 15 | Format = format; 16 | SqlParameters = sqlParameters; 17 | ExplicitParameters = explicitParameters; 18 | } 19 | 20 | /// 21 | public string Format { get; private set; } 22 | 23 | /// 24 | public IReadOnlyList SqlParameters { get; private set; } 25 | 26 | /// 27 | public IReadOnlyList ExplicitParameters { get; private set; } 28 | 29 | /// 30 | public string Sql { get; private set; } 31 | } 32 | } -------------------------------------------------------------------------------- /src/InterpolatedSql/InterpolatedSql.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | netstandard2.0;net462;net472;net5.0;net6.0;net7.0 5 | Rick Drizin 6 | MIT 7 | https://github.com/Drizin/InterpolatedSql/ 8 | SQL Builder using Fluent API and String Interpolation 9 | Rick Drizin 10 | Rick Drizin 11 | 2.4.0 12 | false 13 | InterpolatedSql 14 | InterpolatedSql 15 | InterpolatedSql.xml 16 | sql builder;interpolated strings;formattablestring;query builder;string interpolation; 17 | true 18 | true 19 | 20 | NuGetReadMe.md 21 | InterpolatedSql 22 | enable 23 | 9.0 24 | 25 | 26 | 27 | 28 | 29 | 30 | all 31 | runtime; build; native; contentfiles; analyzers; buildtransitive 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | -------------------------------------------------------------------------------- /src/InterpolatedSql/InterpolatedSql.nuspec: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | InterpolatedSql 5 | InterpolatedSql 6 | Rick Drizin 7 | Rick Drizin 8 | MIT 9 | https://github.com/Drizin/InterpolatedSql 10 | false 11 | InterpolatedSqlBuilder: Sql Builder using String Interpolation (injection-safe!) 12 | Copyright Rick Drizin 2023 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /src/InterpolatedSql/InterpolatedSqlParsers/IInterpolatedSqlParser.cs: -------------------------------------------------------------------------------- 1 | using InterpolatedSql.SqlBuilders; 2 | using System; 3 | 4 | namespace InterpolatedSql 5 | { 6 | /// 7 | /// Parses FormattableString into using regex. 8 | /// 9 | public interface IInterpolatedSqlParser 10 | { 11 | /// 12 | /// Parses a FormattableString and Appends it to an existing 13 | /// 14 | void ParseAppend(IInterpolatedSqlBuilderBase target, FormattableString value); 15 | 16 | /// 17 | /// Parses a FormattableString and Inserts it at specified position into an existing 18 | /// 19 | void ParseInsert(IInterpolatedSqlBuilderBase target, int index, FormattableString value); 20 | 21 | /// 22 | /// When a FormattableString is appended to an existing InterpolatedString, 23 | /// the underlying format (where there are numeric placeholders) needs to be shifted because the arguments will have new positions in the final array 24 | /// This method is used to shift a format by a number of positions. 25 | /// 26 | string ShiftPlaceholderPositions(string format, Func getNewPos); 27 | 28 | /// 29 | string AdjustMultilineString(string block); 30 | } 31 | } -------------------------------------------------------------------------------- /src/InterpolatedSql/InterpolatedSqlParsers/InterpolatedSqlHandler{B}.cs: -------------------------------------------------------------------------------- 1 | #if NET6_0_OR_GREATER 2 | using InterpolatedSql.SqlBuilders; 3 | using System; 4 | using System.Diagnostics; 5 | using System.Runtime.CompilerServices; 6 | 7 | namespace InterpolatedSql 8 | { 9 | /// 10 | /// Just a simple InterpolatedStringHandler that builds a without requiring regex parsing. 11 | /// 12 | [InterpolatedStringHandler] 13 | [DebuggerDisplay("{InterpolatedSqlBuilder}")] 14 | public ref struct InterpolatedSqlHandler 15 | where B : IInterpolatedSqlBuilderBase, new() 16 | { 17 | /// 18 | /// Underlying Interpolated String 19 | /// 20 | public B InterpolatedSqlBuilder => _interpolatedSqlBuilder; 21 | 22 | private readonly B _interpolatedSqlBuilder; 23 | 24 | /// 25 | /// Position of the underlying InterpolatedSqlBuilder when this InterpolatedSqlHandler started writing into it. 26 | /// 27 | private int _sqlBuilderStartingPos; 28 | 29 | /// 30 | public InterpolatedSqlHandler(int literalLength, int formattedCount) // InterpolatedSqlFactory.Create() doesn't provide "this" (InterpolatedSqlHandler will create a new StringBuilder) 31 | { 32 | _interpolatedSqlBuilder = InterpolatedSqlBuilderFactory.Default.Create(literalLength, formattedCount); 33 | _sqlBuilderStartingPos = 0; 34 | } 35 | 36 | /// 37 | public InterpolatedSqlHandler(int literalLength, int formattedCount, InterpolatedSqlBuilderOptions options) 38 | { 39 | _interpolatedSqlBuilder = InterpolatedSqlBuilderFactory.Default.Create(literalLength, formattedCount, options); 40 | _sqlBuilderStartingPos = 0; 41 | } 42 | 43 | /// 44 | /// Appends a literal string. 45 | /// 46 | public void AppendLiteral(string value) 47 | { 48 | _interpolatedSqlBuilder.AppendLiteral(value); 49 | _interpolatedSqlBuilder.AutoSpacing = false; // Autospacing should be applied only to the whole interpolated block, not to each individual literal/argument. //TODO: use SCOPES in SqlBuilder and scoped-variables 50 | } 51 | 52 | /// Appends the specified object. 53 | public void AppendFormatted(ReadOnlySpan value) 54 | { 55 | _interpolatedSqlBuilder.AppendArgument(value.ToString()); 56 | _interpolatedSqlBuilder.AutoSpacing = false; 57 | } 58 | 59 | /// Appends the specified object. 60 | public void AppendFormatted(string? value) 61 | { 62 | _interpolatedSqlBuilder.AppendArgument(value?.ToString()); 63 | _interpolatedSqlBuilder.AutoSpacing = false; 64 | } 65 | 66 | /// Appends the specified object. 67 | public void AppendFormatted(string? value, int alignment = 0, string? format = null) 68 | { 69 | _interpolatedSqlBuilder.AppendArgument(value, alignment, format); 70 | _interpolatedSqlBuilder.AutoSpacing = false; 71 | } 72 | 73 | /// Appends the specified object. 74 | public void AppendFormatted(object? value, int alignment = 0, string? format = null) 75 | { 76 | _interpolatedSqlBuilder.AppendArgument(value, alignment, format); 77 | _interpolatedSqlBuilder.AutoSpacing = false; 78 | } 79 | 80 | 81 | /// Appends the specified object. 82 | public void AppendFormatted(T value) 83 | { 84 | // If we get a nested IInterpolatedSql (including InterpolatedSqlBuilder or any derived type), it's already parsed - we just merge to current one 85 | if (value is IInterpolatedSql isqlArg) 86 | { 87 | _interpolatedSqlBuilder.Append(isqlArg); // this will automatically shift the arguments 88 | _interpolatedSqlBuilder.AutoSpacing = false; 89 | return; 90 | } 91 | 92 | if (value is ISqlEnricher enricher) 93 | { 94 | _interpolatedSqlBuilder.Append(enricher.GetEnrichedSql()); // this will automatically shift the arguments 95 | _interpolatedSqlBuilder.AutoSpacing = false; 96 | return; 97 | } 98 | 99 | // If we get a nested FormattableString, we parse it (recursively) and merge it to current one 100 | if (value is FormattableString fsArg) 101 | { 102 | _interpolatedSqlBuilder.AppendFormattableString(fsArg); // this will automatically shift the arguments 103 | _interpolatedSqlBuilder.AutoSpacing = false; 104 | return; 105 | } 106 | 107 | if (value is InterpolatedSqlBuilderBase builder) 108 | { 109 | _interpolatedSqlBuilder.Append(builder.AsSql()); // this will automatically shift the arguments 110 | _interpolatedSqlBuilder.AutoSpacing = false; 111 | return; 112 | } 113 | 114 | _interpolatedSqlBuilder.AppendArgument(value, 0, null); 115 | _interpolatedSqlBuilder.AutoSpacing = false; 116 | } 117 | 118 | /// 119 | /// Appends the specified object. 120 | /// 121 | public void AppendFormatted(T value, string format) 122 | { 123 | _interpolatedSqlBuilder.AppendArgument(value, 0, format); 124 | _interpolatedSqlBuilder.AutoSpacing = false; 125 | } 126 | 127 | 128 | /// 129 | /// Removes the left padding of the whole string that was just processed. 130 | /// See InterpolatedSqlParser.AdjustMultilineString() for more info 131 | /// 132 | public void AdjustMultilineString() 133 | { 134 | //TODO: process the first few characters of _interpolatedSqlBuilder.Format[_sqlBuilderStartingPos...], ignore first linebreak (if exists), and check if it starts with whitespace. 135 | // if it's not whitespace then we don't even need to extract the string and try the expensive reformatting. 136 | string format = _interpolatedSqlBuilder.Format.Substring(_sqlBuilderStartingPos, _interpolatedSqlBuilder.Format.Length - _sqlBuilderStartingPos); 137 | string adjustedFormat = _interpolatedSqlBuilder.Options.Parser.AdjustMultilineString(format); 138 | if (!format.Equals(adjustedFormat)) 139 | { 140 | _interpolatedSqlBuilder.Remove(_sqlBuilderStartingPos, _interpolatedSqlBuilder.Format.Length - _sqlBuilderStartingPos); 141 | _interpolatedSqlBuilder.InsertLiteral(_sqlBuilderStartingPos, adjustedFormat); 142 | } 143 | } 144 | 145 | } 146 | } 147 | #endif 148 | -------------------------------------------------------------------------------- /src/InterpolatedSql/NuGetReadMe.md: -------------------------------------------------------------------------------- 1 | # Interpolated SQL Builder 2 | 3 | **Interpolated SQL Builder using String Interpolation** 4 | 5 | InterpolatedSqlBuilder is a dynamic SQL builder (but injection safe) where SqlParameters are defined using string interpolation. 6 | 7 | -------------------------------------------------------------------------------- /src/InterpolatedSql/SqlBuilders/Factories/ISqlBuilderFactory.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace InterpolatedSql.SqlBuilders 4 | { 5 | public interface ISqlBuilderFactory 6 | { 7 | SqlBuilder Create(); 8 | SqlBuilder Create(InterpolatedSqlBuilderOptions options); 9 | #if NET6_0_OR_GREATER 10 | SqlBuilder Create(ref InterpolatedSqlHandler value); 11 | SqlBuilder Create(InterpolatedSqlBuilderOptions options, [System.Runtime.CompilerServices.InterpolatedStringHandlerArgument("options")] ref InterpolatedSqlHandler value); 12 | SqlBuilder Create(int literalLength, int formattedCount, InterpolatedSqlBuilderOptions? options = null); 13 | #else 14 | SqlBuilder Create(FormattableString value); 15 | SqlBuilder Create(InterpolatedSqlBuilderOptions options, FormattableString value); 16 | #endif 17 | } 18 | } -------------------------------------------------------------------------------- /src/InterpolatedSql/SqlBuilders/Factories/InterpolatedSqlBuilderFactory.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace InterpolatedSql.SqlBuilders 4 | { 5 | /// 6 | /// Generic factory to create any builder (subclasses of ). 7 | /// This uses reflection and is for advanced customization scenarios, for most cases you should just use . 8 | /// 9 | public class InterpolatedSqlBuilderFactory 10 | where B : IInterpolatedSqlBuilderBase, new() 11 | { 12 | /// 13 | /// Creates a new IInterpolatedSqlBuilderBase of type B 14 | /// 15 | public virtual B Create() 16 | { 17 | var ctor = typeof(B).GetConstructor(new Type[] { }); 18 | var builder = (B)ctor.Invoke(new object[] { }); 19 | return builder; 20 | } 21 | 22 | /// 23 | /// Creates a new IInterpolatedSqlBuilderBase of type B 24 | /// 25 | public virtual B Create(InterpolatedSqlBuilderOptions options) 26 | { 27 | var ctor = typeof(B).GetConstructor(new Type[] { typeof(InterpolatedSqlBuilderOptions) }); 28 | B builder; 29 | if (ctor != null) 30 | { 31 | builder = (B)ctor.Invoke(new object[] { options }); 32 | } 33 | else 34 | { 35 | ctor = typeof(B).GetConstructor(new Type[] { }); 36 | builder = (B)ctor.Invoke(new object[] { }); 37 | builder.Options = options; 38 | } 39 | return builder; 40 | } 41 | 42 | 43 | #if NET6_0_OR_GREATER 44 | /// 45 | /// Creates a new IInterpolatedSqlBuilderBase of type B 46 | /// 47 | public virtual B Create(int literalLength, int formattedCount) 48 | { 49 | B builder; 50 | var ctor = typeof(B).GetConstructor(new Type[] { typeof(int), typeof(int) }); 51 | if (ctor != null) 52 | { 53 | builder = (B)ctor.Invoke(new object[] { literalLength, formattedCount }); 54 | } 55 | else 56 | { 57 | ctor = typeof(B).GetConstructor(new Type[] { }); 58 | builder = (B)ctor.Invoke(new object[] { }); 59 | } 60 | return builder; 61 | } 62 | 63 | /// 64 | /// Creates a new IInterpolatedSqlBuilderBase of type B 65 | /// 66 | public virtual B Create(int literalLength, int formattedCount, InterpolatedSqlBuilderOptions options) 67 | { 68 | B builder; 69 | var ctor = typeof(B).GetConstructor(new Type[] { typeof(int), typeof(int), typeof(InterpolatedSqlBuilderOptions) }); 70 | if (ctor != null) 71 | { 72 | builder = (B)ctor.Invoke(new object?[] { literalLength, formattedCount, options }); 73 | } 74 | else 75 | { 76 | ctor = typeof(B).GetConstructor(new Type[] { }); 77 | builder = (B)ctor.Invoke(new object[] { }); 78 | builder.Options = options; 79 | } 80 | return builder; 81 | } 82 | 83 | /// 84 | /// Creates a new IInterpolatedSqlBuilder of type B 85 | /// 86 | public virtual B Create(ref InterpolatedSqlHandler value) 87 | { 88 | if (value.InterpolatedSqlBuilder.Options.AutoAdjustMultilineString) 89 | value.AdjustMultilineString(); 90 | return value.InterpolatedSqlBuilder; 91 | } 92 | 93 | /// 94 | /// Creates a new IInterpolatedSqlBuilder of type B 95 | /// 96 | public virtual B Create(InterpolatedSqlBuilderOptions options, [System.Runtime.CompilerServices.InterpolatedStringHandlerArgument("options")] ref InterpolatedSqlHandler value) 97 | { 98 | if (value.InterpolatedSqlBuilder.Options.AutoAdjustMultilineString) 99 | value.AdjustMultilineString(); 100 | return value.InterpolatedSqlBuilder; 101 | } 102 | 103 | #else 104 | 105 | /// 106 | /// Creates a new IInterpolatedSqlBuilder of type B 107 | /// 108 | public virtual B Create(FormattableString value) 109 | { 110 | B builder; 111 | var ctor = typeof(B).GetConstructor(new Type[] { typeof(FormattableString) }); 112 | if (ctor != null) 113 | { 114 | builder = (B)ctor.Invoke(new object[] { value }); 115 | } 116 | else 117 | { 118 | ctor = typeof(B).GetConstructor(new Type[] { }); 119 | builder = (B)ctor.Invoke(new object[] { }); 120 | Parser.ParseAppend(builder, value); 121 | } 122 | return builder; 123 | } 124 | 125 | /// 126 | /// Creates a new InterpolatedSqlBuilder using regular expression for parsing the FormattableString. 127 | /// 128 | public virtual B Create(InterpolatedSqlBuilderOptions options, FormattableString value) 129 | { 130 | B builder = Create(options); 131 | Parser.ParseAppend(builder, value); 132 | return builder; 133 | } 134 | 135 | #endif 136 | 137 | 138 | /// 139 | /// Default Factory 140 | /// 141 | public static InterpolatedSqlBuilderFactory Default = new InterpolatedSqlBuilderFactory(); 142 | 143 | /// 144 | /// Default Parser 145 | /// 146 | public InterpolatedSqlParser Parser = new InterpolatedSqlParser(); 147 | 148 | } 149 | } 150 | -------------------------------------------------------------------------------- /src/InterpolatedSql/SqlBuilders/Factories/SqlBuilderFactory.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace InterpolatedSql.SqlBuilders 4 | { 5 | /// 6 | /// Factory to create instances of 7 | /// 8 | public class SqlBuilderFactory : ISqlBuilderFactory 9 | { 10 | /// 11 | /// Creates a new SqlBuilder 12 | /// 13 | public virtual SqlBuilder Create() 14 | { 15 | var builder = new SqlBuilder(); 16 | return builder; 17 | } 18 | 19 | /// 20 | /// Creates a new SqlBuilder 21 | /// 22 | public virtual SqlBuilder Create(InterpolatedSqlBuilderOptions options) 23 | { 24 | var builder = new SqlBuilder(options); 25 | return builder; 26 | } 27 | 28 | 29 | 30 | #if NET6_0_OR_GREATER 31 | /// 32 | /// Creates a new SqlBuilder 33 | /// 34 | public virtual SqlBuilder Create(ref InterpolatedSqlHandler value) 35 | { 36 | if (value.InterpolatedSqlBuilder.Options.AutoAdjustMultilineString) 37 | value.AdjustMultilineString(); 38 | return (SqlBuilder)value.InterpolatedSqlBuilder; // InterpolatedSqlHandler might take any IInterpolatedSqlBuilderBase, but can only create SqlBuilder 39 | } 40 | 41 | /// 42 | /// Creates a new SqlBuilder 43 | /// 44 | public virtual SqlBuilder Create(InterpolatedSqlBuilderOptions options, [System.Runtime.CompilerServices.InterpolatedStringHandlerArgument("options")] ref InterpolatedSqlHandler value) 45 | { 46 | if (value.InterpolatedSqlBuilder.Options.AutoAdjustMultilineString) 47 | value.AdjustMultilineString(); 48 | return (SqlBuilder)value.InterpolatedSqlBuilder; // InterpolatedSqlHandler might take any IInterpolatedSqlBuilderBase, but can only create SqlBuilder 49 | } 50 | 51 | /// 52 | /// Creates a new SqlBuilder 53 | /// 54 | public virtual SqlBuilder Create(int literalLength, int formattedCount, InterpolatedSqlBuilderOptions? options = null) 55 | { 56 | SqlBuilder builder = new SqlBuilder(literalLength, formattedCount, options); 57 | return builder; 58 | } 59 | #else 60 | /// 61 | /// Creates a new SqlBuilder 62 | /// 63 | public virtual SqlBuilder Create(FormattableString value) 64 | { 65 | var builder = new SqlBuilder(value); 66 | return builder; 67 | } 68 | 69 | /// 70 | /// Creates a new SqlBuilder 71 | /// 72 | public virtual SqlBuilder Create(InterpolatedSqlBuilderOptions options, FormattableString value) 73 | { 74 | var builder = new SqlBuilder(value, options); 75 | return builder; 76 | } 77 | 78 | #endif 79 | 80 | 81 | /// 82 | /// Default Factory 83 | /// 84 | public static SqlBuilderFactory Default = new SqlBuilderFactory(); 85 | 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /src/InterpolatedSql/SqlBuilders/FluentQueryBuilder/ICompleteBuilder.cs: -------------------------------------------------------------------------------- 1 | namespace InterpolatedSql.SqlBuilders.FluentQueryBuilder 2 | { 3 | /// 4 | /// Query Builder with one or more clause in where, which can still add more clauses to where 5 | /// 6 | public interface ICompleteBuilder 7 | //: ISqlCommand 8 | 9 | where U : IFluentQueryBuilder 10 | where RB : ISqlBuilder 11 | where R: class, IInterpolatedSql 12 | { 13 | /// 14 | /// Builds the SQL statement 15 | /// 16 | R Build(); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/InterpolatedSql/SqlBuilders/FluentQueryBuilder/IEmptyQueryBuilder.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace InterpolatedSql.SqlBuilders.FluentQueryBuilder 4 | { 5 | /// 6 | /// Empty QueryBuilder (initialized without a template), which can start both with Select() or SelectDistinct() 7 | /// 8 | public interface IEmptyQueryBuilder 9 | where U : IFluentQueryBuilder 10 | where RB : ISqlBuilder 11 | where R: class, IInterpolatedSql 12 | { 13 | /// 14 | /// Adds one column to the select clauses 15 | /// 16 | /// 17 | /// 18 | ISelectBuilder Select(FormattableString column); 19 | 20 | /// 21 | /// Adds one or more columns to the select clauses 22 | /// 23 | ISelectBuilder Select(params FormattableString[] moreColumns); 24 | 25 | /// 26 | /// Adds one column to the select clauses, and defines that query is a SELECT DISTINCT type 27 | /// 28 | ISelectDistinctBuilder SelectDistinct(FormattableString select); 29 | 30 | /// 31 | /// Adds one or more columns to the select clauses, and defines that query is a SELECT DISTINCT type 32 | /// 33 | ISelectDistinctBuilder SelectDistinct(params FormattableString[] moreColumns); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/InterpolatedSql/SqlBuilders/FluentQueryBuilder/IFluentQueryBuilder.cs: -------------------------------------------------------------------------------- 1 | namespace InterpolatedSql.SqlBuilders.FluentQueryBuilder 2 | { 3 | /// 4 | /// Any class that provides a fluent query builder. 5 | /// 6 | /// The concrete type that implements this interface. Useful for Fluent APIs 7 | public interface IFluentQueryBuilder : 8 | IEmptyQueryBuilder, 9 | ISelectBuilder, 10 | ISelectDistinctBuilder, 11 | IFromBuilder, 12 | IWhereBuilder, 13 | IGroupByBuilder, 14 | IGroupByHavingBuilder, 15 | IOrderByBuilder, 16 | ICompleteBuilder 17 | 18 | where U : IFluentQueryBuilder 19 | where RB : ISqlBuilder 20 | where R: class, IInterpolatedSql 21 | { 22 | } 23 | } -------------------------------------------------------------------------------- /src/InterpolatedSql/SqlBuilders/FluentQueryBuilder/IFromBuilder.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace InterpolatedSql.SqlBuilders.FluentQueryBuilder 4 | { 5 | /// 6 | /// Query Builder with one or more from clauses, which can still add more clauses to from 7 | /// 8 | public interface IFromBuilder 9 | where U : IFluentQueryBuilder 10 | where RB : ISqlBuilder 11 | where R: class, IInterpolatedSql 12 | { 13 | /// 14 | /// Adds one column to the select clauses 15 | /// 16 | ISelectBuilder Select(FormattableString column); 17 | 18 | /// 19 | /// Adds one or more columns to the select clauses 20 | /// 21 | ISelectBuilder Select(params FormattableString[] moreColumns); 22 | 23 | /// 24 | /// Adds a new table to from clauses.
25 | /// "FROM" word is optional.
26 | /// You can add an alias after table name.
27 | /// You can also add INNER JOIN, LEFT JOIN, etc (with the matching conditions). 28 | ///
29 | IFromBuilder From(FormattableString from); 30 | 31 | /// 32 | /// Adds a new group of conditions to where clauses. 33 | /// 34 | IWhereBuilder Where(Filter filter); 35 | 36 | /// 37 | /// Adds a new condition to where clauses. 38 | /// 39 | IWhereBuilder Where(Filters filter); 40 | 41 | /// 42 | /// Adds a new condition to where clauses.
43 | /// Parameters embedded using string-interpolation will be automatically converted into Dapper parameters. 44 | ///
45 | IWhereBuilder Where(FormattableString filter); 46 | 47 | /// 48 | /// Adds a new condition to groupby clauses. 49 | /// 50 | IGroupByBuilder GroupBy(FormattableString groupBy); 51 | 52 | /// 53 | /// Adds one or more column(s) to orderby clauses. 54 | /// 55 | IOrderByBuilder OrderBy(FormattableString orderBy); 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /src/InterpolatedSql/SqlBuilders/FluentQueryBuilder/IGroupByBuilder.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace InterpolatedSql.SqlBuilders.FluentQueryBuilder 4 | { 5 | /// 6 | /// Query Builder with one or more groupby clauses, which can still add more clauses to groupby 7 | /// 8 | public interface IGroupByBuilder 9 | where U : IFluentQueryBuilder 10 | where RB : ISqlBuilder 11 | where R: class, IInterpolatedSql 12 | { 13 | /// 14 | /// Adds one or more column(s) to groupby clauses. 15 | /// 16 | IGroupByBuilder GroupBy(FormattableString groupBy); 17 | 18 | /// 19 | /// Adds one or more condition(s) to having clauses. 20 | /// 21 | IGroupByHavingBuilder Having(FormattableString having); 22 | 23 | /// 24 | /// Adds one or more column(s) to orderby clauses. 25 | /// 26 | IOrderByBuilder OrderBy(FormattableString orderBy); 27 | 28 | /// 29 | /// Creates final query 30 | /// 31 | /// 32 | R Build(); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/InterpolatedSql/SqlBuilders/FluentQueryBuilder/IGroupByHavingBuilder.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace InterpolatedSql.SqlBuilders.FluentQueryBuilder 4 | { 5 | /// 6 | /// Query Builder with one or more having clauses, which can still add more clauses to having 7 | /// 8 | public interface IGroupByHavingBuilder 9 | where U : IFluentQueryBuilder 10 | where RB : ISqlBuilder 11 | where R: class, IInterpolatedSql 12 | { 13 | 14 | /// 15 | /// Adds a new condition to having clauses. 16 | /// 17 | /// 18 | /// 19 | IGroupByHavingBuilder Having(FormattableString having); 20 | 21 | /// 22 | /// Adds one column to orderby clauses. 23 | /// 24 | IOrderByBuilder OrderBy(FormattableString orderBy); 25 | 26 | /// 27 | /// Creates final query 28 | /// 29 | /// 30 | R Build(); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/InterpolatedSql/SqlBuilders/FluentQueryBuilder/IOrderByBuilder.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace InterpolatedSql.SqlBuilders.FluentQueryBuilder 4 | { 5 | /// 6 | /// Query Builder with one or more orderby clauses, which can still add more clauses to orderby 7 | /// 8 | public interface IOrderByBuilder 9 | where U : IFluentQueryBuilder 10 | where RB : ISqlBuilder 11 | where R: class, IInterpolatedSql 12 | { 13 | /// 14 | /// Adds one column to orderby clauses. 15 | /// 16 | IOrderByBuilder OrderBy(FormattableString column); 17 | 18 | /// 19 | /// Adds offset and rowcount clauses 20 | /// 21 | ICompleteBuilder Limit(int offset, int rowCount); 22 | 23 | /// 24 | /// Creates final query 25 | /// 26 | /// 27 | R Build(); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/InterpolatedSql/SqlBuilders/FluentQueryBuilder/ISelectBuilder.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace InterpolatedSql.SqlBuilders.FluentQueryBuilder 4 | { 5 | /// 6 | /// Query Builder which is preparing a SELECT statement 7 | /// 8 | public interface ISelectBuilder 9 | where U : IFluentQueryBuilder 10 | where RB : ISqlBuilder 11 | where R: class, IInterpolatedSql 12 | { 13 | /// 14 | /// Adds one column to the select clauses 15 | /// 16 | ISelectBuilder Select(FormattableString column); 17 | 18 | /// 19 | /// Adds one or more columns to the select clauses 20 | /// 21 | ISelectBuilder Select(params FormattableString[] moreColumns); 22 | 23 | /// 24 | /// Adds a new table to from clauses.
25 | /// "FROM" word is optional.
26 | /// You can add an alias after table name.
27 | /// You can also add INNER JOIN, LEFT JOIN, etc (with the matching conditions). 28 | ///
29 | IFromBuilder From(FormattableString from); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/InterpolatedSql/SqlBuilders/FluentQueryBuilder/ISelectDistinctBuilder.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace InterpolatedSql.SqlBuilders.FluentQueryBuilder 4 | { 5 | /// 6 | /// QueryBuilder which is preparing a SELECT DISTINCT statement 7 | /// 8 | public interface ISelectDistinctBuilder 9 | where U : IFluentQueryBuilder 10 | where RB : ISqlBuilder 11 | where R: class, IInterpolatedSql 12 | { 13 | /// 14 | /// Adds one column to the select clauses, and defines that query is a SELECT DISTINCT type 15 | /// 16 | ISelectDistinctBuilder SelectDistinct(FormattableString select); 17 | 18 | /// 19 | /// Adds one or more columns to the select clauses, and defines that query is a SELECT DISTINCT type 20 | /// 21 | ISelectDistinctBuilder SelectDistinct(params FormattableString[] moreColumns); 22 | 23 | 24 | /// 25 | /// Adds a new table to from clauses.
26 | /// "FROM" word is optional.
27 | /// You can add an alias after table name.
28 | /// You can also add INNER JOIN, LEFT JOIN, etc (with the matching conditions). 29 | ///
30 | IFromBuilder From(FormattableString from); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/InterpolatedSql/SqlBuilders/FluentQueryBuilder/IWhereBuilder.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace InterpolatedSql.SqlBuilders.FluentQueryBuilder 4 | { 5 | /// 6 | /// Query Builder with one or more clause in where, which can still add more clauses to where 7 | /// 8 | public interface IWhereBuilder 9 | where U : IFluentQueryBuilder 10 | where RB : ISqlBuilder 11 | where R: class, IInterpolatedSql 12 | { 13 | /// 14 | /// Adds a new condition to where clauses. 15 | /// 16 | IWhereBuilder Where(Filter filter); 17 | 18 | /// 19 | /// Adds a new condition to where clauses. 20 | /// 21 | IWhereBuilder Where(Filters filter); 22 | 23 | /// 24 | /// Adds a new condition to where clauses.
25 | /// Parameters embedded using string-interpolation will be automatically converted into Dapper parameters. 26 | ///
27 | IWhereBuilder Where(FormattableString filter); 28 | 29 | /// 30 | /// Adds a new condition to groupby clauses. 31 | /// 32 | IGroupByBuilder GroupBy(FormattableString groupBy); 33 | 34 | /// 35 | /// Adds a new condition to orderby clauses. 36 | /// 37 | IOrderByBuilder OrderBy(FormattableString orderBy); 38 | 39 | /// 40 | /// Creates final query 41 | /// 42 | /// 43 | R Build(); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/InterpolatedSql/SqlBuilders/IBuildable.cs: -------------------------------------------------------------------------------- 1 | namespace InterpolatedSql 2 | { 3 | /// 4 | /// Classes implementing can build SQL statements and return type 5 | /// 6 | public interface IBuildable 7 | where R: class, IInterpolatedSql 8 | { 9 | /// 10 | /// Dynamically build SQL 11 | /// 12 | R Build(); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/InterpolatedSql/SqlBuilders/IInterpolatedSqlBuilderBase.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Data; 3 | 4 | namespace InterpolatedSql.SqlBuilders 5 | { 6 | /// 7 | public interface IInterpolatedSqlBuilderBase 8 | { 9 | /// 10 | bool AutoSpacing { get; set; } 11 | 12 | /// 13 | string Format { get; } 14 | 15 | /// 16 | bool IsEmpty { get; } 17 | 18 | /// 19 | InterpolatedSqlBuilderOptions Options { get; set; } 20 | 21 | /// 22 | void AddObjectProperties(object obj); 23 | 24 | /// 25 | void AddParameter(SqlParameterInfo parameterInfo); 26 | 27 | /// 28 | void AddParameter(string parameterName, object? parameterValue = null, DbType? dbType = null, ParameterDirection? direction = null, int? size = null, byte? precision = null, byte? scale = null); 29 | 30 | /// 31 | void Append(IInterpolatedSql value); 32 | 33 | /// 34 | void AppendArgument(object? argument, int alignment = 0, string? format = null); 35 | 36 | 37 | /// 38 | void AppendFormattableString(FormattableString value); 39 | 40 | /// 41 | void AppendIf(bool condition, IInterpolatedSql value); 42 | 43 | /// 44 | void AppendLine(); 45 | 46 | /// 47 | void AppendLine(IInterpolatedSql value); 48 | 49 | /// 50 | void AppendLiteral(string value); 51 | 52 | /// 53 | void AppendLiteral(string value, int startIndex, int count); 54 | 55 | /// 56 | void AppendRaw(string value); 57 | #if NET6_0_OR_GREATER 58 | /// 59 | void Append([System.Runtime.CompilerServices.InterpolatedStringHandlerArgument("")] ref InterpolatedSqlHandler value); 60 | 61 | /// 62 | void AppendIf(bool condition, [System.Runtime.CompilerServices.InterpolatedStringHandlerArgument(new[] { "", "condition" })] ref InterpolatedSqlHandler value); 63 | 64 | /// 65 | void AppendLine([System.Runtime.CompilerServices.InterpolatedStringHandlerArgument("")] ref InterpolatedSqlHandler value); 66 | #else 67 | /// 68 | void Append(FormattableString value); 69 | 70 | /// 71 | void AppendIf(bool condition, FormattableString value); 72 | 73 | /// 74 | void AppendLine(FormattableString value); 75 | #endif 76 | 77 | 78 | /// 79 | IInterpolatedSql AsSql(); 80 | 81 | /// 82 | FormattableString AsFormattableString(); 83 | 84 | /// 85 | int IndexOf(string value, bool ignoreCase = false); 86 | 87 | 88 | /// 89 | int IndexOf(string value, int startIndex, bool ignoreCase); 90 | 91 | /// 92 | void Insert(int index, FormattableString value); 93 | /// 94 | void Insert(int index, IInterpolatedSql value); 95 | 96 | /// 97 | void InsertLiteral(int index, string value); 98 | 99 | /// 100 | void Remove(int startIndex, int length); 101 | 102 | /// 103 | void Replace(string oldValue, FormattableString newValue); 104 | 105 | /// 106 | void Replace(string oldValue, FormattableString newValue, out bool replaced); 107 | 108 | /// 109 | void Replace(string oldValue, IInterpolatedSql newValue); 110 | 111 | /// 112 | void Replace(string oldValue, IInterpolatedSql newValue, out bool replaced); 113 | 114 | 115 | /// 116 | string ToString(); 117 | 118 | /// 119 | void TrimEnd(); 120 | } 121 | } -------------------------------------------------------------------------------- /src/InterpolatedSql/SqlBuilders/ISqlEnricher.cs: -------------------------------------------------------------------------------- 1 | using InterpolatedSql.SqlBuilders; 2 | 3 | namespace InterpolatedSql 4 | { 5 | /// 6 | /// Classes implementing can enrich/augment on top of a SQL builder (or build a whole new statament) applying custom logic 7 | /// Usually those classes have an underlying SqlBuilder which is stores an initial template, and then there are some commands where users can fill the template blanks, 8 | /// and the will combine all inputs returning a single Sql. 9 | /// 10 | public interface ISqlEnricher 11 | { 12 | /// 13 | /// Dynamically build SQL 14 | /// 15 | IInterpolatedSql GetEnrichedSql(); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/InterpolatedSql/SqlBuilders/InsertUpdateBuilder/IInsertUpdateBuilder.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace InterpolatedSql.SqlBuilders.InsertUpdateBuilder 4 | { 5 | /// 6 | public interface IInsertUpdateBuilder : IBuildable 7 | where U : IInsertUpdateBuilder, IBuildable 8 | where RB : IInterpolatedSqlBuilderBase, IBuildable 9 | where R : class, IInterpolatedSql 10 | { 11 | U AddColumn(string columnName, object value, bool includeInInsert = true, bool includeInUpdate = true); 12 | #if NET6_0_OR_GREATER 13 | U AddColumn(string columnName, ref InterpolatedSqlHandler value, bool includeInInsert = true, bool includeInUpdate = true); 14 | R GetUpdateSql(ref InterpolatedSqlHandler whereCondition); 15 | #else 16 | U AddColumn(string columnName, FormattableString value, bool includeInInsert = true, bool includeInUpdate = true); 17 | R GetUpdateSql(FormattableString whereCondition); 18 | #endif 19 | R GetInsertSql(); 20 | } 21 | } -------------------------------------------------------------------------------- /src/InterpolatedSql/SqlBuilders/InsertUpdateBuilder/InsertUpdateBuilder.cs: -------------------------------------------------------------------------------- 1 | namespace InterpolatedSql.SqlBuilders.InsertUpdateBuilder 2 | { 3 | /// 4 | public class InsertUpdateBuilder : InsertUpdateBuilder, IInterpolatedSql> 5 | { 6 | /// 7 | public InsertUpdateBuilder(string tableName) : base(tableName, opts => new SqlBuilder(opts)) 8 | { 9 | } 10 | 11 | } 12 | 13 | } 14 | -------------------------------------------------------------------------------- /src/InterpolatedSql/SqlBuilders/InsertUpdateBuilder/InsertUpdateBuilder{U,RB,R}.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | 5 | namespace InterpolatedSql.SqlBuilders.InsertUpdateBuilder 6 | { 7 | /// 8 | /// InsertUpdateBuilder expects a list of columns and their respective values, and can build either a INSERT or an UPDATE statement. 9 | /// 10 | public abstract class InsertUpdateBuilder : SqlBuilder, IBuildable, IInsertUpdateBuilder 11 | where U : IInsertUpdateBuilder, ISqlBuilder, IBuildable 12 | where RB : IInterpolatedSqlBuilderBase, IBuildable 13 | where R : class, IInterpolatedSql 14 | { 15 | public class ColumnValue 16 | { 17 | public string ColumnName { get; set; } 18 | public IInterpolatedSql Value { get; set; } 19 | public bool IncludeInInsert { get; set; } 20 | public bool IncludeInUpdate { get; set; } 21 | } 22 | #region Members 23 | protected readonly string _tableName; 24 | protected readonly List _columnValues = new(); 25 | protected readonly Func _combinedBuilderFactory; 26 | #endregion 27 | 28 | #region ctors 29 | /// 30 | /// New empty InsertUpdateBuilder.
31 | /// Query should be built using .Append(), .AppendLine(), or .Where().
32 | /// Parameters embedded using string-interpolation will be automatically captured into . 33 | /// Where filters will later replace /**where**/ keyword 34 | ///
35 | protected InsertUpdateBuilder(string tableName, Func combinedBuilderFactory) : base() 36 | { 37 | _tableName = tableName; 38 | _combinedBuilderFactory = combinedBuilderFactory; 39 | } 40 | #endregion 41 | 42 | /// 43 | /// Registers a column and the respective value 44 | /// 45 | public U AddColumn(string columnName, object value, bool includeInInsert = true, bool includeInUpdate = true) 46 | { 47 | var builder = SqlBuilderFactory.Default.Create(); 48 | builder.AppendArgument(value); 49 | _columnValues.Add(new ColumnValue() 50 | { 51 | ColumnName = columnName, 52 | Value = builder.AsSql(), 53 | IncludeInInsert = includeInInsert, 54 | IncludeInUpdate = includeInUpdate 55 | }); 56 | return (U)(object)this; 57 | } 58 | #if NET6_0_OR_GREATER 59 | /// 60 | /// Registers a column and the respective value 61 | /// 62 | public U AddColumn(string columnName, ref InterpolatedSqlHandler value, bool includeInInsert = true, bool includeInUpdate = true) 63 | { 64 | _columnValues.Add(new ColumnValue() 65 | { 66 | ColumnName = columnName, 67 | Value = value.InterpolatedSqlBuilder.AsSql(), 68 | IncludeInInsert = includeInInsert, 69 | IncludeInUpdate = includeInUpdate 70 | }); 71 | return (U)(object)this; 72 | } 73 | #else 74 | /// 75 | /// Registers a column and the respective value 76 | /// 77 | public U AddColumn(string columnName, FormattableString value, bool includeInInsert = true, bool includeInUpdate = true) 78 | { 79 | _columnValues.Add(new ColumnValue() 80 | { 81 | ColumnName = columnName, 82 | Value = SqlBuilderFactory.Default.Create(value).AsSql(), 83 | IncludeInInsert = includeInInsert, 84 | IncludeInUpdate = includeInUpdate 85 | }); ; 86 | return (U)(object)this; 87 | } 88 | #endif 89 | 90 | /// 91 | public virtual R GetInsertSql() 92 | { 93 | RB combinedQuery; 94 | if (_combinedBuilderFactory == null) 95 | return null!; // initializing 96 | 97 | var cols = _columnValues.Where(x => x.IncludeInInsert).ToList(); 98 | combinedQuery = _combinedBuilderFactory(InterpolatedSqlBuilderOptions.DefaultOptions); 99 | combinedQuery.AppendLiteral("INSERT INTO " + _tableName + " ("); 100 | for (int i = 0; i < cols.Count(); i++) 101 | { 102 | if (i > 0) 103 | combinedQuery.AppendLiteral(", "); 104 | combinedQuery.AppendLiteral(cols[i].ColumnName); 105 | } 106 | combinedQuery.AppendLiteral(") VALUES ("); 107 | for (int i = 0; i < cols.Count(); i++) 108 | { 109 | if (i > 0) 110 | combinedQuery.AppendLiteral(", "); 111 | combinedQuery.Append(cols[i].Value); 112 | } 113 | combinedQuery.AppendLiteral(");"); 114 | 115 | return combinedQuery.Build(); 116 | } 117 | 118 | /// 119 | public virtual R GetUpdateSql( 120 | #if NET6_0_OR_GREATER 121 | ref InterpolatedSqlHandler whereCondition 122 | #else 123 | FormattableString whereCondition 124 | #endif 125 | ) 126 | { 127 | RB combinedQuery; 128 | if (_combinedBuilderFactory == null) 129 | return null!; // initializing 130 | 131 | var cols = _columnValues.Where(x => x.IncludeInUpdate).ToList(); 132 | combinedQuery = _combinedBuilderFactory(InterpolatedSqlBuilderOptions.DefaultOptions); 133 | combinedQuery.AppendLiteral("UPDATE " + _tableName + " SET "); 134 | for (int i = 0; i < cols.Count(); i++) 135 | { 136 | if (i > 0) 137 | combinedQuery.AppendLiteral(", "); 138 | combinedQuery.AppendLiteral(cols[i].ColumnName); 139 | combinedQuery.AppendLiteral("="); 140 | combinedQuery.Append(cols[i].Value); 141 | } 142 | 143 | #if NET6_0_OR_GREATER 144 | if (whereCondition.InterpolatedSqlBuilder.Format.Length > 0 && !char.IsWhiteSpace(whereCondition.InterpolatedSqlBuilder.Format[0])) 145 | combinedQuery.AppendLiteral(" "); 146 | if (!whereCondition.InterpolatedSqlBuilder.Format.Trim().StartsWith("WHERE")) 147 | combinedQuery.AppendLiteral("WHERE "); 148 | combinedQuery.Append(whereCondition.InterpolatedSqlBuilder.AsSql()); 149 | #else 150 | if (whereCondition.Format.Length > 0 && !char.IsWhiteSpace(whereCondition.Format[0])) 151 | combinedQuery.AppendLiteral(" "); 152 | if (!whereCondition.Format.Trim().StartsWith("WHERE")) 153 | combinedQuery.AppendLiteral("WHERE "); 154 | combinedQuery.Append(whereCondition); 155 | #endif 156 | 157 | return combinedQuery.Build(); 158 | } 159 | 160 | public override R Build() 161 | { 162 | throw new NotImplementedException("Should use GetUpdateSql() or GetInsertSql()"); 163 | } 164 | } 165 | } 166 | -------------------------------------------------------------------------------- /src/InterpolatedSql/SqlBuilders/InterpolatedSqlBuilderOptions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | 4 | namespace InterpolatedSql.SqlBuilders 5 | { 6 | /// 7 | /// Settings for InterpolatedSqlBuilder 8 | /// 9 | public class InterpolatedSqlBuilderOptions 10 | { 11 | #region Members 12 | /// 13 | /// All methods that take a will use this Parser to parse the FormattableString. 14 | /// By default it's 15 | /// 16 | public IInterpolatedSqlParser Parser { get; set; } = new InterpolatedSqlParser(); 17 | 18 | /// 19 | /// If true (default is true) then all curly braces are escaped (with double curly braces). 20 | /// By default uses indexed placeholders (numbered placeholders like "{0}", "{1}", etc.) to indicate the arguments, 21 | /// and so does . 22 | /// If your derived type does NOT need those indexed placeholders (e.g. you're writing your own format and do not need the standard one) or 23 | /// if your derived type will never write curly braces through , 24 | /// then you can disable this (set to false) so that it won't escape curly braces (and it will be faster). 25 | /// You can also use ) which does not escape anything. 26 | /// 27 | internal bool AutoEscapeCurlyBraces { get; set; } = true; 28 | 29 | /// 30 | /// Argument Formatting (what comes after colon, like $"My string {val:000}") is always extracted into . 31 | /// If this is true (default is true) then this formatting will also be preserved in the underlying 32 | /// (else, will only have the numeric placeholder like {0} but without the extracted formatting). 33 | /// 34 | internal bool PreserveArgumentFormatting { get; set; } = false; 35 | 36 | /// 37 | /// If true (default is false) each added parameter will check if identical parameter (same type and value) 38 | /// was already added, and if so will reuse the existing parameter. 39 | /// 40 | public bool ReuseIdenticalParameters { get; set; } = false; 41 | 42 | /// 43 | /// Compares two arguments for reusing. Only used if is true. 44 | /// By default it's 45 | /// 46 | public IEqualityComparer ArgumentComparer { get; set; } = new InterpolatedSqlParameterComparer(); 47 | 48 | /// 49 | /// In the rendered SQL statement the parameters by default are named like @p0, @p1, etc.
50 | /// You can change the name p0/p1/etc to any other prfix.
51 | /// Example: if you set to "arg" you'll get @arg0, @arg1, etc.
52 | ///
53 | public string AutoGeneratedParameterPrefix { get; set; } = "p"; 54 | 55 | /// 56 | /// String that is appended to the parameter name for enumerable types to avoid name conflicts. 57 | /// 58 | public string ParameterArrayNameSuffix { get; set; } = "array"; 59 | 60 | /// 61 | /// In the rendered SQL statement the parameters by default are named like @p0, @p1, etc.
62 | /// If your database does not accept @ symbol you can change for any other symbol.
63 | /// For Oracle you should use ":"
64 | ///
65 | public string DatabaseParameterSymbol { get; set; } = "@"; 66 | 67 | 68 | /// 69 | /// If true (default is true), single quotes surrounding interpolated arguments will be automatically stripped 70 | /// 71 | public bool AutoFixSingleQuotes { get; set; } = true; 72 | 73 | /// 74 | /// If not null (default), then spaces will be automatically added if there's no whitespace (or linebreak) between last text and the new text. 75 | /// This assume that a single SQL statement (or identifier, or value) will always be appended in a single statement (why would anyone split a single sql word in 2 appends, right?) 76 | /// To disable auto spacing just set this to null 77 | /// 78 | public AutoSpacingOptions AutoSpacingOptions { get; set; } = new AutoSpacingOptions(); 79 | 80 | 81 | /// 82 | /// This is for legacy compatibility - it adjusts blocks similarly to what is currently available with C#11 Raw String Literals 83 | /// 84 | /// 85 | public bool AutoAdjustMultilineString { get; set; } = false; 86 | 87 | 88 | /// 89 | /// Given a parameter name, how it should be formatted when the SQL statement is generated (e.g. add "@" before name for MSSQL, or add ":" for Oracle, etc.) 90 | /// 91 | public Func FormatParameterName { get; set; } 92 | 93 | /// 94 | /// Given the argument (object) and it's index position (int), this defines how the auto generated parameter name should be named (e.g. add "p" before position) 95 | /// 96 | public Func CalculateAutoParameterName { get; set; } 97 | #endregion 98 | 99 | /// 100 | public InterpolatedSqlBuilderOptions() 101 | { 102 | FormatParameterName = (parameterName) => DatabaseParameterSymbol + parameterName; 103 | CalculateAutoParameterName = (parameter, pos) => AutoGeneratedParameterPrefix + pos.ToString(); 104 | } 105 | 106 | /// 107 | /// Default options used when options are not defined in constructor. 108 | /// 109 | public static InterpolatedSqlBuilderOptions DefaultOptions { get; set; } = new InterpolatedSqlBuilderOptions(); 110 | 111 | } 112 | 113 | #region AutoSpacingOptions 114 | /// 115 | /// AutoSpacing 116 | /// 117 | public class AutoSpacingOptions 118 | { 119 | /// 120 | /// Symbols that can isolate SQL elements. If there is any of these chars before or after the element being written then autospacing will NOT add space automatically. 121 | /// 122 | public HashSet SeparatorSymbols { get; set; } = new HashSet() 123 | { 124 | ',', 125 | ';', 126 | '=', 127 | '>', 128 | '<', 129 | '+', 130 | '-', 131 | '*', 132 | '/', 133 | '^', 134 | '%', 135 | '\'', 136 | }; 137 | 138 | /// 139 | /// Symbols that can close a SQL block, and therefore auto spacing do NOT need to add space before them 140 | /// 141 | public HashSet ClosingSymbols { get; set; } = new HashSet() 142 | { 143 | ')', 144 | }; 145 | 146 | /// 147 | /// Symbols that can open a SQL block, and therefore auto spacing do NOT need to add space after them 148 | /// 149 | public HashSet OpeningSymbols { get; set; } = new HashSet() 150 | { 151 | '(', 152 | }; 153 | 154 | } 155 | #endregion 156 | 157 | } 158 | -------------------------------------------------------------------------------- /src/InterpolatedSql/SqlBuilders/QueryBuilder/Filter.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace InterpolatedSql.SqlBuilders 4 | { 5 | /// 6 | /// Filter statement defined in a single statement
7 | /// It can include multiple conditions (if defined in a single statement during constructor),
8 | /// but usually this is used as one condition (one column, one comparison operator, and one parameter). 9 | ///
10 | public class Filter : SqlBuilder, IFilter 11 | { 12 | /// 13 | /// New Filter statement.
14 | /// Example: $"[CategoryId] = {categoryId}"
15 | /// Example: $"[Name] LIKE {productName}" 16 | ///
17 | public Filter(FormattableString filter) : base(filter) 18 | { 19 | } 20 | 21 | /// 22 | public void WriteTo(IInterpolatedSqlBuilderBase sb) 23 | { 24 | sb.Append(this.Build()); 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/InterpolatedSql/SqlBuilders/QueryBuilder/Filters.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Diagnostics; 4 | using System.Linq; 5 | 6 | namespace InterpolatedSql.SqlBuilders 7 | { 8 | /// 9 | /// Multiple Filter statements which are grouped together. Can be grouped with ANDs or ORs. 10 | /// 11 | [DebuggerDisplay("{DebuggerDisplay,nq}")] 12 | public class Filters : List, IFilter 13 | { 14 | #region Members 15 | /// 16 | /// By default Filter Groups are combined with AND operator. But you can use OR. 17 | /// 18 | public FiltersType Type { get; set; } = FiltersType.AND; 19 | 20 | /// 21 | /// How a list of Filters are combined (AND operator or OR operator) 22 | /// 23 | public enum FiltersType 24 | { 25 | /// 26 | /// AND 27 | /// 28 | AND, 29 | 30 | /// 31 | /// OR 32 | /// 33 | OR 34 | } 35 | #endregion 36 | 37 | #region ctor 38 | 39 | /// 40 | /// Create a new group of filters. 41 | /// 42 | public Filters(FiltersType type, IEnumerable filters) 43 | { 44 | Type = type; 45 | this.AddRange(filters); 46 | } 47 | 48 | /// 49 | /// Create a new group of filters which are combined with AND operator. 50 | /// 51 | public Filters(IEnumerable filters): this(FiltersType.AND, filters) 52 | { 53 | } 54 | 55 | /// 56 | /// Create a new group of filters from formattable strings 57 | /// 58 | public Filters(FiltersType type, params FormattableString[] filters): 59 | this(type, filters.Select(fiString => new Filter(fiString))) 60 | { 61 | } 62 | 63 | /// 64 | /// Create a new group of filters from formattable strings which are combined with AND operator. 65 | /// 66 | public Filters(params FormattableString[] filters) : this(FiltersType.AND, filters) 67 | { 68 | } 69 | #endregion 70 | 71 | #region IFilter 72 | /// 73 | public void WriteTo(IInterpolatedSqlBuilderBase sb) 74 | { 75 | //if (this.Count() > 1) //TODO: EncloseMultipleConditions to wrap first-level conditions around parentheses 76 | // sb.Append("("); 77 | for (int i = 0; i < this.Count(); i++) 78 | { 79 | if (i > 0 && Type == FiltersType.AND) 80 | sb.AppendLiteral(" AND "); 81 | else if (i > 0 && Type == FiltersType.OR) 82 | sb.AppendLiteral(" OR "); 83 | IFilter filter = this[i]; 84 | if (filter is Filters && ((Filters)filter).Count() > 1) // only put brackets in groups after the first level 85 | { 86 | sb.AppendLiteral("("); 87 | filter.WriteTo(sb); 88 | sb.AppendLiteral(")"); 89 | } 90 | else 91 | filter.WriteTo(sb); 92 | } 93 | //if (this.Count() > 1) 94 | // sb.Append(")"); 95 | } 96 | 97 | /// 98 | public IInterpolatedSql Build() 99 | { 100 | var command = SqlBuilderFactory.Default.Create(); 101 | WriteTo(command); 102 | if (!command.IsEmpty && !command.Format.StartsWith("WHERE ")) 103 | command.InsertLiteral(0, "WHERE "); 104 | return command.AsSql(); 105 | } 106 | 107 | private string DebuggerDisplay 108 | { 109 | get 110 | { 111 | var sb = SqlBuilderFactory.Default.Create(); 112 | sb.AppendLiteral("("); 113 | sb.AppendLiteral(this.Count().ToString()); 114 | sb.AppendLiteral(" filters): "); 115 | WriteTo(sb); 116 | return sb.ToString(); 117 | } 118 | } 119 | #endregion 120 | 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /src/InterpolatedSql/SqlBuilders/QueryBuilder/IFilter.cs: -------------------------------------------------------------------------------- 1 | namespace InterpolatedSql.SqlBuilders 2 | { 3 | /// 4 | /// Can be both individual filter or a list of filters. 5 | /// 6 | public interface IFilter 7 | { 8 | /// 9 | /// Writes the filter(s) (both SQL Statement and SqlParameters) into an existing InterpolatedSqlBuilder 10 | /// 11 | void WriteTo(IInterpolatedSqlBuilderBase sb); 12 | 13 | /// 14 | /// If you're using Filters in standalone mode (without QueryBuilder),
15 | /// you can just "build" the filters to get the string for the filters (with leading WHERE) and get the SqlParameters 16 | ///
17 | IInterpolatedSql Build(); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/InterpolatedSql/SqlBuilders/QueryBuilder/IQueryBuilder.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace InterpolatedSql.SqlBuilders 4 | { 5 | /// 6 | public interface IQueryBuilder : IBuildable 7 | where U : IQueryBuilder, IBuildable 8 | where RB : IInterpolatedSqlBuilderBase, IBuildable 9 | where R : class, IInterpolatedSql 10 | { 11 | Filters.FiltersType FiltersType { get; set; } 12 | 13 | IInterpolatedSqlBuilderBase? GetFilters(); 14 | U Where(Filter filter); 15 | U Where(Filters filters); 16 | U Where(FormattableString filter); 17 | #if NET6_0_OR_GREATER 18 | U From(ref InterpolatedSqlHandler fromString); 19 | U GroupBy(ref InterpolatedSqlHandler selectString); 20 | U Having(ref InterpolatedSqlHandler selectString); 21 | U OrderBy(ref InterpolatedSqlHandler selectString); 22 | U Select(ref InterpolatedSqlHandler selectString); 23 | #else 24 | U From(FormattableString fromString); 25 | U GroupBy(FormattableString selectString); 26 | U Having(FormattableString selectString); 27 | U OrderBy(FormattableString selectString); 28 | U Select(FormattableString selectString); 29 | #endif 30 | } 31 | } -------------------------------------------------------------------------------- /src/InterpolatedSql/SqlBuilders/QueryBuilder/QueryBuilder.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Data; 3 | 4 | namespace InterpolatedSql.SqlBuilders 5 | { 6 | /// 7 | public class QueryBuilder : QueryBuilder, IInterpolatedSql> 8 | { 9 | /// 10 | public QueryBuilder() : base(opts => new SqlBuilder(opts), (opts, format, arguments) => new SqlBuilder(opts, format, arguments)) 11 | { 12 | } 13 | 14 | /// 15 | public QueryBuilder(IDbConnection connection) : this() 16 | { 17 | DbConnection = connection; 18 | } 19 | 20 | /// 21 | public QueryBuilder(FormattableString query) : base(opts => new SqlBuilder(opts), (opts, format, arguments) => new SqlBuilder(opts, format, arguments), query) 22 | { 23 | } 24 | 25 | /// 26 | public QueryBuilder(IDbConnection connection, FormattableString query) : this(query) 27 | { 28 | DbConnection = connection; 29 | } 30 | 31 | } 32 | 33 | } 34 | -------------------------------------------------------------------------------- /src/InterpolatedSql/SqlBuilders/SqlBuilder/ISqlBuilder.cs: -------------------------------------------------------------------------------- 1 | namespace InterpolatedSql.SqlBuilders 2 | { 3 | /// 4 | public interface ISqlBuilder : ISqlBuilder 5 | { 6 | } 7 | } -------------------------------------------------------------------------------- /src/InterpolatedSql/SqlBuilders/SqlBuilder/ISqlBuilder{U,R}.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Data; 3 | 4 | namespace InterpolatedSql.SqlBuilders 5 | { 6 | /// 7 | public interface ISqlBuilder : IInterpolatedSqlBuilderBase, IBuildable 8 | where U : ISqlBuilder 9 | where R: class, IInterpolatedSql 10 | { 11 | /// 12 | new U AddParameter(SqlParameterInfo parameterInfo); 13 | 14 | /// 15 | new U AddParameter(string parameterName, object? parameterValue = null, DbType? dbType = null, ParameterDirection? direction = null, int? size = null, byte? precision = null, byte? scale = null); 16 | 17 | /// 18 | new U Append(IInterpolatedSql value); 19 | 20 | /// 21 | new U AppendFormattableString(FormattableString value); 22 | 23 | /// 24 | new U AppendIf(bool condition, IInterpolatedSql value); 25 | 26 | 27 | /// 28 | new U AppendLine(); 29 | 30 | /// 31 | new U AppendLine(IInterpolatedSql value); 32 | 33 | /// 34 | new U AppendLiteral(string value); 35 | 36 | /// 37 | new U AppendLiteral(string value, int startIndex, int count); 38 | 39 | /// 40 | new U AppendRaw(string value); 41 | 42 | 43 | #if NET6_0_OR_GREATER 44 | /// 45 | new U Append([System.Runtime.CompilerServices.InterpolatedStringHandlerArgument("")] ref InterpolatedSqlHandler value); 46 | 47 | /// 48 | new U AppendIf(bool condition, [System.Runtime.CompilerServices.InterpolatedStringHandlerArgument(new[] { "", "condition" })] ref InterpolatedSqlHandler value); 49 | 50 | /// 51 | new U AppendLine(ref InterpolatedSqlHandler value); 52 | #else 53 | /// 54 | new U Append(FormattableString value); 55 | /// 56 | new U AppendIf(bool condition, FormattableString value); 57 | /// 58 | new U AppendLine(FormattableString value); 59 | #endif 60 | 61 | /// 62 | new U Insert(int index, IInterpolatedSql value); 63 | 64 | /// 65 | new U Insert(int index, FormattableString value); 66 | 67 | /// 68 | new U InsertLiteral(int index, string value); 69 | 70 | /// 71 | new U Remove(int startIndex, int length); 72 | 73 | /// 74 | new U TrimEnd(); 75 | 76 | /// 77 | new U Replace(string oldValue, IInterpolatedSql newValue, out bool replaced); 78 | 79 | /// 80 | new U Replace(string oldValue, IInterpolatedSql newValue); 81 | 82 | /// 83 | new U Replace(string oldValue, FormattableString newValue, out bool replaced); 84 | 85 | /// 86 | new U Replace(string oldValue, FormattableString newValue); 87 | } 88 | } -------------------------------------------------------------------------------- /src/InterpolatedSql/SqlBuilders/SqlBuilder/SqlBuilder.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Text; 3 | using System; 4 | 5 | namespace InterpolatedSql.SqlBuilders 6 | { 7 | /// 8 | /// This is just a simplification of InterpolatedSqlBuilder{U, R} where R is IInterpolatedSql (if you don't need a custom return type) 9 | /// and U is this same type (if you dont'need to extend the class) 10 | public class SqlBuilder : SqlBuilder 11 | { 12 | #region ctor 13 | /// 14 | protected internal SqlBuilder(InterpolatedSqlBuilderOptions? options, StringBuilder? format, List? arguments) : base(options ?? InterpolatedSqlBuilderOptions.DefaultOptions, format, arguments) 15 | { 16 | } 17 | 18 | /// 19 | public SqlBuilder() : this(options: null, format: null, arguments: null) 20 | { 21 | } 22 | 23 | /// 24 | public SqlBuilder(InterpolatedSqlBuilderOptions? options) : this(options: options, format: null, arguments: null) 25 | { 26 | } 27 | 28 | 29 | /// 30 | public SqlBuilder(FormattableString value, InterpolatedSqlBuilderOptions? options = null) : this(options: options) 31 | { 32 | // This constructor gets a FormattableString to be immediately parsed, and therefore it can be important to provide Options (and Parser) immediately together 33 | if (value != null) 34 | { 35 | Options.Parser.ParseAppend(this, value); 36 | ResetAutoSpacing(); // rearm after appending initial text 37 | } 38 | } 39 | 40 | #if NET6_0_OR_GREATER 41 | /// 42 | public SqlBuilder(int literalLength, int formattedCount, InterpolatedSqlBuilderOptions? options = null) : base(literalLength, formattedCount, options ?? InterpolatedSqlBuilderOptions.DefaultOptions) 43 | { 44 | } 45 | #endif 46 | #endregion 47 | 48 | /// 49 | public override IInterpolatedSql Build() 50 | { 51 | return AsSql(); 52 | } 53 | 54 | } 55 | } -------------------------------------------------------------------------------- /src/InterpolatedSql/SqlBuilders/SqlBuilder/SqlBuilder{U,R}.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Data; 4 | using System.Text; 5 | 6 | namespace InterpolatedSql.SqlBuilders 7 | { 8 | /// 9 | /// Dynamic SQL builder where SqlParameters are defined using string interpolation (but it's injection safe). This is the most important piece of the library. 10 | /// 11 | /// Parameters should just be embedded using interpolated objects, and they will be preserved (will not be mixed with the literals) 12 | /// and will be parametrized when you need to run the command. 13 | /// So it wraps the underlying SQL statement and the associated parameters, 14 | /// allowing to easily add new clauses to underlying statement and also add new parameters. 15 | /// 16 | /// Recursive Generics: U Should be the same class that implements InterpolatedSqlBuilder{U}, or any other interface implemented by this class 17 | /// R Should be the type that this builder builds (type returned by Build()) - should implement IInterpolatedSql 18 | /// Fluent Builder with Recursive Generics - allows Fluent API to always return the same type U 19 | public abstract class SqlBuilder : InterpolatedSqlBuilderBase, ISqlBuilder, IBuildable 20 | where U : ISqlBuilder 21 | where R : class, IInterpolatedSql 22 | { 23 | #region ctor 24 | /// 25 | protected SqlBuilder(InterpolatedSqlBuilderOptions? options, StringBuilder? format, List? arguments) : base(options ?? InterpolatedSqlBuilderOptions.DefaultOptions, format, arguments) 26 | { 27 | } 28 | 29 | /// 30 | public SqlBuilder(InterpolatedSqlBuilderOptions? options = null) : this(options: options, format: null, arguments: null) 31 | { 32 | } 33 | 34 | 35 | /// 36 | public SqlBuilder(FormattableString value, InterpolatedSqlBuilderOptions? options = null) : this(options: options) 37 | { 38 | // This constructor gets a FormattableString to be immediately parsed, and therefore it can be important to provide Options (and Parser) immediately together 39 | if (value != null) 40 | { 41 | Options.Parser.ParseAppend(this, value); 42 | ResetAutoSpacing(); // rearm after appending initial text 43 | } 44 | } 45 | 46 | #if NET6_0_OR_GREATER 47 | /// 48 | public SqlBuilder(int literalLength, int formattedCount, InterpolatedSqlBuilderOptions? options = null) : base(literalLength, formattedCount, options ?? InterpolatedSqlBuilderOptions.DefaultOptions) 49 | { 50 | } 51 | #endif 52 | #endregion 53 | 54 | 55 | } 56 | } -------------------------------------------------------------------------------- /src/InterpolatedSql/SqlBuilders/SqlBuilder/SqlBuilder{U}.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Text; 3 | using System; 4 | 5 | namespace InterpolatedSql.SqlBuilders 6 | { 7 | /// 8 | /// This is just a simplification of InterpolatedSqlBuilder{U, R} where R is IInterpolatedSql (if you don't need a custom return type) 9 | public abstract class SqlBuilder : SqlBuilder // this is just a simplification of InterpolatedSqlBuilder where R is IInterpolatedSql (if you don't need a custom return type) 10 | where U : SqlBuilder, ISqlBuilder 11 | { 12 | #region ctors 13 | /// 14 | protected internal SqlBuilder(InterpolatedSqlBuilderOptions? options, StringBuilder? format, List? arguments) : base(options, format, arguments) 15 | { 16 | } 17 | 18 | /// 19 | public SqlBuilder(InterpolatedSqlBuilderOptions? options = null) : base(options) 20 | { 21 | } 22 | 23 | 24 | /// 25 | public SqlBuilder(FormattableString value, InterpolatedSqlBuilderOptions? options = null) : base(value, options) 26 | { 27 | } 28 | 29 | #if NET6_0_OR_GREATER 30 | /// 31 | public SqlBuilder(int literalLength, int formattedCount, InterpolatedSqlBuilderOptions? options = null) : base(literalLength, formattedCount, options) 32 | { 33 | } 34 | #endif 35 | #endregion 36 | } 37 | } -------------------------------------------------------------------------------- /src/InterpolatedSql/SqlParameters/DbTypeParameterInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Data; 2 | 3 | namespace InterpolatedSql 4 | { 5 | /// 6 | /// Represents a Sql Parameter of any DbType. 7 | /// For Strings (where there are important differences like Ansi/Unicode which define if each character will ocuppy 8 | /// 9 | public class DbTypeParameterInfo : SqlParameterInfo 10 | { 11 | #region Members 12 | /// 13 | /// Parameters added through string interpolation usually do not need to define their DbType, and Dapper/ORM will automatically detect the correct type,
14 | /// but it's possible to explicitly define the DbType (which Dapper/ORM will map to corresponding type in your database) 15 | ///
16 | public DbType? DbType { get; set; } 17 | 18 | /// 19 | /// Parameters added through string interpolation usually do not need to define their Size, and Dapper/ORM will automatically detect the correct size,
20 | /// but it's possible to explicitly define the size (usually for strings, where in some specific scenarios you can get better performance by passing the exact data type) 21 | ///
22 | public int? Size { get; set; } 23 | 24 | /// 25 | /// Parameters added through string interpolation usually do not need to define this, as Dapper/ORM will automatically calculate the correct value 26 | /// 27 | public byte? Precision { get; set; } 28 | 29 | /// 30 | /// Parameters added through string interpolation usually do not need to define this, as Dapper/ORM will automatically calculate the correct value 31 | /// 32 | public byte? Scale { get; set; } 33 | #endregion 34 | 35 | #region ctors 36 | /// 37 | /// New DbTypeParameterInfo 38 | /// 39 | /// The name of the parameter. 40 | /// The value of the parameter. 41 | /// The in or out direction of the parameter. 42 | /// The type of the parameter. 43 | /// The size of the parameter. 44 | /// The precision of the parameter. 45 | /// The scale of the parameter. 46 | public DbTypeParameterInfo(string? name, object? value = null, ParameterDirection? direction = null, DbType? dbType = null, int? size = null, byte? precision = null, byte? scale = null) 47 | : base(name, value, direction) 48 | { 49 | this.DbType = dbType; 50 | this.Size = size; 51 | this.Precision = precision; 52 | this.Scale = scale; 53 | } 54 | 55 | 56 | /// 57 | /// New Parameter 58 | /// 59 | /// The name of the parameter. 60 | /// The value of the parameter. 61 | /// The type of the parameter. 62 | /// The in or out direction of the parameter. 63 | /// The size of the parameter. 64 | public DbTypeParameterInfo(string? name, object value, ParameterDirection? direction, DbType? dbType, int? size) : this(name, value, direction, dbType, size, null, null) 65 | { 66 | } 67 | 68 | #endregion 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /src/InterpolatedSql/SqlParameters/InterpolatedSqlParameter.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace InterpolatedSql 4 | { 5 | /// 6 | /// Interpolated string arguments are defined like this: $"My string {arg}", $"My string {val:000}" 7 | /// InterpolatedSqlParameter will hold both the arguments (like arg or val) in 8 | /// and if there's a format (after colon, like "000") it will be saved in (else it will be null). 9 | /// 10 | public class InterpolatedSqlParameter 11 | { 12 | /// 13 | /// Value of argument embedded into Interpolated String. Like 14 | /// 15 | public object? Argument { get; set; } //TODO: rename to Value? like DapperParameters 16 | 17 | /// 18 | /// Each argument embedded into FormattableString may have a format specifier which will be kept here. 19 | /// If there's no format specified then this will be null. 20 | /// 21 | public string? Format { get; set; } 22 | 23 | /// 24 | public InterpolatedSqlParameter(object? argument, string? format) 25 | { 26 | Argument = argument; 27 | Format = format; 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/InterpolatedSql/SqlParameters/InterpolatedSqlParameterComparer.cs: -------------------------------------------------------------------------------- 1 | using InterpolatedSql.SqlBuilders; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Diagnostics.CodeAnalysis; 5 | 6 | namespace InterpolatedSql 7 | { 8 | /// 9 | /// Compares two sql parameters for reusing duplicates. Only used if is true. 10 | /// 11 | public class InterpolatedSqlParameterComparer : IEqualityComparer 12 | { 13 | /// 14 | public bool Equals(InterpolatedSqlParameter? arg1, InterpolatedSqlParameter? arg2) 15 | { 16 | if (arg1 == null && arg2 == null) 17 | return true; 18 | if (arg1 == null || arg2 == null) 19 | return false; 20 | if (arg1.Format != arg2.Format) 21 | return false; 22 | if (arg1.Argument == null && arg2.Argument == null) 23 | return true; 24 | if (arg1.Argument == null || arg2.Argument == null) 25 | return false; 26 | if (arg1.Argument.GetType() == arg2.Argument.GetType() && arg1.Argument.Equals(arg2.Argument)) 27 | return true; 28 | return false; 29 | } 30 | 31 | /// 32 | public int GetHashCode( 33 | #if NETCOREAPP 34 | [DisallowNull] 35 | #endif 36 | InterpolatedSqlParameter obj) 37 | { 38 | return (obj.Argument?.GetHashCode() ?? 0) ^ (obj.Format?.GetHashCode() ?? 0); 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/InterpolatedSql/SqlParameters/SqlParameterInfo.cs: -------------------------------------------------------------------------------- 1 | using InterpolatedSql.SqlBuilders; 2 | using System; 3 | using System.Data; 4 | using System.Diagnostics; 5 | using System.Linq.Expressions; 6 | using System.Reflection; 7 | 8 | namespace InterpolatedSql 9 | { 10 | /// 11 | /// Generic wrapper to store information about SQL Parameters. 12 | /// Holds the underlying Value, parameter Name, and fields to support output parameters. 13 | /// This class should be mapped into something compatible with your database or ORM 14 | /// 15 | [DebuggerDisplay("{Name,nq} = {Value,nq}")] 16 | public class SqlParameterInfo 17 | { 18 | #region Members 19 | /// 20 | /// This is only used for Explicit Parameters (explicitly defined and stored in ), 21 | /// and should NOT contain database-specific prefixes like "@" or ":". 22 | /// For Implicit Parameters (captured through string interpolation and stored in ) the names 23 | /// are automatically calculated using 24 | /// 25 | public string? Name { get; set; } 26 | 27 | /// 28 | /// Value of parameter 29 | /// 30 | public object? Value { get; set; } 31 | 32 | /// 33 | /// Parameters added through string interpolation are usually input parameters (passed from C# to SQL),
34 | /// but you may explicitly describe parameters as Output, InputOutput, or ReturnValues. 35 | ///
36 | public ParameterDirection? ParameterDirection { get; set; } 37 | 38 | 39 | /// 40 | /// Output parameters (created using ) invoke this callback to set their value back to Target type 41 | /// 42 | public Action? OutputCallback { get; set; } 43 | #endregion 44 | 45 | #region ctors 46 | /// 47 | /// New Parameter 48 | /// 49 | /// The name of the parameter. 50 | /// The value of the parameter. 51 | /// The in or out direction of the parameter. 52 | public SqlParameterInfo(string? name, object? value = null, ParameterDirection? direction = null) 53 | { 54 | this.Name = name; 55 | this.Value = value; 56 | this.ParameterDirection = direction; 57 | } 58 | 59 | 60 | /// 61 | /// Creates a new Output Parameter (can be Output, InputOutput, or ReturnValue)
62 | /// and registers a callback action which (after command invocation) will populate back parameter output value into an instance property. 63 | ///
64 | /// Target variable where output value will be set. 65 | /// Property where output value will be set. If it's InputOutput type this value will be passed. 66 | /// The type of output of the parameter. 67 | public void ConfigureOutputParameter(T target, Expression> expression, OutputParameterDirection direction = OutputParameterDirection.Output) 68 | { 69 | if (ParameterDirection != null) 70 | throw new ArgumentException($"For Output-type parameters the ParameterDirection should only be set by {nameof(ConfigureOutputParameter)}"); 71 | 72 | ParameterDirection = (ParameterDirection)direction; 73 | 74 | // For InputOutput we send current value 75 | if (ParameterDirection == System.Data.ParameterDirection.InputOutput) 76 | { 77 | if (Value != null) 78 | throw new ArgumentException($"For {nameof(OutputParameterDirection.InputOutput)} the value should be set by the target expression"); 79 | Value = expression.Compile().Invoke(target); 80 | } 81 | 82 | var setter = GetSetter(expression); 83 | OutputCallback = new Action(o => { 84 | TP val; 85 | if (o is TP) 86 | val = (TP)o; 87 | else 88 | { 89 | try 90 | { 91 | val = (TP)Convert.ChangeType(o, typeof(TP)); 92 | } 93 | catch (Exception ex) 94 | { 95 | throw new Exception($"Can't convert {Name} ({Value}) to type {typeof(TP).Name}", ex); 96 | } 97 | } 98 | setter(target, val); // TP (property type) must match the return value 99 | }); 100 | } 101 | #endregion 102 | 103 | #region Enums 104 | /// 105 | /// Type of Output 106 | /// 107 | public enum OutputParameterDirection 108 | { 109 | /// 110 | /// The parameter is an output parameter. 111 | /// 112 | Output = 2, 113 | 114 | /// 115 | /// The parameter is capable of both input and output. 116 | /// 117 | InputOutput = 3, 118 | 119 | /// 120 | /// The parameter represents a return value from an operation such as a stored procedure, built-in function, or user-defined function. 121 | /// 122 | ReturnValue = 6 123 | } 124 | #endregion 125 | 126 | #region Methods 127 | /// 128 | /// Convert a lambda expression for a getter into a setter 129 | /// 130 | private static Action GetSetter(Expression> expression) 131 | { 132 | var memberExpression = (MemberExpression)expression.Body; 133 | var property = (PropertyInfo)memberExpression.Member; 134 | var setMethod = property.GetSetMethod()!; 135 | 136 | var parameterT = Expression.Parameter(typeof(T), "x"); 137 | var parameterTProperty = Expression.Parameter(typeof(TProperty), "y"); 138 | 139 | var newExpression = 140 | Expression.Lambda>( 141 | Expression.Call(parameterT, setMethod, parameterTProperty), 142 | parameterT, 143 | parameterTProperty 144 | ); 145 | 146 | return newExpression.Compile(); 147 | } 148 | 149 | /// 150 | /// Determines whether the specified object is equal to the current object. 151 | /// 152 | public override bool Equals(object obj) 153 | { 154 | if (ReferenceEquals(null, obj)) 155 | return false; 156 | if (ReferenceEquals(this, obj)) 157 | return true; 158 | 159 | SqlParameterInfo other = (obj as SqlParameterInfo)!; 160 | if (other == null) return false; 161 | 162 | if (Name != other.Name) 163 | return false; 164 | if (Value != other.Value) 165 | return false; 166 | if (ParameterDirection != other.ParameterDirection) 167 | return false; 168 | return true; 169 | } 170 | #endregion 171 | 172 | } 173 | } 174 | -------------------------------------------------------------------------------- /src/InterpolatedSql/SqlParameters/StringParameterInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Data; 2 | 3 | namespace InterpolatedSql 4 | { 5 | /// 6 | /// Represents a Sql Parameter of String type. 7 | /// Copied from Dapper DbString. 8 | /// Most other types can use (or can just pass the value, without using a SqlParameterInfo subtype), 9 | /// but strings have some important aspects: 10 | /// - Fixed Length or Variable Length (in MSSQL fixed length would be char/nchar, and variable would be varchar/nvarchar) 11 | /// - Ansi vs Unicode (in MSSQL Ansi would be char/varchar, while Unicode would be nchar/nvarchar). 12 | /// 13 | public class StringParameterInfo : SqlParameterInfo 14 | { 15 | #region Members 16 | /// 17 | /// Default value for IsAnsi. 18 | /// 19 | public static bool IsAnsiDefault { get; set; } 20 | 21 | /// 22 | /// Default length of strings when they are passed to Dapper (or other ORM). 23 | /// Default is 4000, but any value larger than this field will not have the default value applied. 24 | /// 25 | public const int DefaultLength = 4000; 26 | 27 | /// 28 | /// Ansi vs Unicode 29 | /// 30 | public bool IsAnsi { get; set; } 31 | 32 | /// 33 | /// Fixed length 34 | /// 35 | public bool IsFixedLength { get; set; } 36 | 37 | /// 38 | /// Length of the string -1 for max 39 | /// 40 | public int Length { get; set; } 41 | #endregion 42 | 43 | /// 44 | /// Create a new StringParameterInfo 45 | /// 46 | /// The name of the parameter. 47 | /// The value of the parameter. 48 | /// The in or out direction of the parameter. 49 | public StringParameterInfo(string? name, object? value = null, ParameterDirection? direction = null) : base(name, value, direction) 50 | { 51 | Length = -1; 52 | IsAnsi = IsAnsiDefault; 53 | Value = null; 54 | } 55 | 56 | /// 57 | /// 58 | /// 59 | public StringParameterInfo() : base(null, null) 60 | { 61 | Length = -1; 62 | IsAnsi = IsAnsiDefault; 63 | Value = null; 64 | } 65 | 66 | 67 | /// 68 | /// Gets a string representation of this DbString. 69 | /// 70 | public override string ToString() => 71 | $"StringParameterInfo (Value: '{Value}', Length: {Length}, IsAnsi: {IsAnsi}, IsFixedLength: {IsFixedLength})"; 72 | 73 | 74 | /// 75 | /// Determines whether the specified object is equal to the current object. 76 | /// 77 | public override bool Equals(object obj) 78 | { 79 | if (ReferenceEquals(null, obj)) 80 | return false; 81 | if (ReferenceEquals(this, obj)) 82 | return true; 83 | 84 | StringParameterInfo other = (obj as StringParameterInfo)!; 85 | if (other == null) return false; 86 | 87 | if (!base.Equals(other)) 88 | return false; 89 | if (IsAnsi != other.IsAnsi) 90 | return false; 91 | if (IsFixedLength != other.IsFixedLength) 92 | return false; 93 | if (Length != other.Length) 94 | return false; 95 | return true; 96 | } 97 | 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /src/build.ps1: -------------------------------------------------------------------------------- 1 | [cmdletbinding()] 2 | param( 3 | [Parameter(Mandatory=$False)] 4 | [ValidateSet('Release','Debug')] 5 | [string]$configuration 6 | ) 7 | 8 | # How to run: 9 | # .\build.ps1 10 | # or 11 | # .\build.ps1 -configuration Debug 12 | 13 | 14 | $scriptpath = $MyInvocation.MyCommand.Path 15 | $dir = Split-Path $scriptpath 16 | Push-Location $dir 17 | 18 | if (-not $PSBoundParameters.ContainsKey('configuration')) 19 | { 20 | if (Test-Path Release.snk) { $configuration = "Release"; } else { $configuration = "Debug"; } 21 | } 22 | Write-Host "Using configuration $configuration..." -ForegroundColor Yellow 23 | 24 | $msbuild = ( 25 | "$Env:programfiles\Microsoft Visual Studio\2022\*\MSBuild\15.0\Bin\msbuild.exe", 26 | "$Env:programfiles (x86)\Microsoft Visual Studio\2022\*\MSBuild\15.0\Bin\msbuild.exe", 27 | "$Env:programfiles\Microsoft Visual Studio\2022\*\MSBuild\*\Bin\msbuild.exe", 28 | "$Env:programfiles (x86)\Microsoft Visual Studio\2022\*\MSBuild\*\Bin\msbuild.exe", 29 | "$Env:programfiles\Microsoft Visual Studio\2019\*\MSBuild\*\Bin\msbuild.exe", 30 | "$Env:programfiles (x86)\Microsoft Visual Studio\2019\*\MSBuild\*\Bin\msbuild.exe", 31 | "${Env:ProgramFiles(x86)}\MSBuild\15.0\Bin\MSBuild.exe", 32 | "${Env:ProgramFiles(x86)}\MSBuild\14.0\Bin\MSBuild.exe" 33 | ) | Where-Object { Test-Path $_ } | Select-Object -first 1 34 | 35 | 36 | Remove-Item -Recurse -Force -ErrorAction Ignore ".\packages-local" 37 | Remove-Item -Recurse -Force -ErrorAction Ignore "$env:HOMEDRIVE$env:HOMEPATH\.nuget\packages\interpolatedsql" 38 | Remove-Item -Recurse -Force -ErrorAction Ignore "$env:HOMEDRIVE$env:HOMEPATH\.nuget\packages\interpolatedsql.strongname" 39 | Remove-Item -Recurse -Force -ErrorAction Ignore "$env:HOMEDRIVE$env:HOMEPATH\.nuget\packages\interpolatedsql.dapper" 40 | Remove-Item -Recurse -Force -ErrorAction Ignore "$env:HOMEDRIVE$env:HOMEPATH\.nuget\packages\interpolatedsql.dapper.strongname" 41 | 42 | New-Item -ItemType Directory -Force -Path ".\packages-local" 43 | 44 | 45 | try { 46 | 47 | # when target frameworks are added/modified dotnet clean might fail and we may need to cleanup the old dependency tree 48 | Get-ChildItem .\ -Recurse | Where{$_.FullName -CMatch ".*\\bin$" -and $_.PSIsContainer} | Remove-Item -Recurse -Force -ErrorAction Ignore 49 | Get-ChildItem .\ -Recurse | Where{$_.FullName -CMatch ".*\\obj$" -and $_.PSIsContainer} | Remove-Item -Recurse -Force -ErrorAction Ignore 50 | Get-ChildItem .\ -Recurse | Where{$_.FullName -Match ".*\\obj\\.*project.assets.json$"} | Remove-Item 51 | Get-ChildItem .\ -Recurse | Where{$_.FullName -Match ".*\.csproj$" -and $_.FullName -NotMatch ".*\\VSExtensions\\" } | ForEach { dotnet clean $_.FullName } 52 | 53 | dotnet clean InterpolatedSql.sln 54 | dotnet clean InterpolatedSql.Dapper.sln 55 | dotnet clean InterpolatedSql\InterpolatedSql.csproj 56 | dotnet clean InterpolatedSql.StrongName\InterpolatedSql.StrongName.csproj 57 | dotnet clean InterpolatedSql.Dapper\InterpolatedSql.Dapper.csproj 58 | dotnet clean InterpolatedSql.Dapper.StrongName\InterpolatedSql.Dapper.StrongName.csproj 59 | 60 | dotnet restore InterpolatedSql.sln 61 | dotnet restore InterpolatedSql.Dapper.sln 62 | 63 | 64 | # InterpolatedSql + nupkg/snupkg (dotnet build is the best at restoring packages; but for deterministic builds we need msbuild) 65 | dotnet build -c release InterpolatedSql\InterpolatedSql.csproj 66 | & $msbuild "InterpolatedSql\InterpolatedSql.csproj" ` 67 | /t:Pack ` 68 | /p:PackageOutputPath="..\packages-local\" ` 69 | '/p:targetFrameworks="netstandard2.0;net462;net472;net5.0;net6.0;net7.0"' ` 70 | /p:Configuration=$configuration ` 71 | /p:IncludeSymbols=true ` 72 | /p:SymbolPackageFormat=snupkg ` 73 | /verbosity:minimal ` 74 | /p:ContinuousIntegrationBuild=true 75 | if (! $?) { throw "msbuild failed" } 76 | 77 | # InterpolatedSql.StrongName + nupkg/snupkg (dotnet build is the best at restoring packages; but for deterministic builds we need msbuild) 78 | dotnet build -c release InterpolatedSql.StrongName\InterpolatedSql.StrongName.csproj 79 | & $msbuild "InterpolatedSql.StrongName\InterpolatedSql.StrongName.csproj" ` 80 | /t:Pack ` 81 | /p:PackageOutputPath="..\packages-local\" ` 82 | '/p:targetFrameworks="netstandard2.0;net462;net472;net5.0;net6.0;net7.0"' ` 83 | /p:Configuration=$configuration ` 84 | /p:IncludeSymbols=true ` 85 | /p:SymbolPackageFormat=snupkg ` 86 | /verbosity:minimal ` 87 | /p:ContinuousIntegrationBuild=true 88 | if (! $?) { throw "msbuild failed" } 89 | 90 | # InterpolatedSql.Dapper + nupkg/snupkg (dotnet build is the best at restoring packages; but for deterministic builds we need msbuild) 91 | dotnet build -c release InterpolatedSql.Dapper\InterpolatedSql.Dapper.csproj 92 | & $msbuild "InterpolatedSql.Dapper\InterpolatedSql.Dapper.csproj" ` 93 | /t:Pack ` 94 | /p:PackageOutputPath="..\packages-local\" ` 95 | '/p:targetFrameworks="netstandard2.0;net462;net472;net5.0;net6.0;net7.0"' ` 96 | /p:Configuration=$configuration ` 97 | /p:IncludeSymbols=true ` 98 | /p:SymbolPackageFormat=snupkg ` 99 | /verbosity:minimal ` 100 | /p:ContinuousIntegrationBuild=true 101 | if (! $?) { throw "msbuild failed" } 102 | 103 | # InterpolatedSql.Dapper.StrongName + nupkg/snupkg (dotnet build is the best at restoring packages; but for deterministic builds we need msbuild) 104 | dotnet build -c release InterpolatedSql.Dapper.StrongName\InterpolatedSql.Dapper.StrongName.csproj 105 | & $msbuild "InterpolatedSql.Dapper.StrongName\InterpolatedSql.Dapper.StrongName.csproj" ` 106 | /t:Pack ` 107 | /p:PackageOutputPath="..\packages-local\" ` 108 | '/p:targetFrameworks="netstandard2.0;net462;net472;net5.0;net6.0;net7.0"' ` 109 | /p:Configuration=$configuration ` 110 | /p:IncludeSymbols=true ` 111 | /p:SymbolPackageFormat=snupkg ` 112 | /verbosity:minimal ` 113 | /p:ContinuousIntegrationBuild=true 114 | if (! $?) { throw "msbuild failed" } 115 | 116 | } finally { 117 | Pop-Location 118 | } 119 | 120 | 121 | # Unit tests 122 | if ($configuration -eq "Debug") 123 | { 124 | dotnet build -c release InterpolatedSql.Tests\InterpolatedSql.Tests.csproj 125 | dotnet test InterpolatedSql.Tests\InterpolatedSql.Tests.csproj 126 | 127 | dotnet build -c release InterpolatedSql.Dapper.Tests\InterpolatedSql.Dapper.Tests.csproj 128 | dotnet test InterpolatedSql.Dapper.Tests\InterpolatedSql.Dapper.Tests.csproj 129 | 130 | } 131 | -------------------------------------------------------------------------------- /src/debug.snk: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Drizin/InterpolatedSql/764e9b03017a94b513c61f78b1931f1ee18eb1af/src/debug.snk --------------------------------------------------------------------------------