├── .gitignore ├── LambdaSqlBuilder.Tests ├── Entities │ ├── Category.cs │ ├── Employee.cs │ ├── FakeGuid.cs │ ├── Order.cs │ ├── OrderDetails.cs │ └── Product.cs ├── ExpressionTests.cs ├── ExtraTests.cs ├── Infrastructure │ ├── Bootstrap.cs │ └── TestBase.cs ├── JoinSqlQueryTests.cs ├── LambdaSqlBuilder.Tests.csproj ├── LinqSqlQueryTests.cs ├── Properties │ └── AssemblyInfo.cs ├── SqlQueryTests.cs └── packages.config ├── LambdaSqlBuilder.sln ├── LambdaSqlBuilder ├── Adapter │ ├── ISqlAdapter.cs │ ├── SqlAdapterBase.cs │ ├── SqlServer2008Adapter.cs │ ├── SqlServer2012Adapter.cs │ └── SqlServerAdapterBase.cs ├── Builder │ ├── SqlQueryBuilder.cs │ ├── SqlQueryBuilderExpr.cs │ └── SqlQueryBuilderSpec.cs ├── LambdaSqlBuilder.csproj ├── Properties │ └── AssemblyInfo.cs ├── Resolver │ ├── ExpressionTree │ │ ├── LikeNode.cs │ │ ├── MemberNode.cs │ │ ├── Node.cs │ │ ├── OperationNode.cs │ │ ├── SingleOperationNode.cs │ │ └── ValueNode.cs │ ├── LambdaResolver.cs │ ├── LambdaResolverIsIn.cs │ ├── LambdaResolverQuery.cs │ ├── LambdaResolverSpec.cs │ └── LambdaResolverTree.cs ├── SqlLam.cs ├── SqlLamBase.cs ├── SqlLamColumnAttribute.cs ├── SqlLamTableAttribute.cs └── ValueObjects │ ├── LikeMethod.cs │ ├── SelectFunction.cs │ └── SqlAdapter.cs ├── License.txt └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | # Build Folders (you can keep bin if you'd like, to store dlls and pdbs) 2 | [Bb]in/ 3 | [Oo]bj/ 4 | 5 | # mstest test results 6 | TestResults 7 | 8 | ## Ignore Visual Studio temporary files, build results, and 9 | ## files generated by popular Visual Studio add-ons. 10 | 11 | # User-specific files 12 | *.suo 13 | *.user 14 | *.sln.docstates 15 | 16 | # Build results 17 | [Dd]ebug/ 18 | [Rr]elease/ 19 | x64/ 20 | *_i.c 21 | *_p.c 22 | *.ilk 23 | *.meta 24 | *.obj 25 | *.pch 26 | *.pdb 27 | *.pgc 28 | *.pgd 29 | *.rsp 30 | *.sbr 31 | *.tlb 32 | *.tli 33 | *.tlh 34 | *.tmp 35 | *.log 36 | *.vspscc 37 | *.vssscc 38 | .builds 39 | 40 | # Visual C++ cache files 41 | ipch/ 42 | *.aps 43 | *.ncb 44 | *.opensdf 45 | *.sdf 46 | 47 | # Visual Studio profiler 48 | *.psess 49 | *.vsp 50 | *.vspx 51 | 52 | # Guidance Automation Toolkit 53 | *.gpState 54 | 55 | # ReSharper is a .NET coding add-in 56 | _ReSharper* 57 | 58 | # NCrunch 59 | *.ncrunch* 60 | .*crunch*.local.xml 61 | 62 | # Installshield output folder 63 | [Ee]xpress 64 | 65 | # DocProject is a documentation generator add-in 66 | DocProject/buildhelp/ 67 | DocProject/Help/*.HxT 68 | DocProject/Help/*.HxC 69 | DocProject/Help/*.hhc 70 | DocProject/Help/*.hhk 71 | DocProject/Help/*.hhp 72 | DocProject/Help/Html2 73 | DocProject/Help/html 74 | 75 | # Click-Once directory 76 | publish 77 | 78 | # Publish Web Output 79 | *.Publish.xml 80 | 81 | # NuGet Packages Directory 82 | packages 83 | 84 | # Windows Azure Build Output 85 | csx 86 | *.build.csdef 87 | 88 | # Windows Store app package directory 89 | AppPackages/ 90 | 91 | # Others 92 | [Bb]in 93 | [Oo]bj 94 | sql 95 | TestResults 96 | [Tt]est[Rr]esult* 97 | *.Cache 98 | ClientBin 99 | [Ss]tyle[Cc]op.* 100 | ~$* 101 | *.dbmdl 102 | Generated_Code #added for RIA/Silverlight projects 103 | 104 | # Backup & report files from converting an old project file to a newer 105 | # Visual Studio version. Backup files are not needed, because we have git ;-) 106 | _UpgradeReport_Files/ 107 | Backup*/ 108 | UpgradeLog*.XML 109 | -------------------------------------------------------------------------------- /LambdaSqlBuilder.Tests/Entities/Category.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | namespace LambdaSqlBuilder.Tests.Entities 4 | { 5 | [SqlLamTable(Name = "Categories")] 6 | public class Category 7 | { 8 | [SqlLamColumn(Name = "Category ID")] 9 | public int CategoryId { get; set; } 10 | 11 | public int GetCategoryId() 12 | { 13 | return CategoryId; 14 | } 15 | 16 | [SqlLamColumn(Name = "Category Name")] 17 | public string CategoryName { get; set; } 18 | 19 | public List Products { get; set; } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /LambdaSqlBuilder.Tests/Entities/Employee.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | 6 | namespace LambdaSqlBuilder.Tests.Entities 7 | { 8 | [SqlLamTable(Name = "Employees")] 9 | public class Employee 10 | { 11 | [SqlLamColumn(Name = "Employee ID")] 12 | public int EmployeeId { get; set; } 13 | 14 | [SqlLamColumn(Name = "First Name")] 15 | public string FirstName { get; set; } 16 | 17 | [SqlLamColumn(Name = "Last Name")] 18 | public string LastName { get; set; } 19 | 20 | [SqlLamColumn(Name = "City")] 21 | public string City { get; set; } 22 | 23 | [SqlLamColumn(Name = "Title")] 24 | public string Title { get; set; } 25 | 26 | [SqlLamColumn(Name = "Birth Date")] 27 | public DateTime BirthDate { get; set; } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /LambdaSqlBuilder.Tests/Entities/FakeGuid.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | 6 | namespace LambdaSqlBuilder.Tests.Entities 7 | { 8 | class FakeGuid 9 | { 10 | public FakeGuid() 11 | { 12 | Id = Guid.NewGuid(); 13 | } 14 | 15 | public Guid? Id { get; set; } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /LambdaSqlBuilder.Tests/Entities/Order.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | 6 | namespace LambdaSqlBuilder.Tests.Entities 7 | { 8 | [SqlLamTable(Name = "Orders")] 9 | public class Order 10 | { 11 | [SqlLamColumn(Name = "Order ID")] 12 | public int OrderId { get; set; } 13 | 14 | [SqlLamColumn(Name = "Ship Name")] 15 | public string ShipName { get; set; } 16 | 17 | [SqlLamColumn(Name = "Ship Region")] 18 | public string ShipRegion { get; set; } 19 | 20 | [SqlLamColumn(Name = "Required Date")] 21 | public DateTime RequiredDate { get; set; } 22 | 23 | [SqlLamColumn(Name = "Shipped Date")] 24 | public DateTime ShippedDate { get; set; } 25 | 26 | public List Products { get; set; } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /LambdaSqlBuilder.Tests/Entities/OrderDetails.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | 6 | namespace LambdaSqlBuilder.Tests.Entities 7 | { 8 | [SqlLamTable(Name = "Order Details")] 9 | public class OrderDetails 10 | { 11 | [SqlLamColumn(Name = "Order ID")] 12 | public int OrderId { get; set; } 13 | 14 | [SqlLamColumn(Name = "Product ID")] 15 | public int ProductId { get; set; } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /LambdaSqlBuilder.Tests/Entities/Product.cs: -------------------------------------------------------------------------------- 1 |  2 | namespace LambdaSqlBuilder.Tests.Entities 3 | { 4 | [SqlLamTable(Name = "Products")] 5 | public class Product 6 | { 7 | [SqlLamColumn(Name = "Product ID")] 8 | public int ProductId { get; set; } 9 | 10 | public int GetProductId() 11 | { 12 | return ProductId; 13 | } 14 | 15 | [SqlLamColumn(Name = "Product Name")] 16 | public string ProductName { get; set; } 17 | 18 | [SqlLamColumn(Name = "English Name")] 19 | public string EnglishName { get; set; } 20 | 21 | [SqlLamColumn(Name = "Category ID")] 22 | public int CategoryId { get; set; } 23 | 24 | [SqlLamColumn(Name = "Unit Price")] 25 | public double UnitPrice { get; set; } 26 | 27 | [SqlLamColumn(Name = "Reorder Level")] 28 | public int ReorderLevel { get; set; } 29 | 30 | [SqlLamColumn(Name = "Reorder Level")] 31 | public int? NullableReorderLevel { get { return ReorderLevel; } } 32 | 33 | [SqlLamColumn(Name = "Discontinued")] 34 | public bool Discontinued { get; set; } 35 | 36 | public Category Category { get; set; } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /LambdaSqlBuilder.Tests/ExpressionTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using LambdaSqlBuilder.Tests.Entities; 6 | using LambdaSqlBuilder.Tests.Infrastructure; 7 | using NUnit.Framework; 8 | using Dapper; 9 | 10 | namespace LambdaSqlBuilder.Tests 11 | { 12 | public class ExpressionTests : TestBase 13 | { 14 | /// 15 | /// Find the products which name starts with 'To' 16 | /// 17 | [Test] 18 | public void FindByStringPrefix() 19 | { 20 | const string prefix = "To"; 21 | 22 | var query = new SqlLam(p => p.ProductName.StartsWith(prefix)); 23 | 24 | var result = Connection.Query(query.QueryString, query.QueryParameters).ToList(); 25 | 26 | Assert.AreEqual(2, result.Count); 27 | 28 | foreach (var product in result) 29 | { 30 | Assert.IsTrue(product.ProductName.StartsWith(prefix)); 31 | } 32 | } 33 | 34 | /// 35 | /// Find the products which name ends with 'ld' 36 | /// 37 | [Test] 38 | public void FindByStringSuffix() 39 | { 40 | const string suffix = "ld"; 41 | 42 | var query = new SqlLam(p => p.ProductName.EndsWith(suffix)); 43 | 44 | var result = Connection.Query(query.QueryString, query.QueryParameters).ToList(); 45 | 46 | Assert.AreEqual(2, result.Count); 47 | 48 | foreach (var product in result) 49 | { 50 | Assert.IsTrue(product.ProductName.EndsWith(suffix)); 51 | } 52 | } 53 | 54 | /// 55 | /// Find the products which name contains substring 'ge' 56 | /// 57 | [Test] 58 | public void FindByStringPart() 59 | { 60 | const string part = "ge"; 61 | 62 | var query = new SqlLam(p => p.ProductName.Contains(part)); 63 | 64 | var result = Connection.Query(query.QueryString, query.QueryParameters).ToList(); 65 | 66 | Assert.AreEqual(9, result.Count); 67 | 68 | foreach (var product in result) 69 | { 70 | Assert.IsTrue(product.ProductName.ToLower().Contains(part)); 71 | } 72 | } 73 | 74 | /// 75 | /// Find the products which name is equal to string 'Tofu' 76 | /// 77 | [Test] 78 | public void FindByStringEquality() 79 | { 80 | const string name = "Tofu"; 81 | 82 | var query = new SqlLam(p => p.ProductName.Equals(name)); 83 | 84 | var result = Connection.Query(query.QueryString, query.QueryParameters).Single(); 85 | 86 | Assert.AreEqual(name, result.ProductName); 87 | } 88 | 89 | /// 90 | /// Use an unsupported function to trigger the exception 91 | /// 92 | [Test] 93 | public void FindByInvalidFunction() 94 | { 95 | Assert.Throws(() => new SqlLam(p => p.ProductName.IsNormalized())); 96 | } 97 | 98 | /// 99 | /// Find the orders which ship region is null 100 | /// 101 | [Test] 102 | public void FindByNull() 103 | { 104 | var query = new SqlLam(o => o.ShipRegion == null); 105 | 106 | var result = Connection.Query(query.QueryString, query.QueryParameters).ToList(); 107 | 108 | Assert.AreEqual(671, result.Count); 109 | 110 | foreach (var order in result) 111 | { 112 | Assert.IsTrue(order.ShipRegion == null); 113 | } 114 | } 115 | 116 | /// 117 | /// Find the orders which ship region is not null 118 | /// 119 | [Test] 120 | public void FindByNotNull() 121 | { 122 | var query = new SqlLam(o => o.ShipRegion != null); 123 | 124 | var result = Connection.Query(query.QueryString, query.QueryParameters).ToList(); 125 | 126 | Assert.AreEqual(407, result.Count); 127 | 128 | foreach (var order in result) 129 | { 130 | Assert.IsTrue(order.ShipRegion != null); 131 | } 132 | } 133 | 134 | /// 135 | /// Find products with unit price greater than 10.5 136 | /// 137 | [Test] 138 | public void FindByDouble() 139 | { 140 | const double minUnitPrice = 10.5d; 141 | 142 | var query = new SqlLam(p => p.UnitPrice >= minUnitPrice); 143 | 144 | var result = Connection.Query(query.QueryString, query.QueryParameters).ToList(); 145 | 146 | Assert.AreEqual(63, result.Count); 147 | 148 | foreach (var product in result) 149 | { 150 | Assert.GreaterOrEqual(product.UnitPrice, minUnitPrice); 151 | } 152 | } 153 | 154 | /// 155 | /// Find products by nullable reorder level 156 | /// 157 | [Test] 158 | public void FindByNullableField() 159 | { 160 | var product = new Product() {ReorderLevel = 5}; 161 | 162 | var query = new SqlLam(p => p.NullableReorderLevel == product.NullableReorderLevel.Value); 163 | 164 | var result = Connection.Query(query.QueryString, query.QueryParameters).ToList(); 165 | 166 | Assert.AreEqual(8, result.Count); 167 | } 168 | 169 | /// 170 | /// Find discontinued products 171 | /// 172 | [Test] 173 | public void FindByBoolean() 174 | { 175 | const int expectedNumberOfResults = 8; 176 | 177 | var query = new SqlLam(p => p.Discontinued); 178 | 179 | var result = Connection.Query(query.QueryString, query.QueryParameters).ToList(); 180 | 181 | Assert.AreEqual(expectedNumberOfResults, result.Count); 182 | 183 | foreach (var product in result) 184 | { 185 | Assert.AreEqual(true, product.Discontinued); 186 | } 187 | } 188 | 189 | /// 190 | /// Find employees born before 1.1.1960 191 | /// 192 | [Test] 193 | public void FindByDateTime() 194 | { 195 | var minBirthDate = new DateTime(1960, 1, 1); 196 | const int expectedNumberOfResults = 5; 197 | 198 | var query = new SqlLam(p => p.BirthDate < minBirthDate); 199 | 200 | var result = Connection.Query(query.QueryString, query.QueryParameters).ToList(); 201 | 202 | Assert.AreEqual(expectedNumberOfResults, result.Count); 203 | 204 | foreach (var employee in result) 205 | { 206 | Assert.Less(employee.BirthDate, minBirthDate); 207 | } 208 | } 209 | 210 | /// 211 | /// Test guid access and nullable guid conversion 212 | /// 213 | [Test] 214 | public void FindByGuidFake() 215 | { 216 | var fakeGuid = new FakeGuid(); 217 | 218 | var query = new SqlLam(p => p.Id == fakeGuid.Id.Value); 219 | Assert.AreEqual("[FakeGuid].[Id] = @Param1", query.SqlBuilder.WhereConditions.First()); 220 | Assert.AreEqual(fakeGuid.Id.Value, query.QueryParameters.First().Value); 221 | } 222 | 223 | /// 224 | /// Find orders where the shipped date is after the required date 225 | /// 226 | [Test] 227 | public void FindByMemberComparison() 228 | { 229 | const int expectedNumberOfResults = 55; 230 | 231 | var query = new SqlLam(o => o.RequiredDate < o.ShippedDate); 232 | 233 | var result = Connection.Query(query.QueryString, query.QueryParameters).ToList(); 234 | 235 | Assert.AreEqual(expectedNumberOfResults, result.Count); 236 | 237 | foreach (var order in result) 238 | { 239 | Assert.Less(order.RequiredDate, order.ShippedDate); 240 | } 241 | } 242 | 243 | /// 244 | /// Find product by an Id retrieved using a member access 245 | /// 246 | [Test] 247 | public void FindByMemberAccess() 248 | { 249 | var product = new Product() 250 | { 251 | ProductId = 17 252 | }; 253 | 254 | var query = new SqlLam(p => p.ProductId == product.ProductId); 255 | 256 | var result = Connection.Query(query.QueryString, query.QueryParameters).Single(); 257 | 258 | Assert.AreEqual(product.ProductId, result.ProductId); 259 | } 260 | 261 | /// 262 | /// Find category by an Id retrieved using a member access 263 | /// 264 | [Test] 265 | public void FindByDoubleMemberAccess() 266 | { 267 | var category = new Category() 268 | { 269 | CategoryId = 8 270 | }; 271 | 272 | var product = new Product() 273 | { 274 | Category = category 275 | }; 276 | 277 | var query = new SqlLam(c => c.CategoryId == product.Category.CategoryId); 278 | 279 | var result = Connection.Query(query.QueryString, query.QueryParameters).Single(); 280 | 281 | Assert.AreEqual(category.CategoryId, result.CategoryId); 282 | } 283 | 284 | /// 285 | /// Find product by an Id retrieved using a method call 286 | /// 287 | [Test] 288 | public void FindByMethodCall() 289 | { 290 | var product = new Product() 291 | { 292 | ProductId = 17 293 | }; 294 | 295 | var query = new SqlLam(p => p.ProductId == product.GetProductId()); 296 | 297 | var result = Connection.Query(query.QueryString, query.QueryParameters).Single(); 298 | 299 | Assert.AreEqual(product.ProductId, result.ProductId); 300 | } 301 | 302 | /// 303 | /// Find product by an Id retrieved using a method call 304 | /// 305 | [Test] 306 | public void FindByMemberAccessAndMethodCall() 307 | { 308 | var category = new Category() 309 | { 310 | CategoryId = 8 311 | }; 312 | 313 | var product = new Product() 314 | { 315 | Category = category 316 | }; 317 | 318 | var query = new SqlLam(c => c.CategoryId == product.Category.GetCategoryId()); 319 | 320 | var result = Connection.Query(query.QueryString, query.QueryParameters).Single(); 321 | 322 | Assert.AreEqual(category.CategoryId, result.CategoryId); 323 | } 324 | 325 | [Test] 326 | public void FindByComplexQuery1() 327 | { 328 | const int expectedResultCount = 3; 329 | var expectedNames = new [] {"Nancy", "Margaret", "Laura"}; 330 | 331 | var query = new SqlLam(p => p.City == "Seattle" || p.City == "Redmond" && p.Title == "Sales Representative") 332 | .OrderByDescending(p => p.FirstName); 333 | 334 | var results = Connection.Query(query.QueryString, query.QueryParameters).ToList(); 335 | 336 | Assert.AreEqual(expectedResultCount, results.Count); 337 | for (int i=0; i < expectedResultCount; ++i) 338 | { 339 | Assert.AreEqual(expectedNames[i], results[i].FirstName); 340 | } 341 | } 342 | 343 | [Test] 344 | public void FindByComplexQuery2() 345 | { 346 | const int expectedResultCount = 2; 347 | var expectedNames = new[] { "Nancy", "Margaret" }; 348 | 349 | var query = new SqlLam(p => (p.City == "Seattle" || p.City == "Redmond") && p.Title == "Sales Representative") 350 | .OrderByDescending(p => p.FirstName); 351 | 352 | var results = Connection.Query(query.QueryString, query.QueryParameters).ToList(); 353 | 354 | Assert.AreEqual(expectedResultCount, results.Count); 355 | for (int i = 0; i < expectedResultCount; ++i) 356 | { 357 | Assert.AreEqual(expectedNames[i], results[i].FirstName); 358 | } 359 | } 360 | 361 | [Test] 362 | public void FindByComplexQuery2Flipped() 363 | { 364 | const int expectedResultCount = 2; 365 | var expectedNames = new[] { "Nancy", "Margaret" }; 366 | 367 | var query = new SqlLam(p => "Sales Representative" == p.Title && ("Seattle" == p.City || "Redmond" == p.City) ) 368 | .OrderByDescending(p => p.FirstName); 369 | 370 | var results = Connection.Query(query.QueryString, query.QueryParameters).ToList(); 371 | 372 | Assert.AreEqual(expectedResultCount, results.Count); 373 | for (int i = 0; i < expectedResultCount; ++i) 374 | { 375 | Assert.AreEqual(expectedNames[i], results[i].FirstName); 376 | } 377 | } 378 | 379 | [Test] 380 | public void FindByComplexQuery2Negated() 381 | { 382 | const int expectedResultCount = 13; 383 | 384 | var query = new SqlLam(p => !(p.City == "Seattle" || p.City == "Redmond") || p.Title != "Sales Representative") 385 | .OrderByDescending(p => p.FirstName); 386 | 387 | var results = Connection.Query(query.QueryString, query.QueryParameters).ToList(); 388 | 389 | Assert.AreEqual(expectedResultCount, results.Count); 390 | } 391 | 392 | [Test] 393 | public void FindByComplexQuery3() 394 | { 395 | const int expectedNumberOfResults = 16; 396 | const int reorderLevel = 0; 397 | 398 | var query = new SqlLam(p => !p.Discontinued && p.ReorderLevel == reorderLevel); 399 | 400 | var result = Connection.Query(query.QueryString, query.QueryParameters).ToList(); 401 | 402 | Assert.AreEqual(expectedNumberOfResults, result.Count); 403 | 404 | foreach (var product in result) 405 | { 406 | Assert.AreEqual(false, product.Discontinued); 407 | Assert.AreEqual(reorderLevel, product.ReorderLevel); 408 | } 409 | } 410 | 411 | [Test] 412 | public void FindByComplexQuery4() 413 | { 414 | var dateTime1 = new DateTime(1900, 1, 1); 415 | var dateTime2 = new DateTime(1950, 1, 1); 416 | var dateTime3 = new DateTime(1970, 1, 1); 417 | var dateTime4 = new DateTime(2000, 1, 1); 418 | 419 | const int expectedNumberOfResults = 5; 420 | 421 | var query = 422 | new SqlLam(e => 423 | (e.BirthDate > dateTime1 && e.BirthDate < dateTime2) 424 | || 425 | (e.BirthDate > dateTime3 && e.BirthDate < dateTime4)); 426 | 427 | var result = Connection.Query(query.QueryString, query.QueryParameters).ToList(); 428 | 429 | Assert.AreEqual(expectedNumberOfResults, result.Count); 430 | 431 | foreach (var employee in result) 432 | { 433 | Assert.IsTrue((employee.BirthDate > dateTime1 && employee.BirthDate < dateTime2) 434 | || (employee.BirthDate > dateTime3 && employee.BirthDate < dateTime4)); 435 | } 436 | } 437 | } 438 | } 439 | -------------------------------------------------------------------------------- /LambdaSqlBuilder.Tests/ExtraTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Data.SqlServerCe; 4 | using System.Linq; 5 | using System.Text; 6 | using LambdaSqlBuilder.Tests.Entities; 7 | using LambdaSqlBuilder.Tests.Infrastructure; 8 | using LambdaSqlBuilder.ValueObjects; 9 | using NUnit.Framework; 10 | using Dapper; 11 | 12 | namespace LambdaSqlBuilder.Tests 13 | { 14 | public class ExtraTests : TestBase 15 | { 16 | /// 17 | /// Access the underlying SqlBuilder to directly specify the selection and the having condition 18 | /// 19 | [Test] 20 | public void DirectSqlBuilderAccess() 21 | { 22 | var query = from product in new SqlLam() 23 | where product.ReorderLevel > 10 24 | group product by product.ReorderLevel; 25 | 26 | query.SqlBuilder.SelectionList.Add("MAX([Unit Price]) AS MaxPrice"); 27 | query.SqlBuilder.HavingConditions.Add("MAX([Unit Price]) < @PriceParam"); 28 | query.SqlBuilder.Parameters.Add("PriceParam", 25M); 29 | 30 | var result = Connection.Query(query.QueryString, query.QueryParameters).ToList(); 31 | 32 | Assert.AreEqual(1, result.Count); 33 | Assert.AreEqual(24, result.First()); 34 | } 35 | 36 | /// 37 | /// Load all product names without using the Dapper only with the standard Sql Command and the Data Reader 38 | /// 39 | [Test] 40 | public void DapperLessExecution() 41 | { 42 | var query = from product in new SqlLam() 43 | orderby product.ProductName 44 | select product.ProductName; 45 | 46 | var selectCommand = new SqlCeCommand(query.QueryString, Connection); 47 | foreach (var param in query.QueryParameters) 48 | selectCommand.Parameters.AddWithValue(param.Key, param.Value); 49 | 50 | var result = selectCommand.ExecuteReader(); 51 | 52 | int count = 0; 53 | while(result.Read()) 54 | { 55 | ++count; 56 | Assert.NotNull(result.GetString(0)); 57 | } 58 | Assert.AreEqual(77, count); 59 | } 60 | 61 | /// 62 | /// Change the adapter to Sql Server 2008 and verify whether the pagination SQL string contains the specific keyword ROW_NUMBER 63 | /// 64 | [Test] 65 | public void ChangeSqlAdapter() 66 | { 67 | SqlLamBase.SetAdapter(SqlAdapter.SqlServer2008); 68 | 69 | var query = from product in new SqlLam() 70 | orderby product.ProductName 71 | select product; 72 | 73 | var queryString = query.QueryStringPage(10, 1); 74 | 75 | SqlLamBase.SetAdapter(SqlAdapter.SqlServer2012); 76 | 77 | Assert.IsTrue(queryString.Contains("ROW_NUMBER")); 78 | } 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /LambdaSqlBuilder.Tests/Infrastructure/Bootstrap.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Reflection; 5 | using System.Text; 6 | using Dapper; 7 | 8 | namespace LambdaSqlBuilder.Tests.Infrastructure 9 | { 10 | static class Bootstrap 11 | { 12 | public const string CONNECTION_STRING = @"Data Source=AppData\Northwind.sdf"; 13 | 14 | public static void Initialize() 15 | { 16 | InitializeDapper(); 17 | } 18 | 19 | private static void InitializeDapper() 20 | { 21 | const string @namespace = "LambdaSqlBuilder.Tests.Entities"; 22 | 23 | var entityTypes = from t in Assembly.GetExecutingAssembly().GetTypes() 24 | where t.IsClass && t.Namespace == @namespace 25 | select t; 26 | 27 | foreach (var entityType in entityTypes) 28 | { 29 | SqlMapper.SetTypeMap( 30 | entityType, 31 | new CustomPropertyTypeMap( 32 | entityType, 33 | (type, columnName) => 34 | type.GetProperties().FirstOrDefault(prop => 35 | prop.GetCustomAttributes(false) 36 | .OfType() 37 | .Any(attr => attr.Name == columnName)) 38 | ) 39 | ); 40 | } 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /LambdaSqlBuilder.Tests/Infrastructure/TestBase.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Data.SqlServerCe; 4 | using System.Linq; 5 | using System.Text; 6 | using NUnit.Framework; 7 | 8 | namespace LambdaSqlBuilder.Tests.Infrastructure 9 | { 10 | public abstract class TestBase 11 | { 12 | protected SqlCeConnection Connection; 13 | 14 | [SetUp] 15 | public void Init() 16 | { 17 | Bootstrap.Initialize(); 18 | 19 | Connection = new SqlCeConnection(Bootstrap.CONNECTION_STRING); 20 | Connection.Open(); 21 | } 22 | 23 | [TearDown] 24 | public void TearDown() 25 | { 26 | Connection.Close(); 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /LambdaSqlBuilder.Tests/JoinSqlQueryTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using Dapper; 6 | using LambdaSqlBuilder.Tests.Entities; 7 | using LambdaSqlBuilder.Tests.Infrastructure; 8 | using NUnit.Framework; 9 | 10 | namespace LambdaSqlBuilder.Tests 11 | { 12 | public class JoinSqlQueryTests : TestBase 13 | { 14 | /// 15 | /// Find all products for the category Beverages 16 | /// 17 | [Test] 18 | public void FindByJoinedEntityValue() 19 | { 20 | const string categoryName = "Beverages"; 21 | const int categoryId = 1; 22 | 23 | var query = new SqlLam() 24 | .Join((p, c) => p.CategoryId == c.CategoryId) 25 | .Where(c => c.CategoryName == categoryName); 26 | 27 | var results = Connection.Query(query.QueryString, query.QueryParameters).ToList(); 28 | 29 | Assert.AreEqual(12, results.Count); 30 | Assert.IsTrue(results.All(p => p.CategoryId == categoryId)); 31 | } 32 | 33 | /// 34 | /// Find products with category being one of Beverages, Condiments, or Seafood 35 | /// 36 | [Test] 37 | public void FindByJoinedEntityListOfValues() 38 | { 39 | var categoryNames = new object[] { "Beverages", "Condiments", "Seafood" }; 40 | var categoryIds = new[] { 1, 2, 8 }; 41 | 42 | var query = new SqlLam() 43 | .Join((p, c) => p.CategoryId == c.CategoryId) 44 | .WhereIsIn(c => c.CategoryName, categoryNames); 45 | 46 | var results = Connection.Query(query.QueryString, query.QueryParameters).ToList(); 47 | 48 | Assert.AreEqual(36, results.Count); 49 | Assert.IsTrue(results.All(p => categoryIds.Contains(p.CategoryId))); 50 | } 51 | 52 | /// 53 | /// Find products by getting the category Ids first using a subquery 54 | /// 55 | [Test] 56 | public void FindByJoinedEntityWithSubQuery() 57 | { 58 | var categoryNames = new object[] { "Beverages", "Condiments", "Seafood" }; 59 | var categoryIds = new[] { 1, 2, 8 }; 60 | 61 | var subQuery = new SqlLam() 62 | .WhereIsIn(c => c.CategoryName, categoryNames) 63 | .Select(p => p.CategoryId); 64 | 65 | var query = new SqlLam() 66 | .Join((p, c) => p.CategoryId == c.CategoryId) 67 | .WhereIsIn(c => c.CategoryId, subQuery); 68 | 69 | var results = Connection.Query(query.QueryString, query.QueryParameters).ToList(); 70 | 71 | Assert.AreEqual(36, results.Count); 72 | Assert.IsTrue(results.All(p => categoryIds.Contains(p.CategoryId))); 73 | } 74 | 75 | /// 76 | /// Get products sorted by categories 77 | /// 78 | [Test] 79 | public void OrderEntitiesByJoinedEntityField() 80 | { 81 | var categoryQuery = new SqlLam(); 82 | var categories = Connection.Query(categoryQuery.QueryString).ToDictionary(k => k.CategoryId); 83 | 84 | var query = new SqlLam() 85 | .Join((p, c) => p.CategoryId == c.CategoryId) 86 | .OrderBy(c => c.CategoryName); 87 | 88 | var results = Connection.Query(query.QueryString, query.QueryParameters).ToList(); 89 | 90 | for (int i = 1; i < results.Count; ++i) 91 | { 92 | Assert.IsTrue(String.CompareOrdinal( 93 | categories[results[i - 1].CategoryId].CategoryName, 94 | categories[results[i].CategoryId].CategoryName) 95 | <= 0); 96 | } 97 | } 98 | 99 | /// 100 | /// Get products sorted by categories 101 | /// 102 | [Test] 103 | public void OrderEntitiesByJoinedEntityFieldDescending() 104 | { 105 | var categoryQuery = new SqlLam(); 106 | var categories = Connection.Query(categoryQuery.QueryString).ToDictionary(k => k.CategoryId); 107 | 108 | var query = new SqlLam() 109 | .Join((p, c) => p.CategoryId == c.CategoryId) 110 | .OrderByDescending(c => c.CategoryName); 111 | 112 | var results = Connection.Query(query.QueryString, query.QueryParameters).ToList(); 113 | 114 | for (int i = 1; i < results.Count; ++i) 115 | { 116 | Assert.IsTrue(String.CompareOrdinal( 117 | categories[results[i - 1].CategoryId].CategoryName, 118 | categories[results[i].CategoryId].CategoryName) 119 | >= 0); 120 | } 121 | } 122 | 123 | /// 124 | /// Load products together with their category details (many to one relationship) 125 | /// The complex selection of the product properties is only required because of the identical column names in the Northwind database for the primary key column and the foreign key column 126 | /// 127 | [Test] 128 | public void GetEntitiesWithManyToOneRelationship() 129 | { 130 | var query = new SqlLam() 131 | .Select(p => p.ProductId, p => p.ProductName, p => p.EnglishName, p => p.ReorderLevel, p => p.UnitPrice) 132 | .Join((p, c) => p.CategoryId == c.CategoryId) 133 | .Select(c => c); 134 | 135 | var result = Connection.Query( 136 | query.QueryString, 137 | (product, category) => 138 | { 139 | product.Category = category; 140 | product.CategoryId = category.CategoryId; 141 | return product; 142 | }, 143 | splitOn: query.SplitColumns[0]).ToList(); 144 | 145 | Assert.AreEqual(77, result.Count); 146 | foreach (var product in result) 147 | { 148 | Assert.IsNotNull(product.Category); 149 | Assert.IsNotNull(product.Category.CategoryName); 150 | } 151 | } 152 | 153 | /// 154 | /// Load categories together with all their products (one to many relationship) 155 | /// The complex selection of the product properties is only required because of the identical column names in the Northwind database for the primary key column and the foreign key column 156 | /// 157 | [Test] 158 | public void GetEntitiesWithOneToManyRelationship() 159 | { 160 | var query = new SqlLam() 161 | .Select(c => c.CategoryName) 162 | .Join((c, p) => c.CategoryId == p.CategoryId) 163 | .Select(p => p.CategoryId, p => p.ProductId, p => p.ProductName, p => p.EnglishName, p => p.ReorderLevel, p => p.UnitPrice); 164 | 165 | var result = new Dictionary(); 166 | Connection.Query( 167 | query.QueryString, 168 | (category, product) => 169 | { 170 | if(!result.ContainsKey(product.CategoryId)) 171 | { 172 | category.CategoryId = product.CategoryId; 173 | category.Products = new List(); 174 | result.Add(category.CategoryId,category); 175 | } 176 | product.Category = result[product.CategoryId]; 177 | result[product.CategoryId].Products.Add(product); 178 | return category; 179 | }, 180 | splitOn: query.SplitColumns[0]); 181 | 182 | Assert.AreEqual(8, result.Count); 183 | foreach (var category in result.Values) 184 | { 185 | Assert.Greater(category.Products.Count, 0); 186 | foreach(var product in category.Products) 187 | { 188 | Assert.AreEqual(category.CategoryId, product.CategoryId); 189 | } 190 | } 191 | } 192 | 193 | /// 194 | /// Load orders together with all their assigned products (many to many relationship via Order Details) 195 | /// 196 | [Test] 197 | public void GetEntitiesWithManyToManyRelationship() 198 | { 199 | var query = new SqlLam() 200 | .Select(o => o) 201 | .Join((o, d) => o.OrderId == d.OrderId) 202 | .Join((d, p) => d.ProductId == p.ProductId) 203 | .Select(p => p); 204 | 205 | var result = new Dictionary(); 206 | Connection.Query( 207 | query.QueryString, 208 | (order, product) => 209 | { 210 | if (!result.ContainsKey(order.OrderId)) 211 | { 212 | order.Products = new List(); 213 | result.Add(order.OrderId, order); 214 | } 215 | result[order.OrderId].Products.Add(product); 216 | return order; 217 | }, 218 | splitOn: query.SplitColumns[1]); 219 | 220 | var controlQuery = new SqlLam(); 221 | var controlSet = 222 | Connection.Query(controlQuery.QueryString).ToLookup(d => d.OrderId + "_" + d.ProductId); 223 | Assert.AreEqual(1078, result.Count); 224 | foreach (var order in result.Values) 225 | { 226 | Assert.Greater(order.Products.Count, 0); 227 | foreach (var product in order.Products) 228 | { 229 | Assert.IsTrue(controlSet.Contains(order.OrderId + "_" + product.ProductId)); 230 | } 231 | } 232 | } 233 | } 234 | } 235 | -------------------------------------------------------------------------------- /LambdaSqlBuilder.Tests/LambdaSqlBuilder.Tests.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | Debug 5 | AnyCPU 6 | 8.0.30703 7 | 2.0 8 | {75D22AB3-B90A-4E91-9888-E7FAB585F393} 9 | Library 10 | Properties 11 | LambdaSqlBuilder.Tests 12 | LambdaSqlBuilder.Tests 13 | v4.0 14 | 512 15 | 16 | 17 | true 18 | full 19 | false 20 | bin\Debug\ 21 | DEBUG;TRACE 22 | prompt 23 | 4 24 | 25 | 26 | pdbonly 27 | true 28 | bin\Debug\ 29 | TRACE 30 | prompt 31 | 4 32 | 33 | 34 | 35 | ..\packages\Dapper.1.13\lib\net40\Dapper.dll 36 | 37 | 38 | ..\packages\NUnit.2.6.2\lib\nunit.framework.dll 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | {3DF62791-F514-40A9-A335-BED1D9CBB34B} 71 | LambdaSqlBuilder 72 | 73 | 74 | 75 | 76 | Always 77 | 78 | 79 | 80 | 87 | -------------------------------------------------------------------------------- /LambdaSqlBuilder.Tests/LinqSqlQueryTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using LambdaSqlBuilder.Tests.Entities; 6 | using LambdaSqlBuilder.Tests.Infrastructure; 7 | using NUnit.Framework; 8 | using Dapper; 9 | 10 | namespace LambdaSqlBuilder.Tests 11 | { 12 | [TestFixture] 13 | public class LinqSqlQueryTests : TestBase 14 | { 15 | /// 16 | /// Find the product with name Tofu 17 | /// 18 | [Test] 19 | public void FindByFieldValue() 20 | { 21 | const string productName = "Tofu"; 22 | 23 | var query = from product in new SqlLam() 24 | where product.ProductName == productName 25 | select product; 26 | 27 | var results = Connection.Query(query.QueryString, query.QueryParameters).ToList(); 28 | 29 | Assert.AreEqual(1, results.Count); 30 | Assert.AreEqual(productName, results.First().ProductName); 31 | } 32 | 33 | /// 34 | /// Get product Tofu by its Id and select the value of the Unit Price only 35 | /// 36 | [Test] 37 | public void SelectField() 38 | { 39 | const int productId = 14; 40 | 41 | var query = from product in new SqlLam() 42 | where product.ProductId == productId 43 | select product.UnitPrice; 44 | 45 | var results = Connection.Query(query.QueryString, query.QueryParameters).ToList(); 46 | 47 | Assert.AreEqual(1, results.Count); 48 | Assert.AreEqual(23.25, results.First()); 49 | } 50 | 51 | /// 52 | /// Find all products for the category Beverages and the Reorder Level 25 53 | /// 54 | [Test] 55 | public void FindByJoinedEntityValue() 56 | { 57 | const int reorderLevel = 25; 58 | const string categoryName = "Beverages"; 59 | const int categoryId = 1; 60 | 61 | var query = from product in new SqlLam() 62 | join category in new SqlLam() 63 | on product.CategoryId equals category.CategoryId 64 | where product.ReorderLevel == reorderLevel && category.CategoryName == categoryName 65 | select product; 66 | 67 | var results = Connection.Query(query.QueryString, query.QueryParameters).ToList(); 68 | 69 | Assert.AreEqual(3, results.Count); 70 | Assert.IsTrue(results.All(p => p.CategoryId == categoryId)); 71 | } 72 | 73 | /// 74 | /// Get categories sorted by name 75 | /// 76 | [Test] 77 | public void OrderEntitiesByField() 78 | { 79 | var query = from category in new SqlLam() 80 | orderby category.CategoryName 81 | select category; 82 | 83 | var results = Connection.Query(query.QueryString, query.QueryParameters).ToList(); 84 | 85 | for (int i = 1; i < results.Count; ++i) 86 | { 87 | Assert.IsTrue(String.CompareOrdinal(results[i - 1].CategoryName, results[i].CategoryName) <= 0); 88 | } 89 | } 90 | 91 | /// 92 | /// Get categories sorted by name descending 93 | /// 94 | [Test] 95 | public void OrderEntitiesByFieldDescending() 96 | { 97 | var query = from category in new SqlLam() 98 | orderby category.CategoryName descending 99 | select category; 100 | 101 | var results = Connection.Query(query.QueryString, query.QueryParameters).ToList(); 102 | 103 | for (int i = 1; i < results.Count; ++i) 104 | { 105 | Assert.IsTrue(String.CompareOrdinal(results[i - 1].CategoryName, results[i].CategoryName) >= 0); 106 | } 107 | } 108 | 109 | /// 110 | /// Select number of products for individual Reorder Levels 111 | /// 112 | [Test] 113 | public void SelectGroupedCounts() 114 | { 115 | var groupSizes = new[] { 24, 8, 7, 10, 8, 12, 8 }; 116 | 117 | var query = from product in new SqlLam() 118 | group product by product.ReorderLevel; 119 | 120 | query.SelectCount(p => p.ProductId); 121 | 122 | var results = Connection.Query(query.QueryString, query.QueryParameters).ToList(); 123 | 124 | Assert.AreEqual(groupSizes.Length, results.Count); 125 | 126 | for (int i = 0; i < groupSizes.Length; ++i) 127 | { 128 | Assert.AreEqual(groupSizes[i], results[i]); 129 | } 130 | } 131 | } 132 | } 133 | -------------------------------------------------------------------------------- /LambdaSqlBuilder.Tests/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | // General Information about an assembly is controlled through the following 6 | // set of attributes. Change these attribute values to modify the information 7 | // associated with an assembly. 8 | [assembly: AssemblyTitle("LambdaSqlBuilder.Tests")] 9 | [assembly: AssemblyDescription("")] 10 | [assembly: AssemblyConfiguration("")] 11 | [assembly: AssemblyCompany("")] 12 | [assembly: AssemblyProduct("LambdaSqlBuilder.Tests")] 13 | [assembly: AssemblyCopyright("Copyright © 2013")] 14 | [assembly: AssemblyTrademark("")] 15 | [assembly: AssemblyCulture("")] 16 | 17 | // Setting ComVisible to false makes the types in this assembly not visible 18 | // to COM components. If you need to access a type in this assembly from 19 | // COM, set the ComVisible attribute to true on that type. 20 | [assembly: ComVisible(false)] 21 | 22 | // The following GUID is for the ID of the typelib if this project is exposed to COM 23 | [assembly: Guid("62cd49ae-06e0-4598-affe-229ac805341b")] 24 | 25 | // Version information for an assembly consists of the following four values: 26 | // 27 | // Major Version 28 | // Minor Version 29 | // Build Number 30 | // Revision 31 | // 32 | // You can specify all the values or you can default the Build and Revision Numbers 33 | // by using the '*' as shown below: 34 | // [assembly: AssemblyVersion("1.0.*")] 35 | [assembly: AssemblyVersion("1.0.0.0")] 36 | [assembly: AssemblyFileVersion("1.0.0.0")] 37 | -------------------------------------------------------------------------------- /LambdaSqlBuilder.Tests/SqlQueryTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Configuration; 4 | using System.Data.SqlClient; 5 | using System.Data.SqlServerCe; 6 | using System.Linq; 7 | using System.Text; 8 | using LambdaSqlBuilder.Tests.Entities; 9 | using LambdaSqlBuilder.Tests.Infrastructure; 10 | using NUnit.Framework; 11 | using Dapper; 12 | 13 | namespace LambdaSqlBuilder.Tests 14 | { 15 | [TestFixture] 16 | public class SqlQueryTests : TestBase 17 | { 18 | /// 19 | /// Find the product with name Tofu 20 | /// 21 | [Test] 22 | public void FindByFieldValue() 23 | { 24 | const string productName = "Tofu"; 25 | 26 | var query = new SqlLam(p => p.ProductName == productName); 27 | 28 | var results = Connection.Query(query.QueryString, query.QueryParameters).ToList(); 29 | 30 | Assert.AreEqual(1, results.Count); 31 | Assert.AreEqual(productName, results.First().ProductName); 32 | } 33 | 34 | /// 35 | /// Find products with the reorder level being 5, 15, or 25 36 | /// 37 | [Test] 38 | public void FindByListOfValues() 39 | { 40 | var reorderLevels = new object[] {5, 15, 25}; 41 | 42 | var query = new SqlLam() 43 | .WhereIsIn(p => p.ReorderLevel, reorderLevels); 44 | 45 | var results = Connection.Query(query.QueryString, query.QueryParameters).ToList(); 46 | 47 | Assert.AreEqual(30, results.Count); 48 | Assert.IsTrue(results.All(p => reorderLevels.Contains(p.ReorderLevel))); 49 | } 50 | 51 | /// 52 | /// Find products by getting the product Ids first using a subquery 53 | /// 54 | [Test] 55 | public void FindBySubQuery() 56 | { 57 | var productNames = new object[] { "Konbu", "Tofu", "Pavlova" }; 58 | 59 | var subQuery = new SqlLam() 60 | .WhereIsIn(p => p.ProductName, productNames) 61 | .Select(p => p.ProductId); 62 | 63 | var query = new SqlLam() 64 | .WhereIsIn(p => p.ProductId, subQuery); 65 | 66 | var results = Connection.Query(query.QueryString, query.QueryParameters).ToList(); 67 | 68 | Assert.AreEqual(3, results.Count); 69 | Assert.IsTrue(results.All(p => productNames.Contains(p.ProductName))); 70 | } 71 | 72 | /// 73 | /// Find products with the reorder level not being 5, 15, or 25 74 | /// 75 | [Test] 76 | public void FindByListOfValuesNegated() 77 | { 78 | var reorderLevels = new object[] { 5, 15, 25 }; 79 | 80 | var query = new SqlLam() 81 | .WhereNotIn(p => p.ReorderLevel, reorderLevels); 82 | 83 | var results = Connection.Query(query.QueryString, query.QueryParameters).ToList(); 84 | 85 | Assert.AreEqual(47, results.Count); 86 | Assert.IsTrue(results.All(p => !reorderLevels.Contains(p.ReorderLevel))); 87 | } 88 | 89 | /// 90 | /// Find products not being included in a subquery 91 | /// 92 | [Test] 93 | public void FindBySubQueryNegated() 94 | { 95 | var productNames = new object[] { "Konbu", "Tofu", "Pavlova" }; 96 | 97 | var subQuery = new SqlLam() 98 | .WhereIsIn(p => p.ProductName, productNames) 99 | .Select(p => p.ProductId); 100 | 101 | var query = new SqlLam() 102 | .WhereNotIn(p => p.ProductId, subQuery); 103 | 104 | var results = Connection.Query(query.QueryString, query.QueryParameters).ToList(); 105 | 106 | Assert.AreEqual(74, results.Count); 107 | Assert.IsTrue(results.All(p => !productNames.Contains(p.ProductName))); 108 | } 109 | 110 | /// 111 | /// Get product Tofu by its Id and select the value of the Unit Price only 112 | /// 113 | [Test] 114 | public void SelectField() 115 | { 116 | const int productId = 14; 117 | 118 | var query = new SqlLam(p => p.ProductId == productId) 119 | .Select(p => p.UnitPrice); 120 | 121 | var results = Connection.Query(query.QueryString, query.QueryParameters).ToList(); 122 | 123 | Assert.AreEqual(1, results.Count); 124 | Assert.AreEqual(23.25, results.First()); 125 | } 126 | 127 | /// 128 | /// Get product Tofu by its Id and selects all its properties 129 | /// 130 | [Test] 131 | public void SelectAllFields() 132 | { 133 | const int productId = 14; 134 | 135 | var query = new SqlLam(p => p.ProductId == productId) 136 | .Select(p => p); 137 | 138 | var results = Connection.Query(query.QueryString, query.QueryParameters).ToList(); 139 | 140 | Assert.AreEqual(1, results.Count); 141 | } 142 | 143 | /// 144 | /// Get categories sorted by name 145 | /// 146 | [Test] 147 | public void OrderEntitiesByField() 148 | { 149 | var query = new SqlLam() 150 | .OrderBy(p => p.CategoryName); 151 | 152 | var results = Connection.Query(query.QueryString, query.QueryParameters).ToList(); 153 | 154 | for (int i = 1; i < results.Count; ++i) 155 | { 156 | Assert.IsTrue(String.CompareOrdinal(results[i - 1].CategoryName, results[i].CategoryName) <= 0); 157 | } 158 | } 159 | 160 | /// 161 | /// Get categories sorted by name descending 162 | /// 163 | [Test] 164 | public void OrderEntitiesByFieldDescending() 165 | { 166 | var query = new SqlLam() 167 | .OrderByDescending(p => p.CategoryName); 168 | 169 | var results = Connection.Query(query.QueryString, query.QueryParameters).ToList(); 170 | 171 | for (int i = 1; i < results.Count; ++i) 172 | { 173 | Assert.IsTrue(String.CompareOrdinal(results[i - 1].CategoryName, results[i].CategoryName) >= 0); 174 | } 175 | } 176 | 177 | /// 178 | /// Get the number of all products 179 | /// 180 | [Test] 181 | public void SelectEntityCount() 182 | { 183 | var query = new SqlLam() 184 | .SelectCount(p => p.ProductId); 185 | 186 | var resultCount = Connection.Query(query.QueryString, query.QueryParameters).Single(); 187 | 188 | Assert.AreEqual(77, resultCount); 189 | } 190 | 191 | /// 192 | /// Select number of Product IDs for products with the Reorder Level equal to 25 193 | /// 194 | [Test] 195 | public void SelectRestrictedEntityCount() 196 | { 197 | var query = new SqlLam() 198 | .SelectCount(p => p.ProductId) 199 | .Where(p => p.ReorderLevel == 25); 200 | 201 | var resultCount = Connection.Query(query.QueryString, query.QueryParameters).Single(); 202 | 203 | Assert.AreEqual(12, resultCount); 204 | } 205 | 206 | /// 207 | /// Select number of products for individual Reorder Levels 208 | /// 209 | [Test] 210 | public void SelectGroupedCounts() 211 | { 212 | var groupSizes = new[] {24, 8, 7, 10, 8, 12, 8}; 213 | 214 | var query = new SqlLam() 215 | .SelectCount(p => p.ProductId) 216 | .GroupBy(p => p.ReorderLevel) 217 | .OrderBy(p => p.ReorderLevel); 218 | 219 | var results = Connection.Query(query.QueryString, query.QueryParameters).ToList(); 220 | 221 | Assert.AreEqual(groupSizes.Length, results.Count); 222 | 223 | for (int i = 0; i < groupSizes.Length; ++i) 224 | { 225 | Assert.AreEqual(groupSizes[i], results[i]); 226 | } 227 | } 228 | 229 | /// 230 | /// Select all distinct possible values of the Reorder Level 231 | /// 232 | [Test] 233 | public void SelectDistinctValues() 234 | { 235 | var allValues = new[] {0, 5, 10, 15, 20, 25, 30}; 236 | 237 | var query = new SqlLam() 238 | .SelectDistinct(p => p.ReorderLevel) 239 | .OrderBy(p => p.ReorderLevel); 240 | 241 | var results = Connection.Query(query.QueryString, query.QueryParameters).ToList(); 242 | 243 | Assert.AreEqual(allValues.Length, results.Count); 244 | for (int i = 0; i < allValues.Length; ++i) 245 | { 246 | Assert.AreEqual(allValues[i], results[i]); 247 | } 248 | } 249 | 250 | /// 251 | /// Select maximum unit price among all the products 252 | /// 253 | [Test] 254 | public void SelectMaximumValue() 255 | { 256 | const decimal maximumValue = 263.5M; 257 | 258 | var query = new SqlLam() 259 | .SelectMax(p => p.UnitPrice); 260 | 261 | var results = Connection.Query(query.QueryString, query.QueryParameters).Single(); 262 | 263 | Assert.AreEqual(maximumValue, results); 264 | } 265 | 266 | /// 267 | /// Select minimum unit price among all the products 268 | /// 269 | [Test] 270 | public void SelectMinimumValue() 271 | { 272 | const decimal minimumValue = 2.5M; 273 | 274 | var query = new SqlLam() 275 | .SelectMin(p => p.UnitPrice); 276 | 277 | var results = Connection.Query(query.QueryString, query.QueryParameters).Single(); 278 | 279 | Assert.AreEqual(minimumValue, results); 280 | } 281 | 282 | /// 283 | /// Select average unit price among all the products 284 | /// 285 | [Test] 286 | public void SelectAverageValue() 287 | { 288 | const decimal averageValue = 28.8663M; 289 | 290 | var query = new SqlLam() 291 | .SelectAverage(p => p.UnitPrice); 292 | 293 | var results = Connection.Query(query.QueryString, query.QueryParameters).Single(); 294 | 295 | Assert.AreEqual(averageValue, results); 296 | } 297 | 298 | /// 299 | /// Select sum of all unit prices among all the products 300 | /// 301 | [Test] 302 | public void SelectSum() 303 | { 304 | const decimal sum = 2222.71M; 305 | 306 | var query = new SqlLam() 307 | .SelectSum(p => p.UnitPrice); 308 | 309 | var results = Connection.Query(query.QueryString, query.QueryParameters).Single(); 310 | 311 | Assert.AreEqual(sum, results); 312 | } 313 | 314 | /// 315 | /// Select the product "Tofu" by listing its individual properties using the 'new' construct 316 | /// 317 | [Test] 318 | public void SelectWithNew() 319 | { 320 | const string productName = "Tofu"; 321 | 322 | var query = new SqlLam() 323 | .Where(p => p.ProductName == productName) 324 | .Select(p => new { 325 | p.ProductId, 326 | p.ProductName, 327 | p.CategoryId, 328 | p.ReorderLevel, 329 | p.UnitPrice 330 | }); 331 | 332 | var results = Connection.Query(query.QueryString, query.QueryParameters).Single(); 333 | 334 | Assert.NotNull(results.ProductId); 335 | Assert.NotNull(results.ProductName); 336 | Assert.NotNull(results.CategoryId); 337 | Assert.NotNull(results.ReorderLevel); 338 | Assert.NotNull(results.UnitPrice); 339 | Assert.Null(results.EnglishName); 340 | } 341 | 342 | /// 343 | /// Load products with reorder level 0 page by page 344 | /// 345 | [Test] 346 | public void PaginateOverResults() 347 | { 348 | const int reorderLevel = 0; 349 | const int pageSize = 5; 350 | const int numberOfPages = 5; 351 | const int lastPageSize = 4; 352 | 353 | var query = new SqlLam(p => p.ReorderLevel == reorderLevel) 354 | .OrderBy(p => p.ProductName); 355 | 356 | for(int page = 1; page < numberOfPages; ++page) 357 | { 358 | var results = Connection.Query(query.QueryStringPage(pageSize, page), query.QueryParameters).ToList(); 359 | Assert.AreEqual(pageSize, results.Count); 360 | Assert.IsTrue(results.All(p => p.ReorderLevel == reorderLevel)); 361 | } 362 | 363 | var lastResults = Connection.Query(query.QueryStringPage(pageSize, numberOfPages), query.QueryParameters).ToList(); 364 | Assert.AreEqual(lastPageSize, lastResults.Count); 365 | Assert.IsTrue(lastResults.All(p => p.ReorderLevel == reorderLevel)); 366 | } 367 | 368 | /// 369 | /// Load first 10 products sorted by the product name with reorder level 0 370 | /// 371 | [Test] 372 | public void TopTenResults() 373 | { 374 | const int reorderLevel = 0; 375 | const int pageSize = 10; 376 | 377 | var query = new SqlLam(p => p.ReorderLevel == reorderLevel) 378 | .OrderBy(p => p.ProductName); 379 | 380 | var results = Connection.Query(query.QueryStringPage(pageSize), query.QueryParameters).ToList(); 381 | Assert.AreEqual(pageSize, results.Count); 382 | Assert.IsTrue(results.All(p => p.ReorderLevel == reorderLevel)); 383 | } 384 | } 385 | } 386 | -------------------------------------------------------------------------------- /LambdaSqlBuilder.Tests/packages.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /LambdaSqlBuilder.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 11.00 3 | # Visual C# Express 2010 4 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LambdaSqlBuilder", "LambdaSqlBuilder\LambdaSqlBuilder.csproj", "{3DF62791-F514-40A9-A335-BED1D9CBB34B}" 5 | EndProject 6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LambdaSqlBuilder.Tests", "LambdaSqlBuilder.Tests\LambdaSqlBuilder.Tests.csproj", "{75D22AB3-B90A-4E91-9888-E7FAB585F393}" 7 | EndProject 8 | Global 9 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 10 | Debug|Any CPU = Debug|Any CPU 11 | Release|Any CPU = Release|Any CPU 12 | EndGlobalSection 13 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 14 | {3DF62791-F514-40A9-A335-BED1D9CBB34B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 15 | {3DF62791-F514-40A9-A335-BED1D9CBB34B}.Debug|Any CPU.Build.0 = Debug|Any CPU 16 | {3DF62791-F514-40A9-A335-BED1D9CBB34B}.Release|Any CPU.ActiveCfg = Release|Any CPU 17 | {3DF62791-F514-40A9-A335-BED1D9CBB34B}.Release|Any CPU.Build.0 = Release|Any CPU 18 | {75D22AB3-B90A-4E91-9888-E7FAB585F393}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 19 | {75D22AB3-B90A-4E91-9888-E7FAB585F393}.Debug|Any CPU.Build.0 = Debug|Any CPU 20 | {75D22AB3-B90A-4E91-9888-E7FAB585F393}.Release|Any CPU.ActiveCfg = Release|Any CPU 21 | {75D22AB3-B90A-4E91-9888-E7FAB585F393}.Release|Any CPU.Build.0 = Release|Any CPU 22 | EndGlobalSection 23 | GlobalSection(SolutionProperties) = preSolution 24 | HideSolutionNode = FALSE 25 | EndGlobalSection 26 | EndGlobal 27 | -------------------------------------------------------------------------------- /LambdaSqlBuilder/Adapter/ISqlAdapter.cs: -------------------------------------------------------------------------------- 1 | /* License: http://www.apache.org/licenses/LICENSE-2.0 */ 2 | 3 | using System; 4 | using System.Collections.Generic; 5 | 6 | namespace LambdaSqlBuilder.Adapter 7 | { 8 | /// 9 | /// SQL adapter provides db specific functionality related to db specific SQL syntax 10 | /// 11 | interface ISqlAdapter 12 | { 13 | string QueryString(string selection, string source, string conditions, 14 | string order, string grouping, string having); 15 | 16 | string QueryStringPage(string selection, string source, string conditions, string order, 17 | int pageSize, int pageNumber); 18 | 19 | string QueryStringPage(string selection, string source, string conditions, string order, 20 | int pageSize); 21 | 22 | string Table(string tableName); 23 | string Field(string tableName, string fieldName); 24 | string Parameter(string parameterId); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /LambdaSqlBuilder/Adapter/SqlAdapterBase.cs: -------------------------------------------------------------------------------- 1 | /* License: http://www.apache.org/licenses/LICENSE-2.0 */ 2 | 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Linq; 6 | using System.Text; 7 | 8 | namespace LambdaSqlBuilder.Adapter 9 | { 10 | /// 11 | /// Provides functionality common to all supported databases 12 | /// 13 | class SqlAdapterBase 14 | { 15 | public string QueryString(string selection, string source, string conditions, string order, string grouping, string having) 16 | { 17 | return string.Format("SELECT {0} FROM {1} {2} {3} {4} {5}", 18 | selection, source, conditions, order, grouping, having); 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /LambdaSqlBuilder/Adapter/SqlServer2008Adapter.cs: -------------------------------------------------------------------------------- 1 | /* License: http://www.apache.org/licenses/LICENSE-2.0 */ 2 | 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Linq; 6 | using System.Text; 7 | 8 | namespace LambdaSqlBuilder.Adapter 9 | { 10 | /// 11 | /// Provides functionality specific to SQL Server 2008 12 | /// 13 | class SqlServer2008Adapter : SqlServerAdapterBase, ISqlAdapter 14 | { 15 | public string QueryStringPage(string source, string selection, string conditions, string order, 16 | int pageSize, int pageNumber) 17 | { 18 | var innerQuery = string.Format("SELECT {0},ROW_NUMBER() OVER ({1}) AS RN FROM {2} {3}", 19 | selection, order, source, conditions); 20 | 21 | return string.Format("SELECT TOP {0} * FROM ({1}) InnerQuery WHERE RN > {2} ORDER BY RN", 22 | pageSize, innerQuery, pageSize*(pageNumber - 1)); 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /LambdaSqlBuilder/Adapter/SqlServer2012Adapter.cs: -------------------------------------------------------------------------------- 1 | /* License: http://www.apache.org/licenses/LICENSE-2.0 */ 2 | 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Linq; 6 | using System.Text; 7 | 8 | namespace LambdaSqlBuilder.Adapter 9 | { 10 | /// 11 | /// Provides functionality specific to SQL Server 2012 12 | /// 13 | class SqlServer2012Adapter : SqlServerAdapterBase, ISqlAdapter 14 | { 15 | public string QueryStringPage(string source, string selection, string conditions, string order, 16 | int pageSize, int pageNumber) 17 | { 18 | return string.Format("SELECT {0} FROM {1} {2} {3} OFFSET {4} ROWS FETCH NEXT {5} ROWS ONLY", 19 | selection, source, conditions, order, pageSize * (pageNumber - 1), pageSize); 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /LambdaSqlBuilder/Adapter/SqlServerAdapterBase.cs: -------------------------------------------------------------------------------- 1 | /* License: http://www.apache.org/licenses/LICENSE-2.0 */ 2 | 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Linq; 6 | using System.Text; 7 | 8 | namespace LambdaSqlBuilder.Adapter 9 | { 10 | /// 11 | /// Provides functionality common to all supported SQL Server versions 12 | /// 13 | class SqlServerAdapterBase : SqlAdapterBase 14 | { 15 | public string QueryStringPage(string source, string selection, string conditions, string order, 16 | int pageSize) 17 | { 18 | return string.Format("SELECT TOP({4}) {0} FROM {1} {2} {3}", 19 | selection, source, conditions, order, pageSize); 20 | } 21 | 22 | 23 | public string Table(string tableName) 24 | { 25 | return string.Format("[{0}]", tableName); 26 | } 27 | 28 | public string Field(string tableName, string fieldName) 29 | { 30 | return string.Format("[{0}].[{1}]", tableName, fieldName); 31 | } 32 | 33 | public string Parameter(string parameterId) 34 | { 35 | return "@" + parameterId; 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /LambdaSqlBuilder/Builder/SqlQueryBuilder.cs: -------------------------------------------------------------------------------- 1 | /* License: http://www.apache.org/licenses/LICENSE-2.0 */ 2 | 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Dynamic; 6 | using System.Globalization; 7 | using System.Linq; 8 | using System.Text; 9 | using LambdaSqlBuilder.Adapter; 10 | 11 | namespace LambdaSqlBuilder.Builder 12 | { 13 | /// 14 | /// Implements the whole SQL building logic. Continually adds and stores the SQL parts as the requests come. 15 | /// When requested to return the QueryString, the parts are combined and returned as a single query string. 16 | /// The query parameters are stored in a dictionary implemented by an ExpandoObject that can be requested by QueryParameters. 17 | /// 18 | public partial class SqlQueryBuilder 19 | { 20 | internal ISqlAdapter Adapter { get; set; } 21 | 22 | private const string PARAMETER_PREFIX = "Param"; 23 | 24 | private readonly List _tableNames = new List(); 25 | private readonly List _joinExpressions = new List(); 26 | private readonly List _selectionList = new List(); 27 | private readonly List _conditions = new List(); 28 | private readonly List _sortList = new List(); 29 | private readonly List _groupingList = new List(); 30 | private readonly List _havingConditions = new List(); 31 | private readonly List _splitColumns = new List(); 32 | private int _paramIndex; 33 | 34 | public List TableNames { get { return _tableNames; } } 35 | public List JoinExpressions { get { return _joinExpressions; } } 36 | public List SelectionList { get { return _selectionList; } } 37 | public List WhereConditions { get { return _conditions; } } 38 | public List OrderByList { get { return _sortList; } } 39 | public List GroupByList { get { return _groupingList; } } 40 | public List HavingConditions { get { return _havingConditions; } } 41 | public List SplitColumns { get { return _splitColumns; } } 42 | public int CurrentParamIndex { get { return _paramIndex; } } 43 | 44 | private string Source 45 | { 46 | get 47 | { 48 | var joinExpression = string.Join(" ", _joinExpressions); 49 | return string.Format("{0} {1}", Adapter.Table(_tableNames.First()), joinExpression); 50 | } 51 | } 52 | 53 | private string Selection 54 | { 55 | get 56 | { 57 | if (_selectionList.Count == 0) 58 | return string.Format("{0}.*", Adapter.Table(_tableNames.First())); 59 | else 60 | return string.Join(", ", _selectionList); 61 | } 62 | } 63 | 64 | private string Conditions 65 | { 66 | get 67 | { 68 | if (_conditions.Count == 0) 69 | return ""; 70 | else 71 | return "WHERE " + string.Join("", _conditions); 72 | } 73 | } 74 | 75 | private string Order 76 | { 77 | get 78 | { 79 | if (_sortList.Count == 0) 80 | return ""; 81 | else 82 | return "ORDER BY " + string.Join(", ", _sortList); 83 | } 84 | } 85 | 86 | private string Grouping 87 | { 88 | get 89 | { 90 | if (_groupingList.Count == 0) 91 | return ""; 92 | else 93 | return "GROUP BY " + string.Join(", ", _groupingList); 94 | } 95 | } 96 | 97 | private string Having 98 | { 99 | get 100 | { 101 | if (_havingConditions.Count == 0) 102 | return ""; 103 | else 104 | return "HAVING " + string.Join(" ", _havingConditions); 105 | } 106 | } 107 | 108 | public IDictionary Parameters { get; private set; } 109 | 110 | public string QueryString 111 | { 112 | get { return Adapter.QueryString(Selection, Source, Conditions, Grouping, Having, Order); } 113 | } 114 | 115 | public string QueryStringPage(int pageSize, int? pageNumber = null) 116 | { 117 | if (pageNumber.HasValue) 118 | { 119 | if (_sortList.Count == 0) 120 | throw new Exception("Pagination requires the ORDER BY statement to be specified"); 121 | 122 | return Adapter.QueryStringPage(Source, Selection, Conditions, Order, pageSize, pageNumber.Value); 123 | } 124 | 125 | return Adapter.QueryStringPage(Source, Selection, Conditions, Order, pageSize); 126 | } 127 | 128 | internal SqlQueryBuilder(string tableName, ISqlAdapter adapter) 129 | { 130 | _tableNames.Add(tableName); 131 | Adapter = adapter; 132 | Parameters = new ExpandoObject(); 133 | _paramIndex = 0; 134 | } 135 | 136 | #region helpers 137 | private string NextParamId() 138 | { 139 | ++_paramIndex; 140 | return PARAMETER_PREFIX + _paramIndex.ToString(CultureInfo.InvariantCulture); 141 | } 142 | 143 | private void AddParameter(string key, object value) 144 | { 145 | if(!Parameters.ContainsKey(key)) 146 | Parameters.Add(key, value); 147 | } 148 | #endregion 149 | } 150 | } 151 | -------------------------------------------------------------------------------- /LambdaSqlBuilder/Builder/SqlQueryBuilderExpr.cs: -------------------------------------------------------------------------------- 1 | /* License: http://www.apache.org/licenses/LICENSE-2.0 */ 2 | 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Linq; 6 | using System.Linq.Expressions; 7 | using System.Text; 8 | using System.Text.RegularExpressions; 9 | using LambdaSqlBuilder.ValueObjects; 10 | 11 | namespace LambdaSqlBuilder.Builder 12 | { 13 | /// 14 | /// Implements the expression buiding for the WHERE statement 15 | /// 16 | partial class SqlQueryBuilder 17 | { 18 | public void BeginExpression() 19 | { 20 | _conditions.Add("("); 21 | } 22 | 23 | public void EndExpression() 24 | { 25 | _conditions.Add(")"); 26 | } 27 | 28 | public void And() 29 | { 30 | if (_conditions.Count > 0) 31 | _conditions.Add(" AND "); 32 | } 33 | 34 | public void Or() 35 | { 36 | if (_conditions.Count > 0) 37 | _conditions.Add(" OR "); 38 | } 39 | 40 | public void Not() 41 | { 42 | _conditions.Add(" NOT "); 43 | } 44 | 45 | public void QueryByField(string tableName, string fieldName, string op, object fieldValue) 46 | { 47 | var paramId = NextParamId(); 48 | string newCondition = string.Format("{0} {1} {2}", 49 | Adapter.Field(tableName, fieldName), 50 | op, 51 | Adapter.Parameter(paramId)); 52 | 53 | _conditions.Add(newCondition); 54 | AddParameter(paramId, fieldValue); 55 | } 56 | 57 | public void QueryByFieldLike(string tableName, string fieldName, string fieldValue) 58 | { 59 | var paramId = NextParamId(); 60 | string newCondition = string.Format("{0} LIKE {1}", 61 | Adapter.Field(tableName, fieldName), 62 | Adapter.Parameter(paramId)); 63 | 64 | _conditions.Add(newCondition); 65 | AddParameter(paramId, fieldValue); 66 | } 67 | 68 | public void QueryByFieldNull(string tableName, string fieldName) 69 | { 70 | _conditions.Add(string.Format("{0} IS NULL", Adapter.Field(tableName, fieldName))); 71 | } 72 | 73 | public void QueryByFieldNotNull(string tableName, string fieldName) 74 | { 75 | _conditions.Add(string.Format("{0} IS NOT NULL", Adapter.Field(tableName, fieldName))); 76 | } 77 | 78 | public void QueryByFieldComparison(string leftTableName, string leftFieldName, string op, 79 | string rightTableName, string rightFieldName) 80 | { 81 | string newCondition = string.Format("{0} {1} {2}", 82 | Adapter.Field(leftTableName, leftFieldName), 83 | op, 84 | Adapter.Field(rightTableName, rightFieldName)); 85 | 86 | _conditions.Add(newCondition); 87 | } 88 | 89 | public void QueryByIsIn(string tableName, string fieldName, SqlLamBase sqlQuery) 90 | { 91 | var innerQuery = sqlQuery.QueryString; 92 | foreach (var param in sqlQuery.QueryParameters) 93 | { 94 | var innerParamKey = "Inner" + param.Key; 95 | innerQuery = Regex.Replace(innerQuery, param.Key, innerParamKey); 96 | AddParameter(innerParamKey, param.Value); 97 | } 98 | 99 | var newCondition = string.Format("{0} IN ({1})", Adapter.Field(tableName, fieldName), innerQuery); 100 | 101 | _conditions.Add(newCondition); 102 | } 103 | 104 | public void QueryByIsIn(string tableName, string fieldName, IEnumerable values) 105 | { 106 | var paramIds = values.Select(x => 107 | { 108 | var paramId = NextParamId(); 109 | AddParameter(paramId, x); 110 | return Adapter.Parameter(paramId); 111 | }); 112 | 113 | var newCondition = string.Format("{0} IN ({1})", Adapter.Field(tableName, fieldName), string.Join(",", paramIds)); 114 | _conditions.Add(newCondition); 115 | } 116 | } 117 | } 118 | -------------------------------------------------------------------------------- /LambdaSqlBuilder/Builder/SqlQueryBuilderSpec.cs: -------------------------------------------------------------------------------- 1 | /* License: http://www.apache.org/licenses/LICENSE-2.0 */ 2 | 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Linq; 6 | using System.Text; 7 | using LambdaSqlBuilder.ValueObjects; 8 | 9 | namespace LambdaSqlBuilder.Builder 10 | { 11 | /// 12 | /// Implements the SQL building for JOIN, ORDER BY, SELECT, and GROUP BY 13 | /// 14 | partial class SqlQueryBuilder 15 | { 16 | public void Join(string originalTableName, string joinTableName, string leftField, string rightField) 17 | { 18 | var joinString = string.Format("JOIN {0} ON {1} = {2}", 19 | Adapter.Table(joinTableName), 20 | Adapter.Field(originalTableName, leftField), 21 | Adapter.Field(joinTableName, rightField)); 22 | _tableNames.Add(joinTableName); 23 | _joinExpressions.Add(joinString); 24 | _splitColumns.Add(rightField); 25 | } 26 | 27 | public void OrderBy(string tableName, string fieldName, bool desc = false) 28 | { 29 | var order = Adapter.Field(tableName, fieldName); 30 | if (desc) 31 | order += " DESC"; 32 | 33 | _sortList.Add(order); 34 | } 35 | 36 | public void Select(string tableName) 37 | { 38 | var selectionString = string.Format("{0}.*", Adapter.Table(tableName)); 39 | _selectionList.Add(selectionString); 40 | } 41 | 42 | public void Select(string tableName, string fieldName) 43 | { 44 | _selectionList.Add(Adapter.Field(tableName, fieldName)); 45 | } 46 | 47 | public void Select(string tableName, string fieldName, SelectFunction selectFunction) 48 | { 49 | var selectionString = string.Format("{0}({1})", selectFunction.ToString(), Adapter.Field(tableName, fieldName)); 50 | _selectionList.Add(selectionString); 51 | } 52 | 53 | public void GroupBy(string tableName, string fieldName) 54 | { 55 | _groupingList.Add(Adapter.Field(tableName, fieldName)); 56 | } 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /LambdaSqlBuilder/LambdaSqlBuilder.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | Debug 5 | AnyCPU 6 | 8.0.30703 7 | 2.0 8 | {3DF62791-F514-40A9-A335-BED1D9CBB34B} 9 | Library 10 | Properties 11 | LambdaSqlBuilder 12 | LambdaSqlBuilder 13 | v4.0 14 | 512 15 | 16 | 17 | true 18 | full 19 | false 20 | bin\Debug\ 21 | DEBUG;TRACE 22 | prompt 23 | 4 24 | 25 | 26 | pdbonly 27 | true 28 | bin\Release\ 29 | TRACE 30 | prompt 31 | 4 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 80 | -------------------------------------------------------------------------------- /LambdaSqlBuilder/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | // General Information about an assembly is controlled through the following 6 | // set of attributes. Change these attribute values to modify the information 7 | // associated with an assembly. 8 | [assembly: AssemblyTitle("LambdaSqlBuilder")] 9 | [assembly: AssemblyDescription("")] 10 | [assembly: AssemblyConfiguration("")] 11 | [assembly: AssemblyCompany("")] 12 | [assembly: AssemblyProduct("LambdaSqlBuilder")] 13 | [assembly: AssemblyCopyright("Copyright © 2013")] 14 | [assembly: AssemblyTrademark("")] 15 | [assembly: AssemblyCulture("")] 16 | 17 | // Setting ComVisible to false makes the types in this assembly not visible 18 | // to COM components. If you need to access a type in this assembly from 19 | // COM, set the ComVisible attribute to true on that type. 20 | [assembly: ComVisible(false)] 21 | 22 | // The following GUID is for the ID of the typelib if this project is exposed to COM 23 | [assembly: Guid("9d71aec5-a052-45df-a9a2-645c23dbeb9d")] 24 | 25 | // Version information for an assembly consists of the following four values: 26 | // 27 | // Major Version 28 | // Minor Version 29 | // Build Number 30 | // Revision 31 | // 32 | // You can specify all the values or you can default the Build and Revision Numbers 33 | // by using the '*' as shown below: 34 | // [assembly: AssemblyVersion("1.0.*")] 35 | [assembly: AssemblyVersion("1.0.0.0")] 36 | [assembly: AssemblyFileVersion("1.0.0.0")] 37 | -------------------------------------------------------------------------------- /LambdaSqlBuilder/Resolver/ExpressionTree/LikeNode.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using LambdaSqlBuilder.ValueObjects; 6 | 7 | namespace LambdaSqlBuilder.Resolver.ExpressionTree 8 | { 9 | class LikeNode : Node 10 | { 11 | public LikeMethod Method { get; set; } 12 | public MemberNode MemberNode { get; set; } 13 | public string Value { get; set; } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /LambdaSqlBuilder/Resolver/ExpressionTree/MemberNode.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | 6 | namespace LambdaSqlBuilder.Resolver.ExpressionTree 7 | { 8 | class MemberNode : Node 9 | { 10 | public string TableName { get; set; } 11 | public string FieldName { get; set; } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /LambdaSqlBuilder/Resolver/ExpressionTree/Node.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | 6 | namespace LambdaSqlBuilder.Resolver.ExpressionTree 7 | { 8 | abstract class Node 9 | { 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /LambdaSqlBuilder/Resolver/ExpressionTree/OperationNode.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Linq.Expressions; 5 | using System.Text; 6 | 7 | namespace LambdaSqlBuilder.Resolver.ExpressionTree 8 | { 9 | class OperationNode : Node 10 | { 11 | public ExpressionType Operator { get; set; } 12 | public Node Left { get; set; } 13 | public Node Right { get; set; } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /LambdaSqlBuilder/Resolver/ExpressionTree/SingleOperationNode.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Linq.Expressions; 5 | using System.Text; 6 | 7 | namespace LambdaSqlBuilder.Resolver.ExpressionTree 8 | { 9 | class SingleOperationNode : Node 10 | { 11 | public ExpressionType Operator { get; set; } 12 | public Node Child { get; set; } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /LambdaSqlBuilder/Resolver/ExpressionTree/ValueNode.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | 6 | namespace LambdaSqlBuilder.Resolver.ExpressionTree 7 | { 8 | class ValueNode : Node 9 | { 10 | public object Value { get; set; } 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /LambdaSqlBuilder/Resolver/LambdaResolver.cs: -------------------------------------------------------------------------------- 1 | /* License: http://www.apache.org/licenses/LICENSE-2.0 */ 2 | 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Linq; 6 | using System.Linq.Expressions; 7 | using System.Reflection; 8 | using System.Runtime.CompilerServices; 9 | using System.Text; 10 | using LambdaSqlBuilder.Builder; 11 | 12 | namespace LambdaSqlBuilder.Resolver 13 | { 14 | partial class LambdaResolver 15 | { 16 | private Dictionary _operationDictionary = new Dictionary() 17 | { 18 | { ExpressionType.Equal, "="}, 19 | { ExpressionType.NotEqual, "!="}, 20 | { ExpressionType.GreaterThan, ">"}, 21 | { ExpressionType.LessThan, "<"}, 22 | { ExpressionType.GreaterThanOrEqual, ">="}, 23 | { ExpressionType.LessThanOrEqual, "<="} 24 | }; 25 | 26 | private SqlQueryBuilder _builder { get; set; } 27 | 28 | public LambdaResolver(SqlQueryBuilder builder) 29 | { 30 | _builder = builder; 31 | } 32 | 33 | #region helpers 34 | public static string GetColumnName(Expression> selector) 35 | { 36 | return GetColumnName(GetMemberExpression(selector.Body)); 37 | } 38 | 39 | public static string GetColumnName(Expression expression) 40 | { 41 | var member = GetMemberExpression(expression); 42 | var column = member.Member.GetCustomAttributes(false).OfType().FirstOrDefault(); 43 | if (column != null) 44 | return column.Name; 45 | else 46 | return member.Member.Name; 47 | } 48 | 49 | public static string GetTableName() 50 | { 51 | return GetTableName(typeof(T)); 52 | } 53 | 54 | public static string GetTableName(Type type) 55 | { 56 | var column = type.GetCustomAttributes(false).OfType().FirstOrDefault(); 57 | if (column != null) 58 | return column.Name; 59 | else 60 | return type.Name; 61 | } 62 | 63 | private static string GetTableName(MemberExpression expression) 64 | { 65 | return GetTableName(expression.Member.DeclaringType); 66 | } 67 | 68 | private static BinaryExpression GetBinaryExpression(Expression expression) 69 | { 70 | if (expression is BinaryExpression) 71 | return expression as BinaryExpression; 72 | 73 | throw new ArgumentException("Binary expression expected"); 74 | } 75 | 76 | private static MemberExpression GetMemberExpression(Expression expression) 77 | { 78 | switch (expression.NodeType) 79 | { 80 | case ExpressionType.MemberAccess: 81 | return expression as MemberExpression; 82 | case ExpressionType.Convert: 83 | return GetMemberExpression((expression as UnaryExpression).Operand); 84 | } 85 | 86 | throw new ArgumentException("Member expression expected"); 87 | } 88 | 89 | #endregion 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /LambdaSqlBuilder/Resolver/LambdaResolverIsIn.cs: -------------------------------------------------------------------------------- 1 | /* License: http://www.apache.org/licenses/LICENSE-2.0 */ 2 | 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Linq; 6 | using System.Linq.Expressions; 7 | using System.Text; 8 | 9 | namespace LambdaSqlBuilder.Resolver 10 | { 11 | partial class LambdaResolver 12 | { 13 | public void QueryByIsIn(Expression> expression, SqlLamBase sqlQuery) 14 | { 15 | var fieldName = GetColumnName(expression); 16 | _builder.QueryByIsIn(GetTableName(), fieldName, sqlQuery); 17 | } 18 | 19 | public void QueryByIsIn(Expression> expression, IEnumerable values) 20 | { 21 | var fieldName = GetColumnName(expression); 22 | _builder.QueryByIsIn(GetTableName(), fieldName, values); 23 | } 24 | 25 | public void QueryByNotIn(Expression> expression, SqlLamBase sqlQuery) 26 | { 27 | var fieldName = GetColumnName(expression); 28 | _builder.Not(); 29 | _builder.QueryByIsIn(GetTableName(), fieldName, sqlQuery); 30 | } 31 | 32 | public void QueryByNotIn(Expression> expression, IEnumerable values) 33 | { 34 | var fieldName = GetColumnName(expression); 35 | _builder.Not(); 36 | _builder.QueryByIsIn(GetTableName(), fieldName, values); 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /LambdaSqlBuilder/Resolver/LambdaResolverQuery.cs: -------------------------------------------------------------------------------- 1 | /* License: http://www.apache.org/licenses/LICENSE-2.0 */ 2 | 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Linq; 6 | using System.Linq.Expressions; 7 | using System.Reflection; 8 | using System.Text; 9 | using LambdaSqlBuilder.Resolver.ExpressionTree; 10 | using LambdaSqlBuilder.ValueObjects; 11 | using System.Diagnostics; 12 | 13 | namespace LambdaSqlBuilder.Resolver 14 | { 15 | partial class LambdaResolver 16 | { 17 | public void ResolveQuery(Expression> expression) 18 | { 19 | var expressionTree = ResolveQuery((dynamic)expression.Body); 20 | BuildSql(expressionTree); 21 | } 22 | 23 | private Node ResolveQuery(ConstantExpression constantExpression) 24 | { 25 | return new ValueNode() { Value = constantExpression.Value}; 26 | } 27 | 28 | private Node ResolveQuery(UnaryExpression unaryExpression) 29 | { 30 | return new SingleOperationNode() 31 | { 32 | Operator = unaryExpression.NodeType, 33 | Child = ResolveQuery((dynamic) unaryExpression.Operand) 34 | }; 35 | } 36 | 37 | private Node ResolveQuery(BinaryExpression binaryExpression) 38 | { 39 | return new OperationNode 40 | { 41 | Left = ResolveQuery((dynamic) binaryExpression.Left), 42 | Operator = binaryExpression.NodeType, 43 | Right = ResolveQuery((dynamic) binaryExpression.Right) 44 | }; 45 | } 46 | 47 | private Node ResolveQuery(MethodCallExpression callExpression) 48 | { 49 | LikeMethod callFunction; 50 | if (Enum.TryParse(callExpression.Method.Name, true, out callFunction)) 51 | { 52 | var member = callExpression.Object as MemberExpression; 53 | var fieldValue = (string)GetExpressionValue(callExpression.Arguments.First()); 54 | 55 | return new LikeNode() 56 | { 57 | MemberNode = new MemberNode() 58 | { 59 | TableName = GetTableName(member), 60 | FieldName = GetColumnName(callExpression.Object) 61 | }, 62 | Method = callFunction, 63 | Value = fieldValue 64 | }; 65 | } 66 | else 67 | { 68 | var value = ResolveMethodCall(callExpression); 69 | return new ValueNode() { Value = value }; 70 | } 71 | } 72 | 73 | private Node ResolveQuery(MemberExpression memberExpression, MemberExpression rootExpression = null) 74 | { 75 | rootExpression = rootExpression ?? memberExpression; 76 | switch (memberExpression.Expression.NodeType) 77 | { 78 | case ExpressionType.Parameter: 79 | return new MemberNode() 80 | {TableName = GetTableName(rootExpression), FieldName = GetColumnName(rootExpression)}; 81 | case ExpressionType.MemberAccess: 82 | return ResolveQuery(memberExpression.Expression as MemberExpression, rootExpression); 83 | case ExpressionType.Call: 84 | case ExpressionType.Constant: 85 | return new ValueNode() {Value = GetExpressionValue(rootExpression)}; 86 | default: 87 | throw new ArgumentException("Expected member expression"); 88 | } 89 | } 90 | 91 | #region Helpers 92 | 93 | private object GetExpressionValue(Expression expression) 94 | { 95 | switch (expression.NodeType) 96 | { 97 | case ExpressionType.Constant: 98 | return (expression as ConstantExpression).Value; 99 | case ExpressionType.Call: 100 | return ResolveMethodCall(expression as MethodCallExpression); 101 | case ExpressionType.MemberAccess: 102 | var memberExpr = (expression as MemberExpression); 103 | var obj = GetExpressionValue(memberExpr.Expression); 104 | return ResolveValue((dynamic) memberExpr.Member, obj); 105 | default: 106 | throw new ArgumentException("Expected constant expression"); 107 | } 108 | } 109 | 110 | private object ResolveMethodCall(MethodCallExpression callExpression) 111 | { 112 | var arguments = callExpression.Arguments.Select(GetExpressionValue).ToArray(); 113 | var obj = callExpression.Object != null ? GetExpressionValue(callExpression.Object) : arguments.First(); 114 | 115 | return callExpression.Method.Invoke(obj, arguments); 116 | } 117 | 118 | private object ResolveValue(PropertyInfo property, object obj) 119 | { 120 | return property.GetValue(obj, null); 121 | } 122 | 123 | private object ResolveValue(FieldInfo field, object obj) 124 | { 125 | return field.GetValue(obj); 126 | } 127 | 128 | #endregion 129 | 130 | #region Fail functions 131 | 132 | private void ResolveQuery(Expression expression) 133 | { 134 | throw new ArgumentException(string.Format("The provided expression '{0}' is currently not supported", expression.NodeType)); 135 | } 136 | 137 | #endregion 138 | } 139 | } 140 | -------------------------------------------------------------------------------- /LambdaSqlBuilder/Resolver/LambdaResolverSpec.cs: -------------------------------------------------------------------------------- 1 | /* License: http://www.apache.org/licenses/LICENSE-2.0 */ 2 | 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Linq; 6 | using System.Linq.Expressions; 7 | using System.Text; 8 | using LambdaSqlBuilder.ValueObjects; 9 | 10 | namespace LambdaSqlBuilder.Resolver 11 | { 12 | partial class LambdaResolver 13 | { 14 | public void Join(Expression> expression) 15 | { 16 | var joinExpression = GetBinaryExpression(expression.Body); 17 | var leftExpression = GetMemberExpression(joinExpression.Left); 18 | var rightExpression = GetMemberExpression(joinExpression.Right); 19 | 20 | Join(leftExpression, rightExpression); 21 | } 22 | 23 | public void Join(Expression> leftExpression, Expression> rightExpression) 24 | { 25 | Join(GetMemberExpression(leftExpression.Body), GetMemberExpression(rightExpression.Body)); 26 | } 27 | 28 | public void Join(MemberExpression leftExpression, MemberExpression rightExpression) 29 | { 30 | _builder.Join(GetTableName(), GetTableName(), GetColumnName(leftExpression), GetColumnName(rightExpression)); 31 | } 32 | 33 | public void OrderBy(Expression> expression, bool desc = false) 34 | { 35 | var fieldName = GetColumnName(GetMemberExpression(expression.Body)); 36 | _builder.OrderBy(GetTableName(), fieldName, desc); 37 | } 38 | 39 | public void Select(Expression> expression) 40 | { 41 | Select(expression.Body); 42 | } 43 | 44 | private void Select(Expression expression) 45 | { 46 | switch (expression.NodeType) 47 | { 48 | case ExpressionType.Parameter: 49 | _builder.Select(GetTableName(expression.Type)); 50 | break; 51 | case ExpressionType.Convert: 52 | case ExpressionType.MemberAccess: 53 | Select(GetMemberExpression(expression)); 54 | break; 55 | case ExpressionType.New: 56 | foreach (MemberExpression memberExp in (expression as NewExpression).Arguments) 57 | Select(memberExp); 58 | break; 59 | default: 60 | throw new ArgumentException("Invalid expression"); 61 | } 62 | } 63 | 64 | private void Select(MemberExpression expression) 65 | { 66 | if (expression.Type.IsClass && expression.Type != typeof(String)) 67 | _builder.Select(GetTableName(expression.Type)); 68 | else 69 | _builder.Select(GetTableName(), GetColumnName(expression)); 70 | } 71 | 72 | public void SelectWithFunction(Expression> expression, SelectFunction selectFunction) 73 | { 74 | SelectWithFunction(expression.Body, selectFunction); 75 | } 76 | 77 | private void SelectWithFunction(Expression expression, SelectFunction selectFunction) 78 | { 79 | var fieldName = GetColumnName(GetMemberExpression(expression)); 80 | _builder.Select(GetTableName(), fieldName, selectFunction); 81 | } 82 | 83 | public void GroupBy(Expression> expression) 84 | { 85 | GroupBy(GetMemberExpression(expression.Body)); 86 | } 87 | 88 | private void GroupBy(MemberExpression expression) 89 | { 90 | var fieldName = GetColumnName(GetMemberExpression(expression)); 91 | _builder.GroupBy(GetTableName(), fieldName); 92 | } 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /LambdaSqlBuilder/Resolver/LambdaResolverTree.cs: -------------------------------------------------------------------------------- 1 | /* License: http://www.apache.org/licenses/LICENSE-2.0 */ 2 | 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Linq; 6 | using System.Linq.Expressions; 7 | using System.Text; 8 | using LambdaSqlBuilder.Resolver.ExpressionTree; 9 | using LambdaSqlBuilder.ValueObjects; 10 | 11 | namespace LambdaSqlBuilder.Resolver 12 | { 13 | partial class LambdaResolver 14 | { 15 | void BuildSql(Node node) 16 | { 17 | BuildSql((dynamic)node); 18 | } 19 | 20 | void BuildSql(LikeNode node) 21 | { 22 | if(node.Method == LikeMethod.Equals) 23 | { 24 | _builder.QueryByField(node.MemberNode.TableName, node.MemberNode.FieldName, 25 | _operationDictionary[ExpressionType.Equal], node.Value); 26 | } 27 | else 28 | { 29 | string value = node.Value; 30 | switch (node.Method) 31 | { 32 | case LikeMethod.StartsWith: 33 | value = node.Value + "%"; 34 | break; 35 | case LikeMethod.EndsWith: 36 | value = "%" + node.Value; 37 | break; 38 | case LikeMethod.Contains: 39 | value = "%" + node.Value + "%"; 40 | break; 41 | } 42 | _builder.QueryByFieldLike(node.MemberNode.TableName, node.MemberNode.FieldName, value); 43 | } 44 | } 45 | 46 | void BuildSql(OperationNode node) 47 | { 48 | BuildSql((dynamic)node.Left, (dynamic)node.Right, node.Operator); 49 | } 50 | 51 | void BuildSql(MemberNode memberNode) 52 | { 53 | _builder.QueryByField(memberNode.TableName, memberNode.FieldName, _operationDictionary[ExpressionType.Equal], true); 54 | } 55 | 56 | void BuildSql(SingleOperationNode node) 57 | { 58 | if(node.Operator == ExpressionType.Not) 59 | _builder.Not(); 60 | BuildSql(node.Child); 61 | } 62 | 63 | void BuildSql(MemberNode memberNode, ValueNode valueNode, ExpressionType op) 64 | { 65 | if(valueNode.Value == null) 66 | { 67 | ResolveNullValue(memberNode, op); 68 | } 69 | else 70 | { 71 | _builder.QueryByField(memberNode.TableName, memberNode.FieldName, _operationDictionary[op], valueNode.Value); 72 | } 73 | } 74 | 75 | void BuildSql(ValueNode valueNode, MemberNode memberNode, ExpressionType op) 76 | { 77 | BuildSql(memberNode, valueNode, op); 78 | } 79 | 80 | void BuildSql(MemberNode leftMember, MemberNode rightMember, ExpressionType op) 81 | { 82 | _builder.QueryByFieldComparison(leftMember.TableName, leftMember.FieldName, _operationDictionary[op], rightMember.TableName, rightMember.FieldName); 83 | } 84 | 85 | void BuildSql(SingleOperationNode leftMember, Node rightMember, ExpressionType op) 86 | { 87 | if (leftMember.Operator == ExpressionType.Not) 88 | BuildSql(leftMember as Node, rightMember, op); 89 | else 90 | BuildSql((dynamic)leftMember.Child, (dynamic)rightMember, op); 91 | } 92 | 93 | void BuildSql(Node leftMember, SingleOperationNode rightMember, ExpressionType op) 94 | { 95 | BuildSql(rightMember, leftMember, op); 96 | } 97 | 98 | void BuildSql(Node leftNode, Node rightNode, ExpressionType op) 99 | { 100 | _builder.BeginExpression(); 101 | BuildSql((dynamic)leftNode); 102 | ResolveOperation(op); 103 | BuildSql((dynamic)rightNode); 104 | _builder.EndExpression(); 105 | } 106 | 107 | void ResolveNullValue(MemberNode memberNode, ExpressionType op) 108 | { 109 | switch (op) 110 | { 111 | case ExpressionType.Equal: 112 | _builder.QueryByFieldNull(memberNode.TableName, memberNode.FieldName); 113 | break; 114 | case ExpressionType.NotEqual: 115 | _builder.QueryByFieldNotNull(memberNode.TableName, memberNode.FieldName); 116 | break; 117 | } 118 | } 119 | 120 | void ResolveSingleOperation(ExpressionType op) 121 | { 122 | switch (op) 123 | { 124 | case ExpressionType.Not: 125 | _builder.Not(); 126 | break; 127 | } 128 | } 129 | 130 | void ResolveOperation(ExpressionType op) 131 | { 132 | switch (op) 133 | { 134 | case ExpressionType.And: 135 | case ExpressionType.AndAlso: 136 | _builder.And(); 137 | break; 138 | case ExpressionType.Or: 139 | case ExpressionType.OrElse: 140 | _builder.Or(); 141 | break; 142 | default: 143 | throw new ArgumentException(string.Format("Unrecognized binary expression operation '{0}'", op.ToString())); 144 | } 145 | } 146 | } 147 | } 148 | -------------------------------------------------------------------------------- /LambdaSqlBuilder/SqlLam.cs: -------------------------------------------------------------------------------- 1 | /* License: http://www.apache.org/licenses/LICENSE-2.0 */ 2 | 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Linq.Expressions; 6 | using LambdaSqlBuilder.Builder; 7 | using LambdaSqlBuilder.Resolver; 8 | using LambdaSqlBuilder.ValueObjects; 9 | 10 | namespace LambdaSqlBuilder 11 | { 12 | /// 13 | /// The single most important LambdaSqlBuilder class. Encapsulates the whole SQL building and lambda expression resolving logic. 14 | /// Serves as a proxy to the underlying SQL builder and the lambda expression resolver. It should be used to continually build the SQL query 15 | /// and then request the QueryString as well as the QueryParameters at the end. 16 | /// 17 | /// Entity type required for lambda expressions as well as for proper resolution of the table name and the column names 18 | public class SqlLam : SqlLamBase 19 | { 20 | public SqlLam() 21 | { 22 | _builder = new SqlQueryBuilder(LambdaResolver.GetTableName(), _defaultAdapter); 23 | _resolver = new LambdaResolver(_builder); 24 | } 25 | 26 | public SqlLam(Expression> expression) : this() 27 | { 28 | Where(expression); 29 | } 30 | 31 | internal SqlLam(SqlQueryBuilder builder, LambdaResolver resolver) 32 | { 33 | _builder = builder; 34 | _resolver = resolver; 35 | } 36 | 37 | public SqlLam Where(Expression> expression) 38 | { 39 | return And(expression); 40 | } 41 | 42 | public SqlLam And(Expression> expression) 43 | { 44 | _builder.And(); 45 | _resolver.ResolveQuery(expression); 46 | return this; 47 | } 48 | 49 | public SqlLam Or(Expression> expression) 50 | { 51 | _builder.Or(); 52 | _resolver.ResolveQuery(expression); 53 | return this; 54 | } 55 | 56 | public SqlLam WhereIsIn(Expression> expression, SqlLamBase sqlQuery) 57 | { 58 | _builder.And(); 59 | _resolver.QueryByIsIn(expression, sqlQuery); 60 | return this; 61 | } 62 | 63 | public SqlLam WhereIsIn(Expression> expression, IEnumerable values) 64 | { 65 | _builder.And(); 66 | _resolver.QueryByIsIn(expression, values); 67 | return this; 68 | } 69 | 70 | public SqlLam WhereNotIn(Expression> expression, SqlLamBase sqlQuery) 71 | { 72 | _builder.And(); 73 | _resolver.QueryByNotIn(expression, sqlQuery); 74 | return this; 75 | } 76 | 77 | public SqlLam WhereNotIn(Expression> expression, IEnumerable values) 78 | { 79 | _builder.And(); 80 | _resolver.QueryByNotIn(expression, values); 81 | return this; 82 | } 83 | 84 | public SqlLam OrderBy(Expression> expression) 85 | { 86 | _resolver.OrderBy(expression); 87 | return this; 88 | } 89 | 90 | public SqlLam OrderByDescending(Expression> expression) 91 | { 92 | _resolver.OrderBy(expression, true); 93 | return this; 94 | } 95 | 96 | public SqlLam Select(params Expression>[] expressions) 97 | { 98 | foreach (var expression in expressions) 99 | _resolver.Select(expression); 100 | return this; 101 | } 102 | 103 | public SqlLam SelectCount(Expression> expression) 104 | { 105 | _resolver.SelectWithFunction(expression, SelectFunction.COUNT); 106 | return this; 107 | } 108 | 109 | public SqlLam SelectDistinct(Expression> expression) 110 | { 111 | _resolver.SelectWithFunction(expression, SelectFunction.DISTINCT); 112 | return this; 113 | } 114 | 115 | public SqlLam SelectSum(Expression> expression) 116 | { 117 | _resolver.SelectWithFunction(expression, SelectFunction.SUM); 118 | return this; 119 | } 120 | 121 | public SqlLam SelectMax(Expression> expression) 122 | { 123 | _resolver.SelectWithFunction(expression, SelectFunction.MAX); 124 | return this; 125 | } 126 | 127 | public SqlLam SelectMin(Expression> expression) 128 | { 129 | _resolver.SelectWithFunction(expression, SelectFunction.MIN); 130 | return this; 131 | } 132 | 133 | public SqlLam SelectAverage(Expression> expression) 134 | { 135 | _resolver.SelectWithFunction(expression, SelectFunction.AVG); 136 | return this; 137 | } 138 | 139 | public SqlLam Join(SqlLam joinQuery, 140 | Expression> primaryKeySelector, 141 | Expression> foreignKeySelector, 142 | Func selection) 143 | { 144 | var query = new SqlLam(_builder, _resolver); 145 | _resolver.Join(primaryKeySelector, foreignKeySelector); 146 | return query; 147 | } 148 | 149 | public SqlLam Join(Expression> expression) 150 | { 151 | var joinQuery = new SqlLam(_builder, _resolver); 152 | _resolver.Join(expression); 153 | return joinQuery; 154 | } 155 | 156 | public SqlLam GroupBy(Expression> expression) 157 | { 158 | _resolver.GroupBy(expression); 159 | return this; 160 | } 161 | } 162 | } 163 | -------------------------------------------------------------------------------- /LambdaSqlBuilder/SqlLamBase.cs: -------------------------------------------------------------------------------- 1 | /* License: http://www.apache.org/licenses/LICENSE-2.0 */ 2 | 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Linq; 6 | using System.Text; 7 | using LambdaSqlBuilder.Adapter; 8 | using LambdaSqlBuilder.Builder; 9 | using LambdaSqlBuilder.Resolver; 10 | using LambdaSqlBuilder.ValueObjects; 11 | 12 | namespace LambdaSqlBuilder 13 | { 14 | /// 15 | /// Base functionality for the SqlLam class that is not related to any specific entity type 16 | /// 17 | public abstract class SqlLamBase 18 | { 19 | internal static ISqlAdapter _defaultAdapter = new SqlServer2012Adapter(); 20 | internal SqlQueryBuilder _builder; 21 | internal LambdaResolver _resolver; 22 | 23 | public SqlQueryBuilder SqlBuilder { get { return _builder; } } 24 | 25 | public string QueryString 26 | { 27 | get { return _builder.QueryString; } 28 | } 29 | 30 | public string QueryStringPage(int pageSize, int? pageNumber = null) 31 | { 32 | return _builder.QueryStringPage(pageSize, pageNumber); 33 | } 34 | 35 | public IDictionary QueryParameters 36 | { 37 | get { return _builder.Parameters; } 38 | } 39 | 40 | public string[] SplitColumns 41 | { 42 | get { return _builder.SplitColumns.ToArray(); } 43 | } 44 | 45 | public static void SetAdapter(SqlAdapter adapter) 46 | { 47 | _defaultAdapter = GetAdapterInstance(adapter); 48 | } 49 | 50 | private static ISqlAdapter GetAdapterInstance(SqlAdapter adapter) 51 | { 52 | switch (adapter) 53 | { 54 | case SqlAdapter.SqlServer2008: 55 | return new SqlServer2008Adapter(); 56 | case SqlAdapter.SqlServer2012: 57 | return new SqlServer2012Adapter(); 58 | default: 59 | throw new ArgumentException("The specified Sql Adapter was not recognized"); 60 | } 61 | } 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /LambdaSqlBuilder/SqlLamColumnAttribute.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace LambdaSqlBuilder 4 | { 5 | /// 6 | /// Configures the name of the column related to this property. If the attribute is not specified, the property name is used instead. 7 | /// 8 | [AttributeUsage(AttributeTargets.Property)] 9 | public class SqlLamColumnAttribute : Attribute 10 | { 11 | public string Name { get; set; } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /LambdaSqlBuilder/SqlLamTableAttribute.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace LambdaSqlBuilder 4 | { 5 | /// 6 | /// Configures the name of the db table related to this entity. If the attribute is not specified, the class name is used instead. 7 | /// 8 | [AttributeUsage(AttributeTargets.Class)] 9 | public class SqlLamTableAttribute : Attribute 10 | { 11 | public string Name { get; set; } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /LambdaSqlBuilder/ValueObjects/LikeMethod.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | 6 | namespace LambdaSqlBuilder.ValueObjects 7 | { 8 | /// 9 | /// An enumeration of the supported string methods for the SQL LIKE statement. The item names should match the related string methods. 10 | /// 11 | public enum LikeMethod 12 | { 13 | StartsWith, 14 | EndsWith, 15 | Contains, 16 | Equals 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /LambdaSqlBuilder/ValueObjects/SelectFunction.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | 6 | namespace LambdaSqlBuilder.ValueObjects 7 | { 8 | /// 9 | /// An enumeration of the supported aggregate SQL functions. The item names should match the related function names 10 | /// 11 | public enum SelectFunction 12 | { 13 | COUNT, 14 | DISTINCT, 15 | SUM, 16 | MIN, 17 | MAX, 18 | AVG 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /LambdaSqlBuilder/ValueObjects/SqlAdapter.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using LambdaSqlBuilder.Adapter; 6 | 7 | namespace LambdaSqlBuilder.ValueObjects 8 | { 9 | /// 10 | /// An enumeration of the available SQL adapters. Can be used to set the backing database for db specific SQL syntax 11 | /// 12 | public enum SqlAdapter 13 | { 14 | SqlServer2008, 15 | SqlServer2012 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /License.txt: -------------------------------------------------------------------------------- 1 | http://www.apache.org/licenses/LICENSE-2.0 -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Lambda SQL Builder is a neat .NET library for independent creation of SQL queries in a strongly typed fashion. 2 | -------------------------------------------------------------------------------------------------------------- 3 | 4 | Support 5 | ======= 6 | Apart from standard SQL queries, the currently supported SQL databases are SQL Server 2008 and SQL Server 2012. 7 | The minimum .NET version required is 4.0. 8 | 9 | Motivation 10 | ========== 11 | The inspiration for Lambda SQL Builder came from tools like **Linq2SQL**, **Entity Framework**, and **QueryOver** 12 | in NHibernate. I used to be a big fan of these ORMs, although they gave me some serious headaches from time to time. 13 | However, nothing lasts forever, and I have recently made a decision to join the new rising trend of using Micro-ORMs 14 | instead of those huge mighty frameworks. And I definately did not regret that. But I still felt like there was 15 | something missing, something that I had to sacrifice in order to gain the flexibility and performance back after 16 | years of using big ORMs. And I soon realized what this thing was: 17 | 18 | **Using C# code to write my SQL queries.** 19 | 20 | No change tracking, no sessions, it was really just this single feature. I simply hate those SQL strings lying there 21 | in my application and forcing me to think twice anytime I want to refactor something. After some research, I have 22 | decided to write something on my own, something independent, something that would provide this single feature and 23 | nothing more, so I can use it along with **SQLCommand**, or with **Dapper**, or even with Nhibernate if it feels 24 | right. It is not my intension to cover the whole SQL world, however, I do believe that it will satisfy most 25 | of the common needs. It certainly does in my case, as I was already able to replace 99% of the queries that I needed 26 | in my recent projects. 27 | 28 | I will be glad if you like the tool. If you don’t, let me know why, so I can make it better. 29 | 30 | Usage 31 | ===== 32 | This basic example queries the database for products named „Tofu” using my favorite Micro-ORM called Dapper: 33 | ```csharp 34 | var query = new SqlLam(p => p.ProductName == "Tofu"); 35 | var results = Connection.Query(query.QueryString, query.QueryParameters); 36 | ``` 37 | 38 | Of course, you can also do this with SQLCommand and SqlDataReader without using any ORM: 39 | ```csharp 40 | var query = new SqlLam(p => p.ProductName == "Tofu"); 41 | 42 | var selectCommand = new SqlCommand(query.QueryString, Connection); 43 | 44 | foreach (var param in query.QueryParameters) 45 | selectCommand.Parameters.AddWithValue(param.Key, param.Value); 46 | 47 | var result = selectCommand.ExecuteReader(); 48 | ``` 49 | 50 | If you prefer using LINQ, you can write something like this: 51 | ```csharp 52 | var query = from product in new SqlLam() 53 | where product.ProductName == "Tofu" 54 | select product; 55 | 56 | var results = Connection.Query(query.QueryString, query.QueryParameters) 57 | ``` 58 | 59 | As you can see the QueryString property will return the SQL string itself, while the QueryParameters property refers 60 | to a dictionary of SQL parameters. 61 | 62 | Most of the time you will create a SqlLam object, call some methods on it, and then request the QueryString and 63 | the QueryParameters at the end. You can also call the methods in a chain, if you prefer. Here are some more 64 | complicated queries, so you get an idea of what is the tool capable of: 65 | ```csharp 66 | var query = new SqlLam(p => !(p.City == "Seattle" || p.City == "Redmond")) 67 | .Or(p => p.Title != "Sales Representative") 68 | .OrderByDescending(p => p.FirstName); 69 | ``` 70 | ```csharp 71 | var query = from product in new SqlLam() 72 | join category in new SqlLam() 73 | on product.CategoryId equals category.CategoryId 74 | where product.ReorderLevel == 25 && category.CategoryName == "Beverages" 75 | select product; 76 | ``` 77 | ```csharp 78 | var subQuery = new SqlLam() 79 | .WhereIsIn(c => c.CategoryName, new object[] { "Beverages", "Condiments" }) 80 | .Select(p => p.CategoryId); 81 | 82 | var query = new SqlLam() 83 | .Join((p, c) => p.CategoryId == c.CategoryId) 84 | .WhereIsIn(c => c.CategoryId, subQuery); 85 | ``` 86 | 87 | Internally there is a Lambda Resolver that translates the C# code into SQL strings, and a SQL Builder, which collects 88 | the SQL strings and stores them as individual SQL clauses (Selection, Join, Order By,...). Everytime you call 89 | a method on the SqlLam object, it is translated into a SQL string, which is stored into the related SQL clause 90 | in the underlying SQL builder. If the expression contains variables, they are stored into the dictionary 91 | of SQL parameters. When requesting the QueryString, the SQL clauses are combined and returned as a single 92 | SQL statement. 93 | 94 | There are also additional SQL adapters in development, that should expand the support for database-specific SQL statements. 95 | 96 | Reference 97 | ========= 98 | There will be later a deeper description of various use cases. Make sure to check out the unit tests for more examples. 99 | --------------------------------------------------------------------------------