├── .gitignore ├── .travis.yml ├── README.md ├── SqlQueryBuilder.Test ├── DeleteQueryBuilderTest.cs ├── InsertQueryBuilderTest.cs ├── POCO │ ├── Car.cs │ ├── CarMaker.cs │ └── Country.cs ├── SelectBuilderTest.cs ├── SelectQueryBuilderTest.cs ├── SqlQueryBuilder.Test.csproj ├── UpdateQueryBuilderTest.cs └── WhereFactoryTest.cs ├── SqlQueryBuilder.sln └── SqlQueryBuilder ├── Delete ├── DeleteQueryBuilder.cs ├── IQueryBuilderDeleteFrom.cs └── IQueryBuilderJoinOrWhere.cs ├── IBuild.cs ├── IQueryBuilderFactory.cs ├── ISqlTranslator.cs ├── Insert ├── IQueryBuilderInsertInto.cs ├── IQueryBuilderValues.cs └── InsertQueryBuilder.cs ├── Select ├── AggregateFunctions.cs ├── IQueryBuilderGroupBy.cs ├── IQueryBuilderJoinOrSelect.cs ├── IQueryBuilderOrderBy.cs ├── IQueryBuilderSelect.cs ├── IQueryBuilderSelectFrom.cs ├── IQueryBuilderSelectOnly.cs ├── IQueryBuilderWhere.cs ├── ISelectBuilder.cs ├── SelectBuilder.cs ├── SelectBuilders.cs └── SelectQueryBuilder.cs ├── SqlQueryBuilder.csproj ├── SqlQueryBuilderFactory.cs ├── SqlTranslator.cs ├── Update ├── IQueryBuilderJoinOrSet.cs ├── IQueryBuilderSet.cs ├── IQueryBuilderUpdateFrom.cs └── UpdateQueryBuilder.cs └── Where ├── Comparator.cs ├── ICompare.cs ├── ICompareBuilder.cs ├── IQueryBuilderWhereOrBuild.cs ├── IWhereBuilder.cs ├── IWhereBuilderFactory.cs ├── Operators.cs └── WhereBuilderFactory.cs /.gitignore: -------------------------------------------------------------------------------- 1 | *.suo 2 | *.user 3 | bin 4 | obj 5 | .vs 6 | packages -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: csharp 2 | dist: trusty 3 | mono: none 4 | dotnet: 2.1 5 | install: 6 | - dotnet restore 7 | script: 8 | - dotnet build 9 | - dotnet test SqlQueryBuilder.Test 10 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # SqlQueryBuilder [![Build Status](https://travis-ci.com/Rem0o/SqlQueryBuilder.svg?branch=master)](https://travis-ci.com/Rem0o/SqlQueryBuilder) 2 | 3 | ### Main features: 4 | - Build all your Select/Update/Insert/Delete T-SQL queries arround your POCOs! 5 | - Fluent interface 6 | - Try-Build pattern (basic validation) 7 | - Evaluate once: use parameters ("@param") within the builder to parametrize your queries! 8 | 9 | ### Other features 10 | - Build your complex "WHERE" clauses as sub-blocks you can assemble 11 | - Use complex selectors like aggregates and SQL functions by inheriting the SelectBuilder class. 12 | - Supports table name alias (when you join the same table multiple times) 13 | - All depedencies are abstracted and injected, can easily be integrated with any IoC library. 14 | 15 | ## Code exemples 16 | 17 | Here is a basic factory method to get a builder. This factory method can be registered into any container library. 18 | ```c# 19 | private IQueryBuilderFactory GetBuilder() 20 | { 21 | ISqlTranslator translatorFactory() => new SqlTranslator(); 22 | ICompare compareFactory() => new Comparator(); 23 | IWhereBuilderFactory whereBuilderFactory() => new WhereBuilderFactory(compareFactory); 24 | return new SqlQueryBuilderFactory(translatorFactory, whereBuilderFactory, compareFactory); 25 | } 26 | ``` 27 | 28 | ### A basic SELECT query 29 | 30 | Simply write your query with terms you are familiar with. 31 | ```c# 32 | bool isValid = GetBuilder().GetSelect().From() 33 | .SelectAll() 34 | .Where(comparator => comparator.Compare(car => car.ModelYear).With(Operators.GT, "@year")) 35 | .TryBuild(out string query); 36 | 37 | //@year can later be replaced with your favorite library 38 | ``` 39 | Resulting SQL: 40 | ```sql 41 | SELECT [Car].* 42 | FROM [Car] 43 | WHERE [CarMaker].[ModelYear] > @year 44 | ``` 45 | 46 | ### A basic UPDATE query 47 | 48 | An update query is just as simple. 49 | ```c# 50 | bool isValid = GetBuilder().GetUpdate().From("CarTableAlias") 51 | .Set(car => car.Mileage, "@mileage") 52 | .Where(c => c.Compare(car => car.Mileage).With(Operators.LT, "0")) 53 | .TryBuild(out string query); 54 | ``` 55 | Resulting SQL: 56 | ```sql 57 | UPDATE [CarTableAlias] 58 | SET [CarTableAlias].[Mileage] = @mileage 59 | FROM [Car] [CarTableAlias] 60 | WHERE [CarTableAlias].[Mileage] < 0 61 | ``` 62 | Note the use of an alias for the "Car" table. 63 | 64 | ### A basic INSERT INTO query 65 | 66 | An insert query is also straightforward. 67 | ```c# 68 | bool isValid = GetBuilder().GetInsert() 69 | .InsertInto(car => new { 70 | car.Id, 71 | car.ModelYear, 72 | car.Mileage, 73 | car.Price, 74 | car.CarMakerId 75 | }).Values("@id", "@modelYear", "@mileage", "@price", "@carMakerId") 76 | .TryBuild(out string query); 77 | ``` 78 | Resulting SQL: 79 | ```sql 80 | INSERT INTO [CAR] 81 | ([Car].[Id], [Car].[ModelYear], [Car].[Mileage], [Car].[Price], [Car].[CarMakerId]) 82 | VALUES (@id, @modelYear, @mileage, @price, @carMakerId) 83 | ``` 84 | 85 | ### A DELETE query 86 | 87 | By now you should expect the delete query to be just as simple. But here's a taste of more advanced features. 88 | ```c# 89 | bool isValid = GetBuilder().GetDelete().DeleteFrom() 90 | .Join(car => car.CarMakerId, carMaker => carMaker.Id) 91 | .WhereFactory(f => f.Or( 92 | f1 => f1.Compare(c => c.Compare(m => m.FoundationDate).With(Operators.LT, new DateTime(1950, 01, 01).ToShortDateString())), 93 | f2 => f2.Compare(c => c.Compare(car => car.Mileage).With(Operators.LTE, 50_000.ToString())) 94 | )); 95 | ``` 96 | Resulting SQL: 97 | ```sql 98 | DELETE FROM [Car] 99 | JOIN [CarMaker] ON [Car].[CarMakerId] = [CarMaker].[Id] 100 | WHERE ((([CarMaker].[FoundationDate]) < (1950-01-01)) OR (([Car].[Mileage]) <= (50000))) 101 | ``` 102 | 103 | ### Table alias 104 | 105 | You can use table aliases if you want to join the same table multiple times. 106 | 107 | ```c# 108 | const string TABLE1 = "MAKER1"; 109 | const string TABLE2 = "MAKER2"; 110 | 111 | var isValid = GetBuilder().GetSelect().From(TABLE1) 112 | .Join(maker1 => maker1.CountryOfOriginId, maker2 => maker2.CountryOfOriginId, TABLE1, TABLE2) 113 | .SelectAll(TABLE1) 114 | .Where(comparator => comparator.Compare(maker1 => maker1.Id, TABLE1).With(Operators.NEQ, maker2 => maker2.Id, TABLE2)) 115 | .TryBuild(out string query); 116 | ``` 117 | Resulting SQL: 118 | ```sql 119 | SELECT [MAKER1].* 120 | FROM [CarMaker] AS [MAKER1] 121 | JOIN [CarMaker] AS [MAKER2] ON [Maker1].[CountryOfOriginId] = [Maker2].[CountryOfOriginId] 122 | WHERE [Maker1].[Id] <> [Maker2].Id 123 | ``` 124 | 125 | ### A more complex SELECT query 126 | 127 | Here is a more complex query. Note the use of a complex selector (average aggregate). 128 | ```c# 129 | var isValid = GetBuilder().GetSelect().From() 130 | .Join(car => car.CarMakerId, maker => maker.Id) 131 | .Select(maker => maker.Name) 132 | .Select(car => car.ModelYear) 133 | .SelectAs(new Aggregate(AggregateFunctions.AVG).Select(car => car.Price), "AveragePrice") 134 | .GroupBy(car => car.ModelYear) 135 | .GroupBy(maker => maker.Name) 136 | .TryBuild(out string query); 137 | ``` 138 | Resulting SQL: 139 | ```sql 140 | SELECT AVG([Car].[Price]) AS [AveragePrice], [CarMaker].[Name], [Car].[ModelYear] 141 | FROM [Car] 142 | JOIN [CarMaker] ON [Car].[CarMakerId] = [CarMaker].[Id] 143 | GROUP BY [Car].[ModelYear], [CarMaker].[Name] 144 | ``` 145 | 146 | A complex selector is described as a class. Simply inherit SelectBuilder to create your own easily. The generic type \ will enforce (or not using object) a specific type on your special selector. For example, here is a "DATEDIFF" implementation that requires the selector to be of a DateTime type. 147 | ```c# 148 | // definition 149 | public class DateDiff : SelectBuilder 150 | { 151 | private readonly DateDiffType type; 152 | private readonly DateTime compareTo; 153 | 154 | public DateDiff(DateDiffType type, DateTime compareTo) 155 | { 156 | this.type = type; 157 | this.compareTo = compareTo; 158 | } 159 | 160 | protected override string BuildSelectClause(string column) 161 | { 162 | return $"DATEDIFF({type.ToString()}, '{compareTo.ToString("yyyy-MM-dd")}', {column})"; 163 | } 164 | } 165 | 166 | // (...) 167 | 168 | // usage 169 | new DateDiff(DateDiffType.YEAR, new DateTime(2018,1,1)) 170 | .Select(maker => maker.FoundationDate); 171 | ``` 172 | 173 | Resulting SQL: 174 | ```sql 175 | DATEDIFF(YEAR, '2018-01-01', [CarMaker].[FoundationDate]) 176 | ``` 177 | 178 | ### WHERE is the fun? 179 | 180 | People's car tastes can be all over the place, and so can be your "WHERE" clauses! Here are some "WHERE" conditions extracted as functions so we can use them later. 181 | ```c# 182 | private IWhereBuilder CheapCarCondition(IWhereBuilderFactory factory) 183 | { 184 | return factory.And( 185 | f => f.Compare(c => c.Compare(car => car.Mileage).With(Compare.LT, "@cheap_mileage")), 186 | f => f.Compare(c => c.Compare(car => car.Price).With(Compare.LT, "@cheap_price")), 187 | f => f.Compare(c => c.Compare(maker => => maker.Name).With(Compare.NEQ, "@cheap_name")), 188 | f => f.Compare(c => c.Compare(country => country.Name).With(Compare.NEQ, "@cheap_country")) 189 | ); 190 | } 191 | 192 | private IWhereBuilder DreamCarExceptionCondition(IWhereBuilderFactory factory) 193 | { 194 | return factory.And( 195 | f => f.Compare(c => c.Compare(car => car.Mileage).With(Compare.LT, "@dream_mileage")), 196 | f => f.Compare(c => c.Compare(car => car.Price).With(Compare.LT, "@dream_price")), 197 | f => f.Compare(c => c.Compare(maker => maker.Name).With(Compare.EQ, "@dream_maker")) 198 | ); 199 | } 200 | ``` 201 | The conditions above are assembled with a "OR" to create our very specific query! Also, notice the anonymous object used inside the select function. 202 | ```c# 203 | 204 | var isValid = GetBuilder().GetSelect().From() 205 | .Join(car => car.CarMakerId, maker => maker.Id) 206 | .Join(maker => maker.CountryOfOriginId, country => country.Id) 207 | .Select(car => new { car.Id, car.Price }) 208 | .WhereFactory(factory => factory.Or( 209 | CheapCarCondition, 210 | DreamCarExceptionCondition 211 | )) 212 | .OrderBy(car => car.Price, desc: true) 213 | .TryBuild(out string query); 214 | ``` 215 | 216 | Resulting SQL: 217 | ```sql 218 | SELECT [Car].[Id], [Car].[Price] 219 | FROM [Car] 220 | JOIN [CarMaker] ON [Car].[CarMakerId] = [CarMaker].[Id] 221 | JOIN [Country] ON [CarMaker].[CountryOfOriginId] = [Country].[Id] 222 | WHERE ( 223 | ((([Car].[Mileage]) < (@cheap_mileage)) AND 224 | (([Car].[Price]) < (@cheap_price)) AND 225 | (([CarMaker].[Name]) <> (@cheap_maker)) AND 226 | (([Country].[Name]) <> (@cheap_country)) 227 | ) OR ( 228 | (([Car].[Mileage]) < (@dream_mileage)) AND 229 | (([Car].[Price]) < (@dream_price)) AND 230 | (([CarMaker].[Name]) = (@dream_maker))) 231 | ) 232 | ORDER BY [Car].[Price] DESC 233 | ``` 234 | -------------------------------------------------------------------------------- /SqlQueryBuilder.Test/DeleteQueryBuilderTest.cs: -------------------------------------------------------------------------------- 1 | using SqlQueryBuilder.Delete; 2 | using SqlQueryBuilder.Test.POCO; 3 | using SqlQueryBuilder.Where; 4 | using System; 5 | using Xunit; 6 | 7 | namespace SqlQueryBuilder.Test 8 | { 9 | public class DeleteQueryBuilderTest 10 | { 11 | private IQueryBuilderDeleteFrom GetBuilder() 12 | { 13 | ISqlTranslator translatorFactory() => new SqlTranslator(); 14 | ICompare compareFactory() => new Comparator(); 15 | IWhereBuilderFactory whereBuilderFactory() => new WhereBuilderFactory(compareFactory); 16 | return new SqlQueryBuilderFactory(translatorFactory, whereBuilderFactory, compareFactory).GetDelete(); 17 | } 18 | 19 | [Fact] 20 | public void DeleteFrom_ValidQuery() 21 | { 22 | var builder = GetBuilder().DeleteFrom(); 23 | 24 | Assert.True(builder.TryBuild(out var query)); 25 | 26 | var expectedQuery = "DELETE FROM [CAR]"; 27 | AssertCompareQueries(expectedQuery, query); 28 | } 29 | 30 | [Fact] 31 | public void DeleteFrom_Join_ValidQuery() 32 | { 33 | var builder = GetBuilder().DeleteFrom() 34 | .Join(car => car.CarMakerId, carMaker => carMaker.Id); 35 | 36 | Assert.True(builder.TryBuild(out var query)); 37 | 38 | var expectedQuery = "DELETE FROM [CAR] JOIN [CARMAKER] ON [CAR].[CarMakerId] = [CarMaker].[Id]"; 39 | AssertCompareQueries(expectedQuery, query); 40 | } 41 | 42 | [Fact] 43 | public void DeleteFrom_JoinWhere_ValidQuery() 44 | { 45 | var builder = GetBuilder().DeleteFrom() 46 | .Join(car => car.CarMakerId, carMaker => carMaker.Id) 47 | .WhereFactory(f => f.Or( 48 | f1 => f1.Compare(c => c.Compare(m => m.FoundationDate).With(Operators.LT, "1950-01-01")), 49 | f2 => f2.Compare(c => c.Compare(car => car.Mileage).With(Operators.LTE, 50_000.ToString())) 50 | )); 51 | 52 | Assert.True(builder.TryBuild(out var query)); 53 | 54 | var expectedQuery = "DELETE FROM [Car] " 55 | + "JOIN [CarMaker] ON [Car].[CarMakerId] = [CarMaker].[Id] " 56 | + "WHERE ((([CarMaker].[FoundationDate]) < (1950-01-01)) OR (([Car].[Mileage]) <= (50000)))"; 57 | 58 | AssertCompareQueries(expectedQuery, query); 59 | } 60 | 61 | [Fact] 62 | public void WhereBuilderValidity_Affect_QueryValidity() 63 | { 64 | var translator = new SqlTranslator(); 65 | translator.AddTable(typeof(Car)); 66 | translator.AddTable(typeof(CarMaker)); 67 | 68 | var whereBuilderFactory = new WhereBuilderFactory(() => new Comparator()); 69 | 70 | var whereIsValid = CountryCondition(whereBuilderFactory) 71 | .TryBuild(translator, out _); 72 | 73 | Assert.False(whereIsValid, "The where clause needs to be invalid"); 74 | 75 | var basicQuery = GetBuilder().DeleteFrom(); 76 | 77 | Assert.True(basicQuery.TryBuild(out _), "The basic query should be valid"); 78 | 79 | var isValid = basicQuery 80 | .WhereFactory(CountryCondition) // Fail condition, country is not joined 81 | .TryBuild(out var query); 82 | 83 | Assert.False(isValid, "An invalid where should cause an otherwise valid query to be invalid"); 84 | } 85 | 86 | private IWhereBuilder CountryCondition(IWhereBuilderFactory factory) 87 | { 88 | return factory.Compare(c => c.Compare(country => country.Name).With(Operators.LIKE, "Germany")); 89 | } 90 | 91 | private void AssertCompareQueries(string first, string second) 92 | { 93 | string prep(string s) => s.Trim().ToUpperInvariant().Replace(Environment.NewLine, " "); 94 | string prepFirst = prep(first), 95 | prepSecond = prep(second); 96 | 97 | Assert.Equal(prepFirst, prepSecond); 98 | } 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /SqlQueryBuilder.Test/InsertQueryBuilderTest.cs: -------------------------------------------------------------------------------- 1 | using SqlQueryBuilder.Insert; 2 | using SqlQueryBuilder.Test.POCO; 3 | using SqlQueryBuilder.Where; 4 | using System; 5 | using System.Linq; 6 | using Xunit; 7 | 8 | namespace SqlQueryBuilder.Test 9 | { 10 | public class InsertQueryBuilderTest 11 | { 12 | private IQueryBuilderInsertInto GetBuilder() 13 | { 14 | ISqlTranslator translatorFactory() => new SqlTranslator(); 15 | ICompare compareFactory() => new Comparator(); 16 | IWhereBuilderFactory whereBuilderFactory() => new WhereBuilderFactory(compareFactory); 17 | return new SqlQueryBuilderFactory(translatorFactory, whereBuilderFactory, compareFactory).GetInsert(); 18 | } 19 | 20 | [Fact] 21 | public void InsertInto_ValidQuery() 22 | { 23 | var builder = GetBuilder().InsertInto(car => new 24 | { 25 | car.Id, 26 | car.ModelYear, 27 | car.Mileage, 28 | car.Price, 29 | car.CarMakerId 30 | }).Values(typeof(Car).GetProperties().Select(p => $"@{p.Name}").ToArray()); 31 | 32 | Assert.True(builder.TryBuild(out var query)); 33 | 34 | var expectedQuery = "INSERT INTO [CAR] ([Car].[Id], [Car].[ModelYear], [Car].[Mileage], [Car].[Price], [Car].[CarMakerId]) " 35 | + "VALUES (@Id, @ModelYear, @Mileage, @Price, @CarMakerId)"; 36 | Assert.True(CompareQueries(expectedQuery, query)); 37 | } 38 | 39 | [Fact] 40 | public void InsertInto_MissingValues_InvalidQuery() 41 | { 42 | 43 | var builder = GetBuilder().InsertInto(car => new 44 | { 45 | car.Id, 46 | car.ModelYear, 47 | car.Mileage, 48 | car.Price, 49 | car.CarMakerId 50 | }).Values("@id", "@modelYear"); // missing values 51 | 52 | Assert.False(builder.TryBuild(out var query)); 53 | } 54 | 55 | private bool CompareQueries(string first, string second) 56 | { 57 | string prep(string s) => s.Trim().ToUpperInvariant().Replace(Environment.NewLine, " "); 58 | return prep(first) == prep(second); 59 | } 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /SqlQueryBuilder.Test/POCO/Car.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace SqlQueryBuilder.Test.POCO 4 | { 5 | public class Car 6 | { 7 | public Guid Id { get; set; } 8 | public int ModelYear { get; set; } 9 | public int Mileage { get; set; } 10 | public decimal Price { get; set; } 11 | public Guid CarMakerId { get; set; } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /SqlQueryBuilder.Test/POCO/CarMaker.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace SqlQueryBuilder.Test.POCO 4 | { 5 | public class CarMaker 6 | { 7 | public Guid Id { get; set; } 8 | public string Name { get; set; } 9 | public Guid CountryOfOriginId { get; set; } 10 | public DateTime FoundationDate { get; set; } 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /SqlQueryBuilder.Test/POCO/Country.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace SqlQueryBuilder.Test.POCO 4 | { 5 | public class Country 6 | { 7 | public Guid Id { get; set; } 8 | public string Name { get; set; } 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /SqlQueryBuilder.Test/SelectBuilderTest.cs: -------------------------------------------------------------------------------- 1 | using SqlQueryBuilder.Select; 2 | using SqlQueryBuilder.Test.POCO; 3 | using System; 4 | using Xunit; 5 | 6 | namespace SqlQueryBuilder.Test 7 | { 8 | public class SelectBuilderTest 9 | { 10 | [Fact] 11 | public void DateDiff_Year_IsValid() 12 | { 13 | DateTime compareTo = DateTime.Now; 14 | var translator = GetTranslator(); 15 | 16 | var mydatediff = new DateDiff(DateDiffType.YEAR, DateTime.Now.ToString("yyyy-MM-dd")); 17 | 18 | Assert.True(mydatediff 19 | .Select(maker => maker.FoundationDate) 20 | .TryBuild(translator, out string clause)); 21 | 22 | Assert.True($"datediff(YEAR, [CarMaker].[FoundationDate], '{compareTo.ToString("yyyy-MM-dd")}')" == clause); 23 | } 24 | 25 | private static SqlTranslator GetTranslator() 26 | { 27 | var translator = new SqlTranslator(); 28 | translator.AddTable(typeof(CarMaker)); 29 | return translator; 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /SqlQueryBuilder.Test/SelectQueryBuilderTest.cs: -------------------------------------------------------------------------------- 1 | using SqlQueryBuilder.Select; 2 | using SqlQueryBuilder.Test.POCO; 3 | using SqlQueryBuilder.Where; 4 | using System; 5 | using Xunit; 6 | 7 | namespace SqlQueryBuilder.Test 8 | { 9 | public class SqlQueryBuilderTest 10 | { 11 | private IQueryBuilderSelectFrom GetBuilder() 12 | { 13 | ISqlTranslator translatorFactory() => new SqlTranslator(); 14 | ICompare compareFactory() => new Comparator(); 15 | IWhereBuilderFactory whereBuilderFactory() => new WhereBuilderFactory(compareFactory); 16 | return new SqlQueryBuilderFactory(translatorFactory, whereBuilderFactory, compareFactory).GetSelect(); 17 | } 18 | 19 | [Fact] 20 | public void SelectFrom_POCO_ValidQuery() 21 | { 22 | var isValid = GetBuilder().From() 23 | .Join(car => car.CarMakerId, maker => maker.Id) 24 | .SelectAll() 25 | .Where(c => c.Compare(maker => maker.Name).With(Operators.EQ, "@brand")) 26 | .OrderBy(maker => maker.FoundationDate, desc: true) 27 | .TryBuild(out var query); 28 | 29 | Assert.True(isValid, "The query should be valid"); 30 | 31 | var expectedQuery = $"SELECT [Car].* FROM [Car] JOIN [CarMaker] ON [Car].[CarMakerId] = [CarMaker].[Id] " 32 | + "WHERE (([CarMaker].[Name]) = (@brand)) ORDER BY [CarMaker].[FoundationDate] DESC"; 33 | Assert.True(CompareQueries(expectedQuery, query)); 34 | } 35 | 36 | [Fact] 37 | public void SelectFrom_POCOWithSkipTake_ValidQuery() 38 | { 39 | var isValid = GetBuilder().From() 40 | .SelectAll() 41 | .OrderBy(car => car.ModelYear) 42 | .Skip(10).Take(10) 43 | .TryBuild(out var query); 44 | 45 | Assert.True(isValid, "The query should be valid"); 46 | 47 | var expectedQuery = $"SELECT [Car].* FROM [Car] ORDER BY [Car].[ModelYear] OFFSET 10 ROWS FETCH NEXT 10 ROWS ONLY "; 48 | Assert.True(CompareQueries(expectedQuery, query)); 49 | } 50 | 51 | [Fact] 52 | public void SelectFrom_POCOWithJustSkip_ValidQuery() 53 | { 54 | var isValid = GetBuilder().From() 55 | .SelectAll() 56 | .OrderBy(car => car.ModelYear) 57 | .Skip(10) 58 | .TryBuild(out var query); 59 | 60 | Assert.True(isValid, "The query should be valid"); 61 | 62 | var expectedQuery = $"SELECT [Car].* FROM [Car] ORDER BY [Car].[ModelYear] OFFSET 10 ROWS "; 63 | Assert.True(CompareQueries(expectedQuery, query)); 64 | } 65 | 66 | [Fact] 67 | public void SelectFrom_POCOWithJustTake_InvalidQuery() 68 | { 69 | var isValid = GetBuilder().From() 70 | .SelectAll() 71 | .OrderBy(car => car.ModelYear) 72 | .Take(10) 73 | .TryBuild(out var query); 74 | 75 | Assert.False(isValid, "The query should not be valid"); 76 | } 77 | 78 | [Theory] 79 | [InlineData("Automobile", "CarMaker")] 80 | [InlineData("Voiture", "Manufacturier")] 81 | public void SelectFrom_POCOWithAlias_ValidQuery(string carTableAlias, string makerTableAlias) 82 | { 83 | var isValid = GetBuilder().From(carTableAlias) 84 | .Join(car => car.CarMakerId, maker => maker.Id, table1Alias: carTableAlias, table2Alias: makerTableAlias) 85 | .SelectAll(carTableAlias) 86 | .Where(c => c.Compare(maker => maker.Name, makerTableAlias).With(Operators.EQ, "@brand")) 87 | .Where(c => c.Compare(car => car.ModelYear, carTableAlias).With(Operators.GT, "@year")) 88 | .TryBuild(out var query); 89 | 90 | Assert.True(isValid, "The query should be valid"); 91 | 92 | var expectedQuery = $"SELECT [{carTableAlias}].* FROM [Car] AS [{carTableAlias}] " 93 | + $"JOIN [CarMaker] AS [{makerTableAlias}] ON [{carTableAlias}].[CarMakerId] = [{makerTableAlias}].[Id] " 94 | + $"WHERE (([{makerTableAlias}].[Name]) = (@brand)) AND (([{carTableAlias}].[ModelYear]) > (@year))"; 95 | Assert.True(CompareQueries(expectedQuery, query)); 96 | } 97 | 98 | [Fact] 99 | public void SelectFrom_InferredAlias_ValidQuery() 100 | { 101 | const string CAR_ALIAS = "SomeCar"; 102 | const string MAKER_ALIAS = "SomeMaker"; 103 | 104 | var isValid = GetBuilder().From(CAR_ALIAS) 105 | .LeftJoin(car => car.CarMakerId, maker => maker.Id, table2Alias: MAKER_ALIAS) 106 | // inferred alias for "Car" because "Car" is only referred in a single FROM/JOIN 107 | .SelectAll() 108 | // inferred alias for "CarMaker" because "CarMaker" is only referred in a single FROM/JOIN 109 | .Where(c => c.Compare(maker => maker.Name).With(Operators.LIKE, "@brand")) 110 | .TryBuild(out var query); 111 | 112 | Assert.True(isValid); 113 | 114 | var expectedQuery = $"SELECT [{CAR_ALIAS}].* FROM [Car] AS [{CAR_ALIAS}] " 115 | + $"LEFT JOIN [CarMaker] AS [{MAKER_ALIAS}] ON [{CAR_ALIAS}].[CarMakerId] = [{MAKER_ALIAS}].[Id] " 116 | + $"WHERE (([{MAKER_ALIAS}].[Name]) LIKE (@brand))"; 117 | 118 | Assert.True(CompareQueries(expectedQuery, query)); 119 | } 120 | 121 | [Fact] 122 | public void SelectFrom_AnonymousObject_ValidQuery() 123 | { 124 | var isValid = GetBuilder().From() 125 | .Select(car => new { car.Id, car.CarMakerId, car.Mileage, car.ModelYear }) 126 | .TryBuild(out var query); 127 | 128 | Assert.True(isValid, "The query should be valid"); 129 | 130 | var expectedQuery = $"SELECT [Car].[Id], [Car].[CarMakerId], [Car].[Mileage], [Car].[ModelYear] FROM [Car]"; 131 | Assert.True(CompareQueries(expectedQuery, query)); 132 | } 133 | 134 | [Fact] 135 | public void SelectAggregate_Average_IsValid() 136 | { 137 | var isValid = GetBuilder().From() 138 | .Join(car => car.CarMakerId, maker => maker.Id) 139 | .SelectAs(new Aggregate(AggregateFunctions.AVG).Select(car => car.Mileage), "AverageMileage") 140 | .Select(car => car.ModelYear) 141 | .Select(maker => maker.Name) 142 | .GroupBy(car => car.ModelYear) 143 | .GroupBy(maker => maker.Name) 144 | .TryBuild(out var query); 145 | 146 | var expectedQuery = $"SELECT AVG([Car].[Mileage]) AS [AverageMileage], [Car].[ModelYear], [CarMaker].[Name] FROM [Car] " 147 | + $"JOIN [CarMaker] ON [Car].[CarMakerId] = [CarMaker].[Id] " 148 | + $"GROUP BY [Car].[ModelYear], [CarMaker].[Name]"; 149 | 150 | Assert.True(isValid); 151 | Assert.True(CompareQueries(expectedQuery, query)); 152 | } 153 | 154 | [Theory] 155 | [InlineData(10, true)] 156 | [InlineData(1, true)] 157 | [InlineData(0, true)] 158 | [InlineData(-1, false)] 159 | public void SelectTop_Integer_PositiveOnly(int top, bool valid) 160 | { 161 | var isValid = GetBuilder().From() 162 | .Top(top) 163 | .Select(maker => new { maker.FoundationDate, maker.Name }) 164 | .TryBuild(out var query); 165 | 166 | var expectedQuery = $"SELECT {(top > 0 ? $"TOP {top} " : string.Empty)}[CarMaker].[FoundationDate], [CarMaker].[Name] FROM [CarMaker]"; 167 | 168 | Assert.True(isValid == valid); 169 | if (isValid) 170 | Assert.True(expectedQuery == query); 171 | } 172 | 173 | [Fact] 174 | public void JoinSamePOCO_WithoutAlias_InvalidQuery() 175 | { 176 | var isValid = GetBuilder().From() 177 | .LeftJoin(car1 => car1.Id, car2 => car2.Id) 178 | .SelectAll() // which? 179 | .TryBuild(out _); 180 | 181 | Assert.False(isValid, "A table join on the same table without an alias should invalidate the query"); 182 | } 183 | 184 | [Fact] 185 | public void JoinSamePOCO_SameAlias_InvalidQuery() 186 | { 187 | const string ALIAS = "SomeAlias"; 188 | var isValid = GetBuilder().From(ALIAS) 189 | .LeftJoin(car1 => car1.Id, car2 => car2.Id, ALIAS, ALIAS) 190 | .SelectAll(ALIAS) // which? 191 | .TryBuild(out _); 192 | 193 | Assert.False(isValid, "A table join with the same alias should invalidate the query"); 194 | } 195 | 196 | [Fact] 197 | public void JoinSamePOCO_WithAlias_ValidQuery() 198 | { 199 | const string CAR1 = "CAR1"; 200 | const string CAR2 = "CAR2"; 201 | 202 | var isValid = GetBuilder().From(CAR1) 203 | .LeftJoin(car1 => car1.ModelYear, car2 => car2.ModelYear, CAR1, CAR2) 204 | .SelectAll(CAR1) 205 | .Where(c => c.Compare(car1 => car1.Id, CAR1).With(Operators.NEQ, car2 => car2.Id, CAR2)) 206 | .TryBuild(out _); 207 | 208 | Assert.True(isValid); 209 | } 210 | 211 | [Fact] 212 | public void WhereBuilderValidity_Affect_QueryValidity() 213 | { 214 | var translator = new SqlTranslator(); 215 | translator.AddTable(typeof(Car)); 216 | translator.AddTable(typeof(CarMaker)); 217 | 218 | var whereBuilderFactory = new WhereBuilderFactory(() => new Comparator()); 219 | 220 | var whereIsValid = CountryCondition(whereBuilderFactory) 221 | .TryBuild(translator, out _); 222 | 223 | Assert.False(whereIsValid, "The where clause needs to be invalid"); 224 | 225 | var basicQuery = GetBuilder().From() 226 | .SelectAll(); 227 | 228 | Assert.True(basicQuery.TryBuild(out _), "The basic query should be valid"); 229 | 230 | var isValid = basicQuery 231 | .WhereFactory(CountryCondition) // Fail condition 232 | .TryBuild(out var query); 233 | 234 | Assert.False(isValid, "An invalid where should cause an otherwise valid query to be invalid"); 235 | } 236 | 237 | private IWhereBuilder CountryCondition(IWhereBuilderFactory factory) 238 | { 239 | return factory.Compare(c => c.Compare(country => country.Name).With(Operators.LIKE, "Germany")); 240 | } 241 | 242 | private bool CompareQueries(string first, string second) 243 | { 244 | string prep(string s) => s.Trim().ToUpperInvariant().Replace(Environment.NewLine, " "); 245 | return prep(first) == prep(second); 246 | } 247 | } 248 | } 249 | -------------------------------------------------------------------------------- /SqlQueryBuilder.Test/SqlQueryBuilder.Test.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netcoreapp2.1 5 | 6 | false 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /SqlQueryBuilder.Test/UpdateQueryBuilderTest.cs: -------------------------------------------------------------------------------- 1 | using SqlQueryBuilder.Test.POCO; 2 | using SqlQueryBuilder.Update; 3 | using SqlQueryBuilder.Where; 4 | using System; 5 | using Xunit; 6 | 7 | namespace SqlQueryBuilder.Test 8 | { 9 | public class UpdateQueryBuilderTest 10 | { 11 | private IQueryBuilderUpdateFrom GetBuilder() 12 | { 13 | ISqlTranslator translatorFactory() => new SqlTranslator(); 14 | ICompare compareFactory() => new Comparator(); 15 | IWhereBuilderFactory whereBuilderFactory() => new WhereBuilderFactory(compareFactory); 16 | return new SqlQueryBuilderFactory(translatorFactory, whereBuilderFactory, compareFactory).GetUpdate(); 17 | } 18 | 19 | [Fact] 20 | public void Update_SetFrom_ValidQuery() 21 | { 22 | var builder = GetBuilder().From() 23 | .Set(car => car.Mileage, "@mileage"); 24 | 25 | Assert.True(builder.TryBuild(out var query)); 26 | Assert.True(CompareQueries("UPDATE [CAR] SET [CAR].[Mileage] = @mileage from [CAR]", query)); 27 | } 28 | 29 | [Fact] 30 | public void Update_NoSet_InvalidQuery() 31 | { 32 | var builder = GetBuilder().From().Set(null, null); 33 | 34 | Assert.False(builder.TryBuild(out var query)); 35 | } 36 | 37 | [Fact] 38 | public void Update_SetFromJoin_ValidQuery() 39 | { 40 | var builder = GetBuilder().From() 41 | .Join(car => car.CarMakerId, maker => maker.Id) 42 | .Join(maker => maker.CountryOfOriginId, country => country.Id) 43 | .Set(car => car.Mileage, "@mileage"); 44 | 45 | Assert.True(builder.TryBuild(out var query)); 46 | 47 | var expectedQuery = "UPDATE [CAR] SET [CAR].[Mileage] = @mileage from [CAR]" + 48 | "JOIN [CarMaker] on [Car].[CarMakerId] = [CarMaker].[Id] " + 49 | "JOIN [Country] on [CarMaker].[CountryOfOriginId] = [Country].[Id]"; 50 | 51 | Assert.True(CompareQueries(expectedQuery, query)); 52 | } 53 | 54 | [Fact] 55 | public void Update_SetFromJoinSameAlias_InvalidQuery() 56 | { 57 | var builder = GetBuilder().From("Alias") 58 | .Join(car => car.CarMakerId, maker => maker.Id, "Alias", "Alias") 59 | .Set(car => car.Mileage, "@mileage"); 60 | 61 | Assert.False(builder.TryBuild(out var query)); 62 | } 63 | 64 | [Fact] 65 | public void Update_SetFromWhere_ValidQuery() 66 | { 67 | var builder = GetBuilder().From() 68 | .Set(car => car.Mileage, "@mileage") 69 | .Where(c => c.Compare(car => car.Mileage).With(Operators.LT, "0")); 70 | 71 | Assert.True(builder.TryBuild(out var query)); 72 | 73 | var expectedQuery = "UPDATE [CAR] SET [CAR].[Mileage] = @mileage from [CAR]" + 74 | "WHERE (([CAR].[Mileage]) < (0))"; 75 | 76 | Assert.True(CompareQueries(expectedQuery, query)); 77 | } 78 | 79 | [Fact] 80 | public void WhereBuilderValidity_Affect_QueryValidity() 81 | { 82 | var translator = new SqlTranslator(); 83 | translator.AddTable(typeof(Car)); 84 | translator.AddTable(typeof(CarMaker)); 85 | 86 | var whereBuilderFactory = new WhereBuilderFactory(() => new Comparator()); 87 | 88 | var whereIsValid = CountryCondition(whereBuilderFactory) 89 | .TryBuild(translator, out _); 90 | 91 | Assert.False(whereIsValid, "The where clause needs to be invalid"); 92 | 93 | var basicQuery = GetBuilder().From() 94 | .Set(car => car.Mileage, "@value"); 95 | 96 | Assert.True(basicQuery.TryBuild(out _), "The basic query should be valid"); 97 | 98 | var isValid = basicQuery 99 | .WhereFactory(CountryCondition) // Fail condition, country is not joined 100 | .TryBuild(out var query); 101 | 102 | Assert.False(isValid, "An invalid where should cause an otherwise valid query to be invalid"); 103 | } 104 | 105 | private IWhereBuilder CountryCondition(IWhereBuilderFactory factory) 106 | { 107 | return factory.Compare(c => c.Compare(country => country.Name).With(Operators.LIKE, "Germany")); 108 | } 109 | 110 | private bool CompareQueries(string first, string second) 111 | { 112 | string prep(string s) => s.Trim().ToUpperInvariant().Replace(" ", string.Empty).Replace(Environment.NewLine, string.Empty); 113 | return prep(first) == prep(second); 114 | } 115 | } 116 | } 117 | -------------------------------------------------------------------------------- /SqlQueryBuilder.Test/WhereFactoryTest.cs: -------------------------------------------------------------------------------- 1 | using SqlQueryBuilder.Test.POCO; 2 | using SqlQueryBuilder.Where; 3 | using Xunit; 4 | 5 | namespace SqlQueryBuilder.Test 6 | { 7 | public class WhereFactoryTest 8 | { 9 | private IWhereBuilderFactory GetFactory() => new WhereBuilderFactory(() => new Comparator()); 10 | 11 | [Fact] 12 | public void Or_WithinMapper_Valid() 13 | { 14 | var translator = GetTranslator(); 15 | 16 | var builder = GetFactory().Or( 17 | CheapCarCondition, 18 | SweetSpotLexusCondition 19 | ); 20 | 21 | var expectedWhereClause = $"(((([Car].[Mileage]) < ({CHEAPCAR_MILEAGE})) AND (([Car].[Price]) < ({CHEAPCAR_PRICE})))" 22 | + $" OR ((([Car].[ModelYear]) > ({LEXUS_YEAR})) AND (([Car].[Mileage]) < ({LEXUS_MILEAGE})) AND" 23 | + $" (([Car].[Price]) <= ({LEXUS_PRICE})) AND (([CarMaker].[Name]) LIKE ({LEXUS_BRAND}))))"; 24 | 25 | Assert.True(builder.TryBuild(translator, out var whereClause)); 26 | Assert.True(whereClause == expectedWhereClause); 27 | } 28 | 29 | [Fact] 30 | public void Or_NotWithinMapper_Invalid() 31 | { 32 | var translator = GetTranslator(); 33 | 34 | IWhereBuilder CheapNonAmericanCondition(IWhereBuilderFactory factory) => factory.And( 35 | CheapCarCondition, 36 | // FAIL CONDITION: The "Country" table is not in the mapper 37 | f => f.Compare(c => c.Compare(country => country.Name).With(Operators.NEQ, "USA")) 38 | ); 39 | 40 | var builder = GetFactory().Or( 41 | CheapNonAmericanCondition, 42 | SweetSpotLexusCondition 43 | ); 44 | 45 | Assert.False(builder.TryBuild(translator, out _)); 46 | } 47 | 48 | private const string CHEAPCAR_MILEAGE = "100000"; 49 | private const string CHEAPCAR_PRICE = "5000"; 50 | 51 | private IWhereBuilder CheapCarCondition(IWhereBuilderFactory factory) 52 | { 53 | return factory.And( 54 | f => f.Compare(c => c.Compare(car => car.Mileage).With(Operators.LT, CHEAPCAR_MILEAGE)), 55 | f => f.Compare(c => c.Compare(car => car.Price).With(Operators.LT, CHEAPCAR_PRICE)) 56 | ); 57 | } 58 | 59 | private const string LEXUS_YEAR = "2015"; 60 | private const string LEXUS_MILEAGE = "25000"; 61 | private const string LEXUS_PRICE = "32000"; 62 | private const string LEXUS_BRAND = "LEXUS"; 63 | 64 | private IWhereBuilder SweetSpotLexusCondition(IWhereBuilderFactory factory) 65 | { 66 | return factory.And( 67 | f => f.Compare(c => c.Compare(car => car.ModelYear).With(Operators.GT, LEXUS_YEAR)), 68 | f => f.Compare(c => c.Compare(car => car.Mileage).With(Operators.LT, LEXUS_MILEAGE)), 69 | f => f.Compare(c => c.Compare(car => car.Price).With(Operators.LTE, LEXUS_PRICE)), 70 | f => f.Compare(c => c.Compare(maker => maker.Name).With(Operators.LIKE, LEXUS_BRAND)) 71 | ); 72 | } 73 | 74 | private static SqlTranslator GetTranslator() 75 | { 76 | var translator = new SqlTranslator(); 77 | translator.AddTable(typeof(Car)); 78 | translator.AddTable(typeof(CarMaker)); 79 | return translator; 80 | } 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /SqlQueryBuilder.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 15 4 | VisualStudioVersion = 15.0.27703.2035 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SqlQueryBuilder", "SqlQueryBuilder\SqlQueryBuilder.csproj", "{C3EABC59-2054-42AE-998D-842799348047}" 7 | EndProject 8 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SqlQueryBuilder.Test", "SqlQueryBuilder.Test\SqlQueryBuilder.Test.csproj", "{33544A9B-E3CB-4280-977E-567E70C9F0EF}" 9 | EndProject 10 | Global 11 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 12 | Debug|Any CPU = Debug|Any CPU 13 | Release|Any CPU = Release|Any CPU 14 | EndGlobalSection 15 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 16 | {C3EABC59-2054-42AE-998D-842799348047}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 17 | {C3EABC59-2054-42AE-998D-842799348047}.Debug|Any CPU.Build.0 = Debug|Any CPU 18 | {C3EABC59-2054-42AE-998D-842799348047}.Release|Any CPU.ActiveCfg = Release|Any CPU 19 | {C3EABC59-2054-42AE-998D-842799348047}.Release|Any CPU.Build.0 = Release|Any CPU 20 | {33544A9B-E3CB-4280-977E-567E70C9F0EF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 21 | {33544A9B-E3CB-4280-977E-567E70C9F0EF}.Debug|Any CPU.Build.0 = Debug|Any CPU 22 | {33544A9B-E3CB-4280-977E-567E70C9F0EF}.Release|Any CPU.ActiveCfg = Release|Any CPU 23 | {33544A9B-E3CB-4280-977E-567E70C9F0EF}.Release|Any CPU.Build.0 = Release|Any CPU 24 | EndGlobalSection 25 | GlobalSection(SolutionProperties) = preSolution 26 | HideSolutionNode = FALSE 27 | EndGlobalSection 28 | GlobalSection(ExtensibilityGlobals) = postSolution 29 | SolutionGuid = {8507F18F-9D26-4D71-AF9E-325519736809} 30 | EndGlobalSection 31 | EndGlobal 32 | -------------------------------------------------------------------------------- /SqlQueryBuilder/Delete/DeleteQueryBuilder.cs: -------------------------------------------------------------------------------- 1 | using SqlQueryBuilder.Where; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Linq.Expressions; 5 | 6 | namespace SqlQueryBuilder.Delete 7 | { 8 | internal class DeleteQueryBuilder : IQueryBuilderDeleteFrom, IQueryBuilderJoinOrWhere 9 | { 10 | private ISqlTranslator _translator; 11 | private Func _createWhereBuilderFactory; 12 | private Func _compareFactory; 13 | private KeyValuePair TableFrom; 14 | private List JoinClauses = new List(); 15 | private List WhereClauses = new List(); 16 | 17 | private DeleteQueryBuilder SkipIfError(Action action) 18 | { 19 | if (!_translator.HasError) 20 | action(); 21 | 22 | return this; 23 | } 24 | 25 | public DeleteQueryBuilder(ISqlTranslator translator, Func createWhereBuilderFactory, Func compareFactory) 26 | { 27 | _translator = translator; 28 | _createWhereBuilderFactory = createWhereBuilderFactory; 29 | _compareFactory = compareFactory; 30 | } 31 | 32 | public IQueryBuilderJoinOrWhere DeleteFrom(string tableAlias = null) 33 | { 34 | var type = typeof(T); 35 | var tableAliasKey = type.Name; 36 | TableFrom = new KeyValuePair(tableAliasKey, type); 37 | _translator.AddTable(type, TableFrom.Key); 38 | 39 | return this; 40 | } 41 | 42 | public IQueryBuilderJoinOrWhere Join(Expression> key1, Expression> key2, string table1Alias = null, string table2Alias = null, string joinType = null) 43 | => SkipIfError(() => 44 | { 45 | var table1Type = typeof(T); 46 | var joinTable2Type = typeof(U); 47 | var joinTable2Name = string.IsNullOrEmpty(table2Alias) ? joinTable2Type.Name : table2Alias; 48 | 49 | if (_translator.AddTable(joinTable2Type, joinTable2Name) == false) 50 | return; 51 | 52 | var joinTypeStr = (string.IsNullOrEmpty(joinType) ? string.Empty : $"{joinType}") + "JOIN"; 53 | var joinTableStr = $"[{joinTable2Type.Name}]" + (!string.IsNullOrEmpty(table2Alias) ? $" AS [{table2Alias}]" : string.Empty); 54 | var joinOnStr = $"{_translator.GetFirstTranslation(table1Type, key1, table1Alias)} = {_translator.GetFirstTranslation(joinTable2Type, key2, joinTable2Name)}"; 55 | var joinClause = $"{joinTypeStr} {joinTableStr} ON {joinOnStr}"; 56 | 57 | JoinClauses.Add(joinClause); 58 | }); 59 | 60 | public IQueryBuilderJoinOrWhere FullOuterJoin(Expression> key1, Expression> key2, string table1Alias = null, string table2Alias = null) 61 | { 62 | return Join(key1, key2, table1Alias, table2Alias, "FULL OUTER"); 63 | } 64 | 65 | public IQueryBuilderJoinOrWhere LeftJoin(Expression> key1, Expression> key2, string table1Alias = null, string table2Alias = null) 66 | { 67 | return Join(key1, key2, table1Alias, table2Alias, "LEFT"); 68 | } 69 | 70 | public IQueryBuilderJoinOrWhere RightJoin(Expression> key1, Expression> key2, string table1Alias = null, string table2Alias = null) 71 | { 72 | return Join(key1, key2, table1Alias, table2Alias, "RIGHT"); 73 | } 74 | 75 | public IQueryBuilderWhereOrBuild Where(Func compareBuilderFactory) => 76 | SkipIfError(() => 77 | { 78 | var success = compareBuilderFactory(_compareFactory()) 79 | .TryBuild(_translator, out string comparison); 80 | 81 | if (success) 82 | WhereClauses.Add(comparison); 83 | }); 84 | 85 | public IQueryBuilderWhereOrBuild WhereFactory(Func whereBuilder) => 86 | SkipIfError(() => 87 | { 88 | var success = whereBuilder(_createWhereBuilderFactory()).TryBuild(_translator, out var whereClause); 89 | 90 | if (success) 91 | WhereClauses.Add(whereClause); 92 | }); 93 | 94 | public bool TryBuild(out string query) 95 | { 96 | query = string.Empty; 97 | 98 | if (!Validate()) 99 | return false; 100 | 101 | var tableName = TableFrom.Value.Name; 102 | var tableAlias = TableFrom.Key; 103 | 104 | query = $"DELETE FROM [{tableName}] {(tableAlias != tableName ? $"[{tableAlias}]" : string.Empty)}" 105 | + (JoinClauses.Count > 0 ? string.Join(" ", JoinClauses) + " " : string.Empty) 106 | + (WhereClauses.Count > 0 ? $"WHERE {string.Join(" AND ", WhereClauses)} " : string.Empty); 107 | 108 | return true; 109 | } 110 | 111 | private bool Validate() => !_translator.HasError; 112 | } 113 | } -------------------------------------------------------------------------------- /SqlQueryBuilder/Delete/IQueryBuilderDeleteFrom.cs: -------------------------------------------------------------------------------- 1 | namespace SqlQueryBuilder.Delete 2 | { 3 | public interface IQueryBuilderDeleteFrom 4 | { 5 | IQueryBuilderJoinOrWhere DeleteFrom(string tableAlias = null); 6 | } 7 | } -------------------------------------------------------------------------------- /SqlQueryBuilder/Delete/IQueryBuilderJoinOrWhere.cs: -------------------------------------------------------------------------------- 1 | using SqlQueryBuilder.Where; 2 | using System; 3 | using System.Linq.Expressions; 4 | 5 | namespace SqlQueryBuilder.Delete 6 | { 7 | public interface IQueryBuilderJoinOrWhere: IQueryBuilderWhereOrBuild 8 | { 9 | IQueryBuilderJoinOrWhere Join(Expression> key1, Expression> key2, string table1Alias = null, string table2Alias = null, string joinType = null); 10 | IQueryBuilderJoinOrWhere LeftJoin(Expression> key1, Expression> key2, string table1Alias = null, string table2Alias = null); 11 | IQueryBuilderJoinOrWhere RightJoin(Expression> key1, Expression> key2, string table1Alias = null, string table2Alias = null); 12 | IQueryBuilderJoinOrWhere FullOuterJoin(Expression> key1, Expression> key2, string table1Alias = null, string table2Alias = null); 13 | } 14 | } -------------------------------------------------------------------------------- /SqlQueryBuilder/IBuild.cs: -------------------------------------------------------------------------------- 1 | namespace SqlQueryBuilder 2 | { 3 | public interface IBuildQuery 4 | { 5 | bool TryBuild(out string query); 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /SqlQueryBuilder/IQueryBuilderFactory.cs: -------------------------------------------------------------------------------- 1 | using SqlQueryBuilder.Delete; 2 | using SqlQueryBuilder.Insert; 3 | using SqlQueryBuilder.Select; 4 | using SqlQueryBuilder.Update; 5 | 6 | namespace SqlQueryBuilder 7 | { 8 | public interface IQueryBuilderFactory 9 | { 10 | IQueryBuilderSelectFrom GetSelect(); 11 | IQueryBuilderUpdateFrom GetUpdate(); 12 | IQueryBuilderInsertInto GetInsert(); 13 | IQueryBuilderDeleteFrom GetDelete(); 14 | } 15 | } -------------------------------------------------------------------------------- /SqlQueryBuilder/ISqlTranslator.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq.Expressions; 4 | 5 | namespace SqlQueryBuilder 6 | { 7 | public interface ISqlTranslator 8 | { 9 | bool HasError { get; } 10 | bool AddTable(Type type, string tableAlias); 11 | string GetFirstTranslation(Type type, Expression lambda, string tableAlias); 12 | IEnumerable Translate(Type type, Expression lambda, string tableAlias); 13 | string Translate(Type type, string col, string tableAlias); 14 | } 15 | } -------------------------------------------------------------------------------- /SqlQueryBuilder/Insert/IQueryBuilderInsertInto.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq.Expressions; 3 | 4 | namespace SqlQueryBuilder.Insert 5 | { 6 | public interface IQueryBuilderInsertInto 7 | { 8 | IQueryBuilderValues InsertInto(Expression> lambda); 9 | } 10 | } -------------------------------------------------------------------------------- /SqlQueryBuilder/Insert/IQueryBuilderValues.cs: -------------------------------------------------------------------------------- 1 | namespace SqlQueryBuilder.Insert 2 | { 3 | public interface IQueryBuilderValues 4 | { 5 | IBuildQuery Values(params string[] values); 6 | } 7 | } -------------------------------------------------------------------------------- /SqlQueryBuilder/Insert/InsertQueryBuilder.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Linq.Expressions; 5 | 6 | namespace SqlQueryBuilder.Insert 7 | { 8 | internal class InsertQueryBuilder : IQueryBuilderInsertInto, IQueryBuilderValues, IBuildQuery 9 | { 10 | private readonly ISqlTranslator _translator; 11 | private KeyValuePair TableInto; 12 | private readonly List Columns = new List(); 13 | private readonly List ValueList = new List(); 14 | 15 | private InsertQueryBuilder SkipIfError(Action action) 16 | { 17 | if (!_translator.HasError) 18 | action(); 19 | 20 | return this; 21 | } 22 | 23 | public InsertQueryBuilder(ISqlTranslator translator) 24 | { 25 | _translator = translator; 26 | } 27 | 28 | public IQueryBuilderValues InsertInto(Expression> lambda) 29 | { 30 | var type = typeof(T); 31 | var tableAliasKey = type.Name; 32 | TableInto = new KeyValuePair(tableAliasKey, type); 33 | _translator.AddTable(type, TableInto.Key); 34 | 35 | Columns.AddRange(_translator.Translate(typeof(T), lambda, null)); 36 | return this; 37 | } 38 | 39 | public IBuildQuery Values(params string[] values) 40 | { 41 | ValueList.AddRange(values); 42 | return this; 43 | } 44 | 45 | public bool TryBuild(out string query) 46 | { 47 | query = string.Empty; 48 | 49 | if (ValueList.Count != Columns.Count) 50 | return false; 51 | 52 | query = $"INSERT INTO [{TableInto.Key}] ({string.Join(", ", Columns.Select(c => $"{c}"))}) " 53 | + $"VALUES ({string.Join(", ", ValueList)})"; 54 | 55 | return true; 56 | } 57 | } 58 | } -------------------------------------------------------------------------------- /SqlQueryBuilder/Select/AggregateFunctions.cs: -------------------------------------------------------------------------------- 1 | namespace SqlQueryBuilder 2 | { 3 | public static class AggregateFunctions 4 | { 5 | public const string AVG = "AVG"; 6 | public const string MIN = "MIN"; 7 | public const string MAX = "MAX"; 8 | public const string SUM = "SUM"; 9 | public const string COUNT = "COUNT"; 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /SqlQueryBuilder/Select/IQueryBuilderGroupBy.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq.Expressions; 3 | 4 | namespace SqlQueryBuilder.Select 5 | { 6 | public interface IQueryBuilderGroupBy: IQueryBuilderOrderBy 7 | { 8 | IQueryBuilderGroupBy GroupBy(Expression> lambda, string tableAlias = null); 9 | } 10 | } -------------------------------------------------------------------------------- /SqlQueryBuilder/Select/IQueryBuilderJoinOrSelect.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq.Expressions; 3 | 4 | namespace SqlQueryBuilder.Select 5 | { 6 | public interface IQueryBuilderJoinOrSelect : IQueryBuilderSelectOnly 7 | { 8 | IQueryBuilderJoinOrSelect Join(Expression> key1, Expression> key2, string table1Alias = null, string table2Alias = null, string joinType = null); 9 | IQueryBuilderJoinOrSelect LeftJoin(Expression> key1, Expression> key2, string table1Alias = null, string table2Alias = null); 10 | IQueryBuilderJoinOrSelect RightJoin(Expression> key1, Expression> key2, string table1Alias = null, string table2Alias = null); 11 | IQueryBuilderJoinOrSelect FullOuterJoin(Expression> key1, Expression> key2, string table1Alias = null, string table2Alias = null); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /SqlQueryBuilder/Select/IQueryBuilderOrderBy.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq.Expressions; 3 | 4 | namespace SqlQueryBuilder.Select 5 | { 6 | public interface IQueryBuilderOrderBy : IBuildQuery 7 | { 8 | IQueryBuilderOrderBy OrderBy(Expression> lambda, bool desc = false, string tableNameAs = null); 9 | IQueryBuilderOrderBy Skip(int skip); 10 | IQueryBuilderOrderBy Take(int take); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /SqlQueryBuilder/Select/IQueryBuilderSelect.cs: -------------------------------------------------------------------------------- 1 | namespace SqlQueryBuilder.Select 2 | { 3 | public interface IQueryBuilderSelectOrWhere: IQueryBuilderSelectOnly, IQueryBuilderWhere {} 4 | } 5 | -------------------------------------------------------------------------------- /SqlQueryBuilder/Select/IQueryBuilderSelectFrom.cs: -------------------------------------------------------------------------------- 1 | namespace SqlQueryBuilder.Select 2 | { 3 | public interface IQueryBuilderSelectFrom 4 | { 5 | IQueryBuilderJoinOrSelect From(string tableAlias = null); 6 | } 7 | } -------------------------------------------------------------------------------- /SqlQueryBuilder/Select/IQueryBuilderSelectOnly.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq.Expressions; 3 | 4 | namespace SqlQueryBuilder.Select 5 | { 6 | public interface IQueryBuilderSelectOnly 7 | { 8 | IQueryBuilderSelectOrWhere Top(int i); 9 | IQueryBuilderSelectOrWhere Select(Expression> lambda, string tableAlias = null); 10 | IQueryBuilderSelectOrWhere SelectAll(string tableAlias = null); 11 | IQueryBuilderSelectOrWhere SelectAs(ISelectBuilder selectBuilder, string alias); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /SqlQueryBuilder/Select/IQueryBuilderWhere.cs: -------------------------------------------------------------------------------- 1 | using SqlQueryBuilder.Where; 2 | using System; 3 | 4 | namespace SqlQueryBuilder.Select 5 | { 6 | public interface IQueryBuilderWhere: IQueryBuilderGroupBy 7 | { 8 | IQueryBuilderWhere Where(Func compareFactory); 9 | IQueryBuilderWhere WhereFactory(Func createBuilder); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /SqlQueryBuilder/Select/ISelectBuilder.cs: -------------------------------------------------------------------------------- 1 | namespace SqlQueryBuilder.Select 2 | { 3 | public interface ISelectBuilder 4 | { 5 | bool TryBuild(ISqlTranslator translator, out string select); 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /SqlQueryBuilder/Select/SelectBuilder.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq.Expressions; 3 | 4 | namespace SqlQueryBuilder.Select 5 | { 6 | public abstract class SelectBuilderBase : ISelectBuilder 7 | { 8 | protected abstract string CreateClause(ISqlTranslator translator); 9 | 10 | public bool TryBuild(ISqlTranslator translator, out string selectClause) 11 | { 12 | selectClause = ""; 13 | if (translator.HasError) 14 | return false; 15 | 16 | selectClause = CreateClause(translator); 17 | if (string.IsNullOrEmpty(selectClause)) 18 | return false; 19 | 20 | return true; 21 | } 22 | } 23 | 24 | public abstract class SelectBuilder : SelectBuilderBase 25 | { 26 | private Func _selectExpression; 27 | 28 | public ISelectBuilder Select(Expression> exp, string tableAlias = null) 29 | { 30 | _selectExpression = t => t.GetFirstTranslation(typeof(A), exp, tableAlias); 31 | return this; 32 | } 33 | 34 | protected override string CreateClause(ISqlTranslator translator) => CreateClause(_selectExpression(translator)); 35 | 36 | protected abstract string CreateClause(string selector); 37 | } 38 | 39 | public abstract class SelectBuilder : SelectBuilderBase 40 | { 41 | private Func _firstSelectExpression; 42 | private Func _secondSelectExpression; 43 | 44 | public ISelectBuilder Select(Expression> exp1, Expression> exp2, string tableAlias1 = null, string tableAlias2 = null) 45 | { 46 | _firstSelectExpression = t => t.GetFirstTranslation(typeof(A), exp1, tableAlias1); 47 | _secondSelectExpression = t => t.GetFirstTranslation(typeof(B), exp2, tableAlias2); 48 | return this; 49 | } 50 | 51 | protected override string CreateClause(ISqlTranslator translator) => 52 | CreateClause(_firstSelectExpression(translator), _secondSelectExpression(translator)); 53 | 54 | protected abstract string CreateClause(string selector1, string selector2); 55 | } 56 | 57 | public abstract class SelectBuilder : SelectBuilderBase 58 | { 59 | private Func _firstSelectExpression; 60 | private Func _secondSelectExpression; 61 | private Func _thirdSelectExpression; 62 | 63 | public ISelectBuilder Select(Expression> exp1, Expression> exp2, Expression> exp3, 64 | string tableAlias1 = null, string tableAlias2 = null, string tableAlias3 = null) 65 | { 66 | _firstSelectExpression = t => t.GetFirstTranslation(typeof(A), exp1, tableAlias1); 67 | _secondSelectExpression = t => t.GetFirstTranslation(typeof(A), exp2, tableAlias2); 68 | _thirdSelectExpression = t => t.GetFirstTranslation(typeof(A), exp2, tableAlias3); ; 69 | return this; 70 | } 71 | 72 | protected override string CreateClause(ISqlTranslator translator) => 73 | CreateClause(_firstSelectExpression(translator), _secondSelectExpression(translator), _thirdSelectExpression(translator)); 74 | 75 | protected abstract string CreateClause(string selector1, string selector2, string selector3); 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /SqlQueryBuilder/Select/SelectBuilders.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace SqlQueryBuilder.Select 4 | { 5 | public enum DateDiffType 6 | { 7 | SECOND, 8 | MINUTE, 9 | HOUR, 10 | DAY, 11 | MONTH, 12 | YEAR 13 | } 14 | 15 | public class DateDiff : SelectBuilder 16 | { 17 | private readonly DateDiffType type; 18 | private readonly string compareTo; 19 | 20 | public DateDiff(DateDiffType type, string compareTo) 21 | { 22 | this.type = type; 23 | this.compareTo = compareTo; 24 | } 25 | 26 | protected override string CreateClause(string column) 27 | { 28 | return $"datediff({type.ToString()}, {column}, '{compareTo}')"; 29 | } 30 | } 31 | 32 | public class DateDiff2 : SelectBuilder 33 | { 34 | private readonly DateDiffType type; 35 | 36 | public DateDiff2(DateDiffType type) 37 | { 38 | this.type = type; 39 | } 40 | 41 | protected override string CreateClause(string column1, string column2) 42 | { 43 | return $"datediff({type.ToString()}, '{column1}', {column2})"; 44 | } 45 | } 46 | 47 | public class Aggregate : SelectBuilder 48 | { 49 | private readonly string aggregateFunction; 50 | 51 | public Aggregate(string aggregateFunction) 52 | { 53 | this.aggregateFunction = aggregateFunction; 54 | } 55 | 56 | protected override string CreateClause(string column) 57 | { 58 | return $"{aggregateFunction}({column})"; 59 | } 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /SqlQueryBuilder/Select/SelectQueryBuilder.cs: -------------------------------------------------------------------------------- 1 | using SqlQueryBuilder.Where; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Linq.Expressions; 5 | 6 | namespace SqlQueryBuilder.Select 7 | { 8 | public class SelectQueryBuilder : IQueryBuilderSelectFrom, IQueryBuilderJoinOrSelect, IQueryBuilderSelectOrWhere 9 | { 10 | private readonly Func _createWhereBuilderFactory; 11 | private readonly Func _compareFactory; 12 | private ISqlTranslator _translator = new SqlTranslator(); 13 | 14 | private KeyValuePair TableFrom { get; set; } 15 | private int TopClause = 0; 16 | private List SelectClauses = new List(); 17 | private List WhereClauses = new List(); 18 | private List JoinClauses = new List(); 19 | private List OrderByClauses = new List(); 20 | private List GroupByClauses = new List(); 21 | private int? SkipClause = null; 22 | private int? TakeClause = null; 23 | 24 | private SelectQueryBuilder SkipIfError(Action action) 25 | { 26 | if (!_translator.HasError) 27 | action(); 28 | 29 | return this; 30 | } 31 | 32 | public SelectQueryBuilder(ISqlTranslator translator, Func createWhereBuilderFactory, Func compareFactory) 33 | { 34 | this._translator = translator; 35 | this._createWhereBuilderFactory = createWhereBuilderFactory; 36 | this._compareFactory = compareFactory; 37 | } 38 | 39 | public IQueryBuilderJoinOrSelect From(string tableAlias = null) 40 | { 41 | var type = typeof(T); 42 | var tableAliasKey = tableAlias ?? type.Name; 43 | TableFrom = new KeyValuePair(tableAliasKey, type); 44 | _translator.AddTable(type, TableFrom.Key); 45 | 46 | return this; 47 | } 48 | 49 | public IQueryBuilderJoinOrSelect Join(Expression> key1, Expression> key2, string table1Alias = null, 50 | string table2Alias = null, string joinType = null) => SkipIfError(() => 51 | { 52 | var table1Type = typeof(T); 53 | var joinTable2Type = typeof(U); 54 | var joinTable2Name = string.IsNullOrEmpty(table2Alias) ? joinTable2Type.Name : table2Alias; 55 | 56 | if (_translator.AddTable(joinTable2Type, joinTable2Name) == false) 57 | return; 58 | 59 | var joinTypeStr = (string.IsNullOrEmpty(joinType) ? string.Empty : $"{joinType} ") + "JOIN"; 60 | var joinTableStr = $"[{joinTable2Type.Name}]" + (!string.IsNullOrEmpty(table2Alias) ? $" AS [{table2Alias}]" : string.Empty); 61 | var joinOnStr = $"{_translator.GetFirstTranslation(table1Type, key1, table1Alias)} = {_translator.GetFirstTranslation(joinTable2Type, key2, joinTable2Name)}"; 62 | var joinClause = $"{joinTypeStr} {joinTableStr} ON {joinOnStr}"; 63 | 64 | JoinClauses.Add(joinClause); 65 | }); 66 | 67 | public IQueryBuilderJoinOrSelect LeftJoin(Expression> key1, Expression> key2, string table1Alias = null, 68 | string table2Alias = null) 69 | { 70 | return Join(key1, key2, table1Alias, table2Alias, "LEFT"); 71 | } 72 | 73 | public IQueryBuilderJoinOrSelect RightJoin(Expression> key1, Expression> key2, string table1Alias = null, 74 | string table2Alias = null) 75 | { 76 | return Join(key1, key2, table1Alias, table2Alias, "RIGHT"); 77 | } 78 | 79 | public IQueryBuilderJoinOrSelect FullOuterJoin(Expression> key1, Expression> key2, string table1Alias = null, 80 | string table2Alias = null) 81 | { 82 | return Join(key1, key2, table1Alias, table2Alias, "FULL OUTER"); 83 | } 84 | 85 | public IQueryBuilderSelectOrWhere Top(int i) => SkipIfError(() => 86 | { 87 | TopClause = i; 88 | }); 89 | 90 | public IQueryBuilderSelectOrWhere SelectAll(string tableAlias = null) => 91 | SkipIfError(() => 92 | SelectClauses.Add(_translator.Translate(typeof(T), "*", tableAlias)) 93 | ); 94 | 95 | public IQueryBuilderSelectOrWhere Select(Expression> lambda, string tableAlias = null) => 96 | SkipIfError(() => 97 | SelectClauses.AddRange(_translator.Translate(typeof(T), lambda, tableAlias)) 98 | ); 99 | 100 | public IQueryBuilderSelectOrWhere SelectAs(ISelectBuilder selectBuilder, string alias) => 101 | SkipIfError(() => 102 | { 103 | var success = selectBuilder 104 | .TryBuild(_translator, out string selectClause); 105 | 106 | if (success) 107 | SelectClauses.Add($"{selectClause} AS [{alias}]"); 108 | }); 109 | 110 | public IQueryBuilderWhere Where(Func compareBuilderFactory) => 111 | SkipIfError(() => 112 | { 113 | var success = compareBuilderFactory(_compareFactory()) 114 | .TryBuild(_translator, out string comparison); 115 | 116 | if (success) 117 | WhereClauses.Add(comparison); 118 | }); 119 | 120 | public IQueryBuilderWhere WhereFactory(Func whereBuilder) => 121 | SkipIfError(() => 122 | { 123 | var success = whereBuilder(_createWhereBuilderFactory()).TryBuild(_translator, out var whereClause); 124 | 125 | if (success) 126 | WhereClauses.Add(whereClause); 127 | }); 128 | 129 | public IQueryBuilderGroupBy GroupBy(Expression> lambda, string tableAlias = null) => 130 | SkipIfError(() => 131 | GroupByClauses.Add(_translator.GetFirstTranslation(typeof(T), lambda, tableAlias)) 132 | ); 133 | 134 | public IQueryBuilderOrderBy OrderBy(Expression> lambda, bool desc = false, string tableAlias = null) => 135 | SkipIfError(() => 136 | OrderByClauses.Add($"{_translator.GetFirstTranslation(typeof(T), lambda, tableAlias)}{(desc ? " DESC" : string.Empty)}") 137 | ); 138 | 139 | public IQueryBuilderOrderBy Skip(int skip) => SkipIfError(() => 140 | { 141 | SkipIfError(() => SkipClause = skip); 142 | }); 143 | 144 | public IQueryBuilderOrderBy Take(int take) => SkipIfError(() => 145 | { 146 | SkipIfError(() => TakeClause = take); 147 | }); 148 | 149 | public bool TryBuild(out string query) 150 | { 151 | query = string.Empty; 152 | 153 | if (Validate() == false) 154 | return false; 155 | 156 | var tableName = TableFrom.Value.Name; 157 | var tableAlias = TableFrom.Key; 158 | 159 | const string separator = ", "; 160 | var selectString = string.Join(separator, SelectClauses); 161 | 162 | query = $"SELECT {(TopClause > 0 ? $"TOP {TopClause} " : "")}{selectString} FROM [{tableName}] {(tableAlias != tableName ? $"AS [{tableAlias}] " : string.Empty)}" 163 | + (JoinClauses.Count > 0 ? string.Join(" ", JoinClauses) + " " : string.Empty) 164 | + (WhereClauses.Count > 0 ? $"WHERE {string.Join(" AND ", WhereClauses)} " : string.Empty) 165 | + (GroupByClauses.Count > 0 ? $"GROUP BY {string.Join(separator, GroupByClauses)} " : string.Empty) 166 | + (OrderByClauses.Count > 0 ? $"ORDER BY {string.Join(separator, OrderByClauses)} " : string.Empty) 167 | + (SkipClause != null ? $"OFFSET {SkipClause} ROWS " : string.Empty) 168 | + (TakeClause != null ? $"FETCH NEXT {TakeClause} ROWS ONLY " : string.Empty); 169 | 170 | query = query.Trim(); 171 | 172 | return true; 173 | } 174 | 175 | private bool Validate() 176 | { 177 | if (_translator.HasError) 178 | return false; 179 | 180 | if (SelectClauses.Count == 0) 181 | return false; 182 | 183 | if (TopClause < 0) 184 | return false; 185 | 186 | if (SkipClause < 0) 187 | return false; 188 | 189 | if (TakeClause < 0) 190 | return false; 191 | 192 | if (SkipClause == null && TakeClause != null) 193 | return false; 194 | 195 | return true; 196 | } 197 | } 198 | } 199 | -------------------------------------------------------------------------------- /SqlQueryBuilder/SqlQueryBuilder.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netstandard2.0 5 | Simple builder interface to build SQL select queries arround your POCO classes. 6 | 7 | Rémi Mercier 8 | SqlQueryBuilder 9 | true 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /SqlQueryBuilder/SqlQueryBuilderFactory.cs: -------------------------------------------------------------------------------- 1 | using SqlQueryBuilder.Delete; 2 | using SqlQueryBuilder.Insert; 3 | using SqlQueryBuilder.Select; 4 | using SqlQueryBuilder.Update; 5 | using SqlQueryBuilder.Where; 6 | using System; 7 | 8 | namespace SqlQueryBuilder 9 | { 10 | public class SqlQueryBuilderFactory : IQueryBuilderFactory 11 | { 12 | private readonly Func _translatorFactory; 13 | private readonly Func _createWhereBuilderFactory; 14 | private readonly Func _compareFactory; 15 | 16 | public SqlQueryBuilderFactory(Func translatorFactory, Func createWhereBuilderFactory, Func compareFactory) 17 | { 18 | this._translatorFactory = translatorFactory; 19 | this._createWhereBuilderFactory = createWhereBuilderFactory; 20 | this._compareFactory = compareFactory; 21 | } 22 | 23 | public IQueryBuilderSelectFrom GetSelect() 24 | { 25 | return new SelectQueryBuilder(_translatorFactory(), _createWhereBuilderFactory, _compareFactory); 26 | } 27 | 28 | public IQueryBuilderUpdateFrom GetUpdate() 29 | { 30 | return new UpdateQueryBuilder(_translatorFactory(), _createWhereBuilderFactory, _compareFactory); 31 | } 32 | 33 | public IQueryBuilderInsertInto GetInsert() 34 | { 35 | return new InsertQueryBuilder(_translatorFactory()); 36 | } 37 | 38 | public IQueryBuilderDeleteFrom GetDelete() 39 | { 40 | return new DeleteQueryBuilder(_translatorFactory(), _createWhereBuilderFactory, _compareFactory); 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /SqlQueryBuilder/SqlTranslator.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Linq.Expressions; 5 | 6 | namespace SqlQueryBuilder 7 | { 8 | public class SqlTranslator : ISqlTranslator 9 | { 10 | public bool HasError { get; private set; } = false; 11 | private readonly Dictionary Tables = new Dictionary(); 12 | 13 | public bool AddTable(Type type, string tableAlias = null) 14 | { 15 | var alias = tableAlias ?? type.Name; 16 | if (Tables.ContainsKey(alias)) 17 | { 18 | HasError = true; 19 | return false; 20 | } 21 | 22 | Tables.Add(alias, type); 23 | return true; 24 | } 25 | 26 | public string Translate(Type type, string col, string tableAlias) 27 | { 28 | KeyValuePair kv = Tables.FirstOrDefault(x => 29 | { 30 | if (string.IsNullOrEmpty(tableAlias)) 31 | return x.Value.Name == type.Name; 32 | else 33 | return x.Key == tableAlias; 34 | }); 35 | 36 | if (kv.Key != null) 37 | return $"[{kv.Key}].{(col == "*" ? "*" : $"[{col}]")}"; 38 | else 39 | { 40 | HasError = true; 41 | return string.Empty; 42 | } 43 | } 44 | 45 | public IEnumerable Translate(Type type, Expression expression, string tableAlias) 46 | { 47 | return NameOf(expression).Select(x => Translate(type, x, tableAlias)); 48 | } 49 | 50 | public string GetFirstTranslation(Type type, Expression expression, string tableAlias) 51 | { 52 | return NameOf(expression).Select(x => Translate(type, x, tableAlias)).FirstOrDefault(); 53 | } 54 | 55 | private IEnumerable NameOf(Expression expression) 56 | { 57 | if (expression is LambdaExpression lambda) 58 | return NameOf(lambda.Body); 59 | else if (expression is UnaryExpression unaryExpression) 60 | return NameOf((MemberExpression)unaryExpression.Operand); 61 | else if (expression is NewExpression newExpression) 62 | return ((NewExpression)expression).Members.Select(x => x.Name); 63 | else if (expression is MemberExpression memberExpression) 64 | return new[] { memberExpression.Member.Name }; 65 | else 66 | { 67 | HasError = true; 68 | return new string[] { }; 69 | } 70 | } 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /SqlQueryBuilder/Update/IQueryBuilderJoinOrSet.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq.Expressions; 3 | 4 | namespace SqlQueryBuilder.Update 5 | { 6 | public interface IQueryBuilderJoinOrSet : IQueryBuilderSet 7 | { 8 | IQueryBuilderJoinOrSet Join(Expression> key1, Expression> key2, string table1Alias = null, string table2Alias = null, string joinType = null); 9 | IQueryBuilderJoinOrSet LeftJoin(Expression> key1, Expression> key2, string table1Alias = null, string table2Alias = null); 10 | IQueryBuilderJoinOrSet RightJoin(Expression> key1, Expression> key2, string table1Alias = null, string table2Alias = null); 11 | IQueryBuilderJoinOrSet FullOuterJoin(Expression> key1, Expression> key2, string table1Alias = null, string table2Alias = null); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /SqlQueryBuilder/Update/IQueryBuilderSet.cs: -------------------------------------------------------------------------------- 1 | using SqlQueryBuilder.Where; 2 | using System; 3 | using System.Linq.Expressions; 4 | 5 | namespace SqlQueryBuilder.Update 6 | { 7 | public interface IQueryBuilderSet 8 | { 9 | IQueryBuilderWhereOrBuild Set(Expression> lambda, string value, string tableAlias = null); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /SqlQueryBuilder/Update/IQueryBuilderUpdateFrom.cs: -------------------------------------------------------------------------------- 1 | namespace SqlQueryBuilder.Update 2 | { 3 | public interface IQueryBuilderUpdateFrom 4 | { 5 | IQueryBuilderJoinOrSet From(string tableAlias = null); 6 | } 7 | } -------------------------------------------------------------------------------- /SqlQueryBuilder/Update/UpdateQueryBuilder.cs: -------------------------------------------------------------------------------- 1 | using SqlQueryBuilder.Where; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Linq.Expressions; 5 | 6 | namespace SqlQueryBuilder.Update 7 | { 8 | public class UpdateQueryBuilder : IQueryBuilderUpdateFrom 9 | { 10 | private readonly ISqlTranslator _translator; 11 | private readonly Func _createWhereBuilderFactory; 12 | private readonly Func _compareFactory; 13 | 14 | public UpdateQueryBuilder(ISqlTranslator translator, Func createWhereBuilderFactory, Func compareFactory) 15 | { 16 | _translator = translator; 17 | _createWhereBuilderFactory = createWhereBuilderFactory; 18 | _compareFactory = compareFactory; 19 | } 20 | 21 | public IQueryBuilderJoinOrSet From(string tableAlias = null) 22 | { 23 | return new UpdateQueryBuilder(_translator, _createWhereBuilderFactory, _compareFactory, tableAlias); 24 | } 25 | } 26 | 27 | public class UpdateQueryBuilder: IQueryBuilderJoinOrSet, IQueryBuilderWhereOrBuild 28 | { 29 | private KeyValuePair TableFrom; 30 | private List JoinClauses = new List(); 31 | private List SetClauses = new List(); 32 | private List WhereClauses = new List(); 33 | private readonly ISqlTranslator _translator; 34 | private readonly Func _createWhereBuilderFactory; 35 | private readonly Func _compareFactory; 36 | 37 | private UpdateQueryBuilder SkipIfError(Action action) 38 | { 39 | if (!_translator.HasError) 40 | action(); 41 | 42 | return this; 43 | } 44 | 45 | public UpdateQueryBuilder(ISqlTranslator translator, Func createWhereBuilderFactory, Func compareFactory, string tableAlias = null) 46 | { 47 | _translator = translator; 48 | _createWhereBuilderFactory = createWhereBuilderFactory; 49 | _compareFactory = compareFactory; 50 | 51 | var type = typeof(T); 52 | var tableAliasKey = tableAlias ?? type.Name; 53 | TableFrom = new KeyValuePair(tableAliasKey, type); 54 | _translator.AddTable(type, TableFrom.Key); 55 | } 56 | 57 | public IQueryBuilderJoinOrSet Join(Expression> key1, Expression> key2, string table1Alias = null, 58 | string table2Alias = null, string joinType = null) => SkipIfError(() => 59 | { 60 | var table1Type = typeof(U); 61 | var joinTable2Type = typeof(V); 62 | var joinTable2Name = string.IsNullOrEmpty(table2Alias) ? joinTable2Type.Name : table2Alias; 63 | 64 | if (_translator.AddTable(joinTable2Type, joinTable2Name) == false) 65 | return; 66 | 67 | var joinTypeStr = (string.IsNullOrEmpty(joinType) ? string.Empty : $"{joinType}") + "JOIN"; 68 | var joinTableStr = $"[{joinTable2Type.Name}]" + (!string.IsNullOrEmpty(table2Alias) ? $" AS [{table2Alias}]" : string.Empty); 69 | var joinOnStr = $"{_translator.GetFirstTranslation(table1Type, key1, table1Alias)} = {_translator.GetFirstTranslation(joinTable2Type, key2, joinTable2Name)}"; 70 | var joinClause = $"{joinTypeStr} {joinTableStr} ON {joinOnStr}"; 71 | 72 | JoinClauses.Add(joinClause); 73 | }); 74 | 75 | public IQueryBuilderJoinOrSet LeftJoin(Expression> key1, Expression> key2, string table1Alias = null, 76 | string table2Alias = null) 77 | { 78 | return Join(key1, key2, table1Alias, table2Alias, "LEFT"); 79 | } 80 | 81 | public IQueryBuilderJoinOrSet RightJoin(Expression> key1, Expression> key2, string table1Alias = null, 82 | string table2Alias = null) 83 | { 84 | return Join(key1, key2, table1Alias, table2Alias, "RIGHT"); 85 | } 86 | 87 | public IQueryBuilderJoinOrSet FullOuterJoin(Expression> key1, Expression> key2, string table1Alias = null, 88 | string table2Alias = null) 89 | { 90 | return Join(key1, key2, table1Alias, table2Alias, "FULL OUTER"); 91 | } 92 | 93 | public IQueryBuilderWhereOrBuild Set(Expression> lambda, string value, string tableAlias = null) 94 | => SkipIfError(() => 95 | { 96 | SetClauses.Add($"{_translator.GetFirstTranslation(typeof(T), lambda, tableAlias)} = {value}"); 97 | }); 98 | 99 | public IQueryBuilderWhereOrBuild Where(Func compareBuilderFactory) => 100 | SkipIfError(() => 101 | { 102 | var success = compareBuilderFactory(_compareFactory()) 103 | .TryBuild(_translator, out string comparison); 104 | 105 | if (success) 106 | WhereClauses.Add(comparison); 107 | }); 108 | 109 | public IQueryBuilderWhereOrBuild WhereFactory(Func whereBuilder) => 110 | SkipIfError(() => 111 | { 112 | var success = whereBuilder(_createWhereBuilderFactory()).TryBuild(_translator, out var whereClause); 113 | 114 | if (success) 115 | WhereClauses.Add(whereClause); 116 | }); 117 | 118 | public bool TryBuild(out string query) 119 | { 120 | query = string.Empty; 121 | 122 | if (Validate() == false) 123 | return false; 124 | 125 | var tableName = TableFrom.Value.Name; 126 | var tableAlias = TableFrom.Key; 127 | 128 | const string separator = ", "; 129 | var setString = string.Join(separator, SetClauses); 130 | 131 | query = $"UPDATE [{tableAlias}] SET {setString} FROM [{tableName}] {(tableAlias != tableName ? $"[{tableAlias}] " : string.Empty)}" 132 | + (JoinClauses.Count > 0 ? string.Join(" ", JoinClauses) + " " : string.Empty) 133 | + (WhereClauses.Count > 0 ? $"WHERE {string.Join(" AND ", WhereClauses)} " : string.Empty); 134 | 135 | query = query.Trim(); 136 | 137 | return true; 138 | } 139 | 140 | private bool Validate() 141 | { 142 | if (_translator.HasError) 143 | return false; 144 | 145 | if (SetClauses.Count == 0) 146 | return false; 147 | 148 | return true; 149 | } 150 | } 151 | } -------------------------------------------------------------------------------- /SqlQueryBuilder/Where/Comparator.cs: -------------------------------------------------------------------------------- 1 | using SqlQueryBuilder.Select; 2 | using System; 3 | using System.Linq.Expressions; 4 | 5 | namespace SqlQueryBuilder.Where 6 | { 7 | public class Comparator : ICompare, ICompareWith, ICompareBuilder 8 | { 9 | private delegate bool TranslateDelegate(ISqlTranslator translator, out string str); 10 | 11 | private TranslateDelegate _first; 12 | private TranslateDelegate _second; 13 | private string _operator; 14 | 15 | public ICompareWith Compare(string val) 16 | { 17 | bool translateFunction (ISqlTranslator translator, out string str) 18 | { 19 | str = val; 20 | return !translator.HasError; 21 | } 22 | 23 | _first = translateFunction; 24 | return this; 25 | } 26 | 27 | public ICompareWith Compare(Expression> lambda, string tableAlias = null) 28 | { 29 | bool translateFunction(ISqlTranslator translator, out string str) 30 | { 31 | str = translator.GetFirstTranslation(typeof(U), lambda, tableAlias); 32 | return !translator.HasError; 33 | } 34 | 35 | _first = translateFunction; 36 | return this; 37 | } 38 | 39 | public ICompareWith Compare(ISelectBuilder selectBuilder) 40 | { 41 | bool translateFunction(ISqlTranslator translator, out string str) 42 | { 43 | return selectBuilder.TryBuild(translator, out str) && !translator.HasError; 44 | } 45 | 46 | _first = translateFunction; 47 | return this; 48 | } 49 | 50 | public ICompareBuilder With(string op, string val) 51 | { 52 | _operator = op; 53 | bool translateFunction(ISqlTranslator translator, out string str) 54 | { 55 | str = val; 56 | return !translator.HasError; 57 | } 58 | 59 | _second = translateFunction; 60 | return this; 61 | } 62 | 63 | public ICompareBuilder With(string op, Expression> lambda, string tableAlias = null) 64 | { 65 | _operator = op; 66 | bool translateFunction(ISqlTranslator translator, out string str) 67 | { 68 | str = translator.GetFirstTranslation(typeof(U), lambda, tableAlias); 69 | return !translator.HasError; 70 | } 71 | 72 | _second = translateFunction; 73 | return this; 74 | } 75 | 76 | public ICompareBuilder With(string op, ISelectBuilder selectBuilder) 77 | { 78 | _operator = op; 79 | bool translateFunction(ISqlTranslator translator, out string str) 80 | { 81 | return selectBuilder.TryBuild(translator, out str) && !translator.HasError; 82 | } 83 | 84 | _second = translateFunction; 85 | return this; 86 | } 87 | 88 | private string WrapWithParentheses(string str) => $"({str})"; 89 | 90 | public bool TryBuild(ISqlTranslator translator, out string comparison) 91 | { 92 | comparison = string.Empty; 93 | 94 | if ( 95 | !_first(translator, out string str1) || 96 | !_second(translator, out string str2) || 97 | string.IsNullOrEmpty(_operator)) 98 | { 99 | return false; 100 | } 101 | 102 | comparison = WrapWithParentheses($"{WrapWithParentheses(str1)} {_operator} {WrapWithParentheses(str2)}"); 103 | return true; 104 | } 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /SqlQueryBuilder/Where/ICompare.cs: -------------------------------------------------------------------------------- 1 | using SqlQueryBuilder.Select; 2 | using System; 3 | using System.Linq.Expressions; 4 | 5 | namespace SqlQueryBuilder.Where 6 | { 7 | public interface ICompare 8 | { 9 | ICompareWith Compare(ISelectBuilder selectBuilder); 10 | ICompareWith Compare(string val); 11 | ICompareWith Compare(Expression> lambda, string tableAlias = null); 12 | } 13 | 14 | public interface ICompareWith 15 | { 16 | ICompareBuilder With(string op, ISelectBuilder selectBuilder); 17 | ICompareBuilder With(string op, string val); 18 | ICompareBuilder With(string op, Expression> lambda, string tableAlias = null); 19 | } 20 | } -------------------------------------------------------------------------------- /SqlQueryBuilder/Where/ICompareBuilder.cs: -------------------------------------------------------------------------------- 1 | namespace SqlQueryBuilder.Where 2 | { 3 | public interface ICompareBuilder 4 | { 5 | bool TryBuild(ISqlTranslator translator, out string comparison); 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /SqlQueryBuilder/Where/IQueryBuilderWhereOrBuild.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace SqlQueryBuilder.Where 4 | { 5 | public interface IQueryBuilderWhereOrBuild : IBuildQuery 6 | { 7 | IQueryBuilderWhereOrBuild Where(Func compareFactory); 8 | IQueryBuilderWhereOrBuild WhereFactory(Func createBuilder); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /SqlQueryBuilder/Where/IWhereBuilder.cs: -------------------------------------------------------------------------------- 1 | namespace SqlQueryBuilder.Where 2 | { 3 | public interface IWhereBuilder 4 | { 5 | bool TryBuild(ISqlTranslator translator, out string where); 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /SqlQueryBuilder/Where/IWhereBuilderFactory.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace SqlQueryBuilder.Where 4 | { 5 | public interface IWhereBuilderFactory 6 | { 7 | IWhereBuilder Compare(Func compareFactory); 8 | IWhereBuilder Or(params Func[] conditions); 9 | IWhereBuilder And(params Func[] conditions); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /SqlQueryBuilder/Where/Operators.cs: -------------------------------------------------------------------------------- 1 | namespace SqlQueryBuilder.Where 2 | { 3 | public static class Operators 4 | { 5 | public const string GT = ">"; 6 | public const string GTE = ">="; 7 | public const string LT = "<"; 8 | public const string LTE = "<="; 9 | public const string EQ = "="; 10 | public const string NEQ = "<>"; 11 | public const string LIKE = "LIKE"; 12 | public const string IN = "IN"; 13 | public const string IS = "IS"; 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /SqlQueryBuilder/Where/WhereBuilderFactory.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | 4 | namespace SqlQueryBuilder.Where 5 | { 6 | public class WhereBuilderFactory: IWhereBuilderFactory, IWhereBuilder 7 | { 8 | private readonly Func _compareFactory; 9 | 10 | private Func _whereExpression { get; set; } 11 | 12 | public WhereBuilderFactory(Func compareFactory) 13 | { 14 | this._compareFactory = compareFactory; 15 | } 16 | 17 | public IWhereBuilder Compare(Func compareBuilderFactory) 18 | { 19 | _whereExpression = t => 20 | { 21 | if (compareBuilderFactory(_compareFactory()).TryBuild(t, out string comparison)) 22 | return comparison; 23 | return string.Empty; 24 | }; 25 | 26 | return this; 27 | } 28 | 29 | public IWhereBuilder Or(params Func[] conditions) => JoinConditions("OR", conditions); 30 | 31 | public IWhereBuilder And(params Func[] conditions) => JoinConditions("AND", conditions); 32 | 33 | private IWhereBuilder JoinConditions(string compare, params Func[] clauses) 34 | { 35 | _whereExpression = t => 36 | { 37 | var res = clauses.Select(condition => 38 | { 39 | var success = condition(new WhereBuilderFactory(_compareFactory)).TryBuild(t, out string whereClause); 40 | return new { success, whereClause }; 41 | }); 42 | 43 | if (res.Any(r => !r.success)) 44 | return ""; 45 | else 46 | return $"({string.Join($" {compare} ", res.Select(x => x.whereClause))})"; 47 | }; 48 | 49 | return this; 50 | } 51 | 52 | public bool TryBuild(ISqlTranslator translator, out string whereClause) 53 | { 54 | whereClause = string.Empty; 55 | if (translator.HasError) 56 | return false; 57 | 58 | whereClause = _whereExpression(translator); 59 | if (string.IsNullOrEmpty(whereClause)) 60 | return false; 61 | 62 | return true; 63 | } 64 | } 65 | } 66 | --------------------------------------------------------------------------------