├── .gitignore ├── .tests └── DynamicExpression.Test │ ├── CriteriaBuilderTest.cs │ ├── CriteriaExpressionTest.cs │ ├── DynamicExpression.Test.csproj │ └── Extensions │ └── QueryableExtensionsTest.cs ├── DynamicExpression.sln ├── DynamicExpression.sln.DotSettings ├── DynamicExpression ├── CriteriaBuilder.cs ├── CriteriaExpression.cs ├── DynamicExpression.csproj ├── Entities │ ├── Criteria.cs │ ├── Ordering.cs │ ├── Pagination.cs │ └── Query.cs ├── Enums │ ├── LogicalType.cs │ ├── OperationType.cs │ └── OrderingDirection.cs ├── Extensions │ ├── QueryableExtensions.cs │ ├── ServiceCollectionExtensions.cs │ └── TypeExtensions.cs ├── Interfaces │ ├── IQuery.cs │ └── IQueryCriteria.cs ├── ModelBinders │ ├── Const │ │ └── Constants.cs │ ├── Extensions │ │ └── StreamExtensions.cs │ ├── QueryModelBinder.cs │ └── QueryModelBinderProvider.cs └── Properties │ └── InternalsVisibleTo.cs ├── LICENSE ├── README.md ├── appveyor.yml └── icon.jpg /.gitignore: -------------------------------------------------------------------------------- 1 | **/*.user 2 | **/*.userprefs 3 | *.user 4 | *.userprefs 5 | *.suo 6 | *.tss 7 | *.vs 8 | _ReSharper* 9 | **/bin 10 | **/obj 11 | *.DotSettings.User 12 | **/wwwroot/lib 13 | -------------------------------------------------------------------------------- /.tests/DynamicExpression.Test/CriteriaBuilderTest.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using DynamicExpression.Enums; 4 | using Microsoft.VisualStudio.TestTools.UnitTesting; 5 | using NetTopologySuite.Geometries; 6 | 7 | namespace DynamicExpression.Test; 8 | 9 | [TestClass] 10 | public class CriteriaBuilderTest 11 | { 12 | [TestMethod] 13 | public void BuildWhenEqualTest() 14 | { 15 | var criteriaExpression = new CriteriaExpression(); 16 | criteriaExpression.Equal("Name", "value"); 17 | 18 | var expression = CriteriaBuilder.Build(criteriaExpression); 19 | 20 | Assert.IsNotNull(expression); 21 | Assert.IsNotNull(expression.Compile()); 22 | Assert.AreEqual("((x.Name != null) AndAlso (x.Name == \"value\"))", expression.Body.ToString()); 23 | } 24 | 25 | [TestMethod] 26 | public void BuildWhenEqualWhenTimeSpanTest() 27 | { 28 | var criteriaExpression = new CriteriaExpression(); 29 | criteriaExpression.Equal("Date", DateOnly.MaxValue); 30 | 31 | var expression = CriteriaBuilder.Build(criteriaExpression); 32 | 33 | Assert.IsNotNull(expression); 34 | Assert.IsNotNull(expression.Compile()); 35 | Assert.AreEqual($"(x.Date == {DateOnly.MaxValue})", expression.Body.ToString()); 36 | } 37 | 38 | [TestMethod] 39 | public void BuildWhenEqualWhenDateOnlyTest() 40 | { 41 | var criteriaExpression = new CriteriaExpression(); 42 | criteriaExpression.Equal("Date", DateOnly.MaxValue); 43 | 44 | var expression = CriteriaBuilder.Build(criteriaExpression); 45 | 46 | Assert.IsNotNull(expression); 47 | Assert.IsNotNull(expression.Compile()); 48 | Assert.AreEqual($"(x.Date == {DateOnly.MaxValue})", expression.Body.ToString()); 49 | } 50 | 51 | [TestMethod] 52 | public void BuildWhenEqualWhenDateTimeTest() 53 | { 54 | var criteriaExpression = new CriteriaExpression(); 55 | criteriaExpression.Equal("DateTime", DateTime.MaxValue); 56 | 57 | var expression = CriteriaBuilder.Build(criteriaExpression); 58 | 59 | Assert.IsNotNull(expression); 60 | Assert.IsNotNull(expression.Compile()); 61 | Assert.AreEqual($"(x.DateTime == {DateTime.MaxValue})", expression.Body.ToString()); 62 | } 63 | 64 | [TestMethod] 65 | public void BuildWhenEqualWhenDateTimeOffsetTest() 66 | { 67 | var criteriaExpression = new CriteriaExpression(); 68 | criteriaExpression.Equal("DateTimeOffset", DateTimeOffset.MaxValue); 69 | 70 | var expression = CriteriaBuilder.Build(criteriaExpression); 71 | 72 | Assert.IsNotNull(expression); 73 | Assert.IsNotNull(expression.Compile()); 74 | Assert.AreEqual($"(x.DateTimeOffset == {DateTimeOffset.MaxValue})", expression.Body.ToString()); 75 | } 76 | 77 | [TestMethod] 78 | public void BuildWhenEqualAndEnumTest() 79 | { 80 | var criteriaExpression = new CriteriaExpression(); 81 | criteriaExpression.Equal("Flags", FlagsEnum.One); 82 | 83 | var expression = CriteriaBuilder.Build(criteriaExpression); 84 | 85 | Assert.IsNotNull(expression); 86 | Assert.IsNotNull(expression.Compile()); 87 | Assert.AreEqual("(Convert(x.Flags, Int32) == Convert(One, Int32))", expression.Body.ToString()); 88 | } 89 | 90 | [TestMethod] 91 | public void BuildWhenEqualAndGuidTest() 92 | { 93 | var criteriaExpression = new CriteriaExpression(); 94 | 95 | var guid = Guid.NewGuid(); 96 | criteriaExpression.Equal("Id", guid); 97 | criteriaExpression.Equal("IdNullable", guid); 98 | 99 | var expression = CriteriaBuilder.Build(criteriaExpression); 100 | 101 | Assert.IsNotNull(expression); 102 | Assert.IsNotNull(expression.Compile()); 103 | Assert.AreEqual($"((x.Id == {guid}) AndAlso ((x.IdNullable != null) AndAlso (x.IdNullable == {guid})))", expression.Body.ToString()); 104 | } 105 | 106 | [TestMethod] 107 | public void BuildWhenEqualAndGuidNullableTest() 108 | { 109 | var criteriaExpression = new CriteriaExpression(); 110 | 111 | Guid? guid = Guid.NewGuid(); 112 | criteriaExpression.Equal("Id", guid); 113 | criteriaExpression.Equal("IdNullable", guid); 114 | 115 | var expression = CriteriaBuilder.Build(criteriaExpression); 116 | 117 | Assert.IsNotNull(expression); 118 | Assert.IsNotNull(expression.Compile()); 119 | Assert.AreEqual($"((x.Id == {guid}) AndAlso ((x.IdNullable != null) AndAlso (x.IdNullable == {guid})))", expression.Body.ToString()); 120 | } 121 | 122 | [TestMethod] 123 | public void BuildWhenNotEqualTest() 124 | { 125 | var criteriaExpression = new CriteriaExpression(); 126 | criteriaExpression.NotEqual("Name", "value"); 127 | 128 | var expression = CriteriaBuilder.Build(criteriaExpression); 129 | 130 | Assert.IsNotNull(expression); 131 | Assert.IsNotNull(expression.Compile()); 132 | Assert.AreEqual("((x.Name == null) OrElse (x.Name != \"value\"))", expression.Body.ToString()); 133 | } 134 | 135 | [TestMethod] 136 | public void BuildWhenNotEqualAndGuidTest() 137 | { 138 | var guid = Guid.NewGuid(); 139 | var criteriaExpression = new CriteriaExpression(); 140 | criteriaExpression.NotEqual("Id", guid); 141 | criteriaExpression.NotEqual("IdNullable", guid); 142 | 143 | var expression = CriteriaBuilder.Build(criteriaExpression); 144 | 145 | Assert.IsNotNull(expression); 146 | Assert.IsNotNull(expression.Compile()); 147 | Assert.AreEqual($"((x.Id != {guid}) AndAlso ((x.IdNullable == null) OrElse (x.IdNullable != {guid})))", expression.Body.ToString()); 148 | } 149 | 150 | [TestMethod] 151 | public void BuildWhenNotEqualAndGuidNullableTest() 152 | { 153 | Guid? guid = Guid.NewGuid(); 154 | var criteriaExpression = new CriteriaExpression(); 155 | criteriaExpression.NotEqual("Id", guid); 156 | criteriaExpression.NotEqual("IdNullable", guid); 157 | 158 | var expression = CriteriaBuilder.Build(criteriaExpression); 159 | 160 | Assert.IsNotNull(expression); 161 | Assert.IsNotNull(expression.Compile()); 162 | Assert.AreEqual($"((x.Id != {guid}) AndAlso ((x.IdNullable == null) OrElse (x.IdNullable != {guid})))", expression.Body.ToString()); 163 | } 164 | 165 | [TestMethod] 166 | public void BuildWhenStartsWithTest() 167 | { 168 | var criteriaExpression = new CriteriaExpression(); 169 | criteriaExpression.StartsWith("Name", "value"); 170 | 171 | var expression = CriteriaBuilder.Build(criteriaExpression); 172 | 173 | Assert.IsNotNull(expression); 174 | Assert.IsNotNull(expression.Compile()); 175 | Assert.AreEqual("((x.Name != null) AndAlso x.Name.StartsWith(\"value\"))", expression.Body.ToString()); 176 | } 177 | 178 | [TestMethod] 179 | public void BuildWhenEndsWithTest() 180 | { 181 | var criteriaExpression = new CriteriaExpression(); 182 | criteriaExpression.EndsWith("Name", "value"); 183 | 184 | var expression = CriteriaBuilder.Build(criteriaExpression); 185 | 186 | Assert.IsNotNull(expression); 187 | Assert.IsNotNull(expression.Compile()); 188 | Assert.AreEqual("((x.Name != null) AndAlso x.Name.EndsWith(\"value\"))", expression.Body.ToString()); 189 | } 190 | 191 | [TestMethod] 192 | public void BuildWhenGreaterThanTest() 193 | { 194 | var criteriaExpression = new CriteriaExpression(); 195 | criteriaExpression.GreaterThan("Age", 1); 196 | 197 | var expression = CriteriaBuilder.Build(criteriaExpression); 198 | 199 | Assert.IsNotNull(expression); 200 | Assert.IsNotNull(expression.Compile()); 201 | Assert.AreEqual("(x.Age > 1)", expression.Body.ToString()); 202 | } 203 | 204 | [TestMethod] 205 | public void BuildWhenGreaterThanOrEqualTest() 206 | { 207 | var criteriaExpression = new CriteriaExpression(); 208 | criteriaExpression.GreaterThanOrEqual("Age", 1); 209 | 210 | var expression = CriteriaBuilder.Build(criteriaExpression); 211 | 212 | Assert.IsNotNull(expression); 213 | Assert.IsNotNull(expression.Compile()); 214 | Assert.AreEqual("(x.Age >= 1)", expression.Body.ToString()); 215 | } 216 | 217 | [TestMethod] 218 | public void BuildWhenLessThanTest() 219 | { 220 | var criteriaExpression = new CriteriaExpression(); 221 | criteriaExpression.LessThan("Age", 1); 222 | 223 | var expression = CriteriaBuilder.Build(criteriaExpression); 224 | 225 | Assert.IsNotNull(expression); 226 | Assert.IsNotNull(expression.Compile()); 227 | Assert.AreEqual("(x.Age < 1)", expression.Body.ToString()); 228 | } 229 | 230 | [TestMethod] 231 | public void BuildWhenLessThanOrEqualTest() 232 | { 233 | var criteriaExpression = new CriteriaExpression(); 234 | criteriaExpression.LessThanOrEqual("Age", 1); 235 | 236 | var expression = CriteriaBuilder.Build(criteriaExpression); 237 | 238 | Assert.IsNotNull(expression); 239 | Assert.IsNotNull(expression.Compile()); 240 | Assert.AreEqual("(x.Age <= 1)", expression.Body.ToString()); 241 | } 242 | 243 | [TestMethod] 244 | public void BuildWhenBetweenTest() 245 | { 246 | var criteriaExpression = new CriteriaExpression(); 247 | criteriaExpression.Between("Age", 1, 5); 248 | 249 | var expression = CriteriaBuilder.Build(criteriaExpression); 250 | 251 | Assert.IsNotNull(expression); 252 | Assert.IsNotNull(expression.Compile()); 253 | Assert.AreEqual("((x.Age >= 1) AndAlso (x.Age <= 5))", expression.Body.ToString()); 254 | } 255 | 256 | [TestMethod] 257 | public void BuildWhenIsNullTest() 258 | { 259 | var criteriaExpression = new CriteriaExpression(); 260 | criteriaExpression.IsNull("Name"); 261 | 262 | var expression = CriteriaBuilder.Build(criteriaExpression); 263 | 264 | Assert.IsNotNull(expression); 265 | Assert.IsNotNull(expression.Compile()); 266 | Assert.AreEqual("(x.Name == null)", expression.Body.ToString()); 267 | } 268 | 269 | [TestMethod] 270 | public void BuildWhenIsNullWhenGuidNullableTest() 271 | { 272 | var criteriaExpression = new CriteriaExpression(); 273 | criteriaExpression.IsNull("IdNullable"); 274 | 275 | var expression = CriteriaBuilder.Build(criteriaExpression); 276 | 277 | Assert.IsNotNull(expression); 278 | Assert.IsNotNull(expression.Compile()); 279 | Assert.AreEqual("(x.IdNullable == null)", expression.Body.ToString()); 280 | } 281 | 282 | [TestMethod] 283 | public void BuildWhenIsNotNullTest() 284 | { 285 | var criteriaExpression = new CriteriaExpression(); 286 | criteriaExpression.IsNotNull("Name"); 287 | 288 | var expression = CriteriaBuilder.Build(criteriaExpression); 289 | 290 | Assert.IsNotNull(expression); 291 | Assert.IsNotNull(expression.Compile()); 292 | Assert.AreEqual("(x.Name != null)", expression.Body.ToString()); 293 | } 294 | 295 | [TestMethod] 296 | public void BuildWhenIsNotNullGuidNullableTest() 297 | { 298 | var criteriaExpression = new CriteriaExpression(); 299 | criteriaExpression.IsNotNull("IdNullable"); 300 | 301 | var expression = CriteriaBuilder.Build(criteriaExpression); 302 | 303 | Assert.IsNotNull(expression); 304 | Assert.IsNotNull(expression.Compile()); 305 | Assert.AreEqual("(x.IdNullable != null)", expression.Body.ToString()); 306 | } 307 | 308 | [TestMethod] 309 | public void BuildWhenIsEmptyTest() 310 | { 311 | var criteriaExpression = new CriteriaExpression(); 312 | criteriaExpression.IsEmpty("Name"); 313 | 314 | var expression = CriteriaBuilder.Build(criteriaExpression); 315 | 316 | Assert.IsNotNull(expression); 317 | Assert.IsNotNull(expression.Compile()); 318 | Assert.AreEqual("(x.Name == \"\")", expression.Body.ToString()); 319 | } 320 | 321 | [TestMethod] 322 | public void BuildWhenIsNotEmptyTest() 323 | { 324 | var criteriaExpression = new CriteriaExpression(); 325 | criteriaExpression.IsNotEmpty("Name"); 326 | 327 | var expression = CriteriaBuilder.Build(criteriaExpression); 328 | 329 | Assert.IsNotNull(expression); 330 | Assert.IsNotNull(expression.Compile()); 331 | Assert.AreEqual("(x.Name != \"\")", expression.Body.ToString()); 332 | } 333 | 334 | [TestMethod] 335 | public void BuildWhenIsNullOrWhiteSpaceTest() 336 | { 337 | var criteriaExpression = new CriteriaExpression(); 338 | criteriaExpression.IsNullOrWhiteSpace("Name"); 339 | 340 | var expression = CriteriaBuilder.Build(criteriaExpression); 341 | 342 | Assert.IsNotNull(expression); 343 | Assert.IsNotNull(expression.Compile()); 344 | Assert.AreEqual("((x.Name == null) OrElse (x.Name.Trim() == \"\"))", expression.Body.ToString()); 345 | } 346 | 347 | [TestMethod] 348 | public void BuildWhenIsNotNullOrWhiteSpaceTest() 349 | { 350 | var criteriaExpression = new CriteriaExpression(); 351 | criteriaExpression.IsNotNullOrWhiteSpace("Name"); 352 | 353 | var expression = CriteriaBuilder.Build(criteriaExpression); 354 | 355 | Assert.IsNotNull(expression); 356 | Assert.IsNotNull(expression.Compile()); 357 | Assert.AreEqual("((x.Name != null) AndAlso (x.Name.Trim() != \"\"))", expression.Body.ToString()); 358 | } 359 | 360 | [TestMethod] 361 | public void BuildWhenInTest() 362 | { 363 | var criteriaExpression = new CriteriaExpression(); 364 | criteriaExpression.In("Name", "value"); 365 | 366 | var expression = CriteriaBuilder.Build(criteriaExpression); 367 | 368 | Assert.IsNotNull(expression); 369 | Assert.IsNotNull(expression.Compile()); 370 | Assert.AreEqual("x.Name.Contains(\"value\")", expression.Body.ToString()); 371 | } 372 | 373 | [TestMethod] 374 | public void BuildWhenInAndEnumTest() 375 | { 376 | var criteriaExpression = new CriteriaExpression(); 377 | criteriaExpression.In("Flags", FlagsEnum.One | FlagsEnum.Two); 378 | 379 | var expression = CriteriaBuilder.Build(criteriaExpression); 380 | 381 | Assert.IsNotNull(expression); 382 | Assert.IsNotNull(expression.Compile()); 383 | Assert.AreEqual("((Convert(x.Flags, Int32) | Convert(One, Two, Int32)) == Convert(One, Two, Int32))", expression.Body.ToString()); 384 | } 385 | 386 | [TestMethod] 387 | public void BuildWhenInAndArrayEnumTest() 388 | { 389 | var criteriaExpression = new CriteriaExpression(); 390 | criteriaExpression.In("Flags", new[] { FlagsEnum.One, FlagsEnum.Two }); 391 | 392 | var expression = CriteriaBuilder.Build(criteriaExpression); 393 | 394 | Assert.IsNotNull(expression); 395 | Assert.IsNotNull(expression.Compile()); 396 | Assert.AreEqual("value(DynamicExpression.Test.CriteriaBuilderTest+FlagsEnum[]).Contains(x.Flags)", expression.Body.ToString()); 397 | } 398 | 399 | [TestMethod] 400 | public void BuildWhenInAndArrayTest() 401 | { 402 | var criteriaExpression = new CriteriaExpression(); 403 | criteriaExpression.In("Name", new[] { "value", "value2" }); 404 | 405 | var expression = CriteriaBuilder.Build(criteriaExpression); 406 | 407 | Assert.IsNotNull(expression); 408 | Assert.IsNotNull(expression.Compile()); 409 | Assert.AreEqual("value(System.String[]).Contains(x.Name)", expression.Body.ToString()); 410 | } 411 | 412 | [TestMethod] 413 | public void BuildWhenNotInTest() 414 | { 415 | var criteriaExpression = new CriteriaExpression(); 416 | criteriaExpression.NotIn("Name", "value"); 417 | 418 | var expression = CriteriaBuilder.Build(criteriaExpression); 419 | 420 | Assert.IsNotNull(expression); 421 | Assert.IsNotNull(expression.Compile()); 422 | Assert.AreEqual("Not(x.Name.Contains(\"value\"))", expression.Body.ToString()); 423 | } 424 | 425 | [TestMethod] 426 | public void BuildWhenNotInAndEnumTest() 427 | { 428 | var criteriaExpression = new CriteriaExpression(); 429 | criteriaExpression.NotIn("Flags", FlagsEnum.One | FlagsEnum.Two); 430 | 431 | var expression = CriteriaBuilder.Build(criteriaExpression); 432 | 433 | Assert.IsNotNull(expression); 434 | Assert.IsNotNull(expression.Compile()); 435 | Assert.AreEqual("Not(((Convert(x.Flags, Int32) | Convert(One, Two, Int32)) == Convert(One, Two, Int32)))", expression.Body.ToString()); 436 | } 437 | 438 | [TestMethod] 439 | public void BuildWhenNotInAndArrayTest() 440 | { 441 | var criteriaExpression = new CriteriaExpression(); 442 | criteriaExpression.NotIn("Name", new[] { "value", "value2" }); 443 | 444 | var expression = CriteriaBuilder.Build(criteriaExpression); 445 | 446 | Assert.IsNotNull(expression); 447 | Assert.IsNotNull(expression.Compile()); 448 | Assert.AreEqual("Not(value(System.String[]).Contains(x.Name))", expression.Body.ToString()); 449 | } 450 | 451 | [TestMethod] 452 | public void BuildWhenContainsTest() 453 | { 454 | var criteriaExpression = new CriteriaExpression(); 455 | criteriaExpression.Contains("Name", "value"); 456 | 457 | var expression = CriteriaBuilder.Build(criteriaExpression); 458 | 459 | Assert.IsNotNull(expression); 460 | Assert.IsNotNull(expression.Compile()); 461 | Assert.AreEqual("x.Name.Contains(\"value\")", expression.Body.ToString()); 462 | } 463 | 464 | [TestMethod] 465 | public void BuildWhenContainsAndEnumTest() 466 | { 467 | var criteriaExpression = new CriteriaExpression(); 468 | criteriaExpression.Contains("Flags", FlagsEnum.One | FlagsEnum.Two); 469 | 470 | var expression = CriteriaBuilder.Build(criteriaExpression); 471 | 472 | Assert.IsNotNull(expression); 473 | Assert.IsNotNull(expression.Compile()); 474 | Assert.AreEqual("((Convert(x.Flags, Int32) | Convert(One, Two, Int32)) == Convert(One, Two, Int32))", expression.Body.ToString()); 475 | } 476 | 477 | [TestMethod] 478 | public void BuildWhenContainsAndIsArrayTest() 479 | { 480 | var criteriaExpression = new CriteriaExpression(); 481 | criteriaExpression.Contains("Name", new[] { "value", "value2" }); 482 | 483 | var expression = CriteriaBuilder.Build(criteriaExpression); 484 | 485 | Assert.IsNotNull(expression); 486 | Assert.IsNotNull(expression.Compile()); 487 | Assert.AreEqual("value(System.String[]).Contains(x.Name)", expression.Body.ToString()); 488 | } 489 | 490 | [TestMethod] 491 | public void BuildWhenNotContainsTest() 492 | { 493 | var criteriaExpression = new CriteriaExpression(); 494 | criteriaExpression.NotContains("Name", "value"); 495 | 496 | var expression = CriteriaBuilder.Build(criteriaExpression); 497 | 498 | Assert.IsNotNull(expression); 499 | Assert.IsNotNull(expression.Compile()); 500 | Assert.AreEqual("Not(x.Name.Contains(\"value\"))", expression.Body.ToString()); 501 | } 502 | 503 | [TestMethod] 504 | public void BuildWhenNotContainsAndEnumTest() 505 | { 506 | var criteriaExpression = new CriteriaExpression(); 507 | criteriaExpression.NotContains("Flags", FlagsEnum.One | FlagsEnum.Two); 508 | 509 | var expression = CriteriaBuilder.Build(criteriaExpression); 510 | 511 | Assert.IsNotNull(expression); 512 | Assert.IsNotNull(expression.Compile()); 513 | Assert.AreEqual("Not(((Convert(x.Flags, Int32) | Convert(One, Two, Int32)) == Convert(One, Two, Int32)))", expression.Body.ToString()); 514 | } 515 | 516 | [TestMethod] 517 | public void BuildWhenCoversTest() 518 | { 519 | var criteriaExpression = new CriteriaExpression(); 520 | 521 | criteriaExpression.Covers(nameof(Customer.Location), new Point(0, 0)); 522 | 523 | var expression = CriteriaBuilder.Build(criteriaExpression); 524 | 525 | Assert.IsNotNull(expression); 526 | Assert.IsNotNull(expression.Compile()); 527 | Assert.AreEqual("((x.Location != null) AndAlso x.Location.Covers(POINT (0 0)))", expression.Body.ToString()); 528 | } 529 | 530 | [TestMethod] 531 | public void BuildWhenCrossesTest() 532 | { 533 | var criteriaExpression = new CriteriaExpression(); 534 | 535 | criteriaExpression.Crosses(nameof(Customer.Location), new Point(0, 0)); 536 | 537 | var expression = CriteriaBuilder.Build(criteriaExpression); 538 | 539 | Assert.IsNotNull(expression); 540 | Assert.IsNotNull(expression.Compile()); 541 | Assert.AreEqual("((x.Location != null) AndAlso x.Location.Crosses(POINT (0 0)))", expression.Body.ToString()); 542 | } 543 | 544 | [TestMethod] 545 | public void BuildWhenTouchesTest() 546 | { 547 | var criteriaExpression = new CriteriaExpression(); 548 | 549 | criteriaExpression.Touches(nameof(Customer.Location), new Point(0, 0)); 550 | 551 | var expression = CriteriaBuilder.Build(criteriaExpression); 552 | 553 | Assert.IsNotNull(expression); 554 | Assert.IsNotNull(expression.Compile()); 555 | Assert.AreEqual("((x.Location != null) AndAlso x.Location.Touches(POINT (0 0)))", expression.Body.ToString()); 556 | } 557 | 558 | [TestMethod] 559 | public void BuildWhenOverlapsTest() 560 | { 561 | var criteriaExpression = new CriteriaExpression(); 562 | 563 | criteriaExpression.Overlaps(nameof(Customer.Location), new Point(0, 0)); 564 | 565 | var expression = CriteriaBuilder.Build(criteriaExpression); 566 | 567 | Assert.IsNotNull(expression); 568 | Assert.IsNotNull(expression.Compile()); 569 | Assert.AreEqual("((x.Location != null) AndAlso x.Location.Overlaps(POINT (0 0)))", expression.Body.ToString()); 570 | } 571 | 572 | [TestMethod] 573 | public void BuildWhenCoveredByTest() 574 | { 575 | var criteriaExpression = new CriteriaExpression(); 576 | 577 | criteriaExpression.CoveredBy(nameof(Customer.Location), new Point(0, 0)); 578 | 579 | var expression = CriteriaBuilder.Build(criteriaExpression); 580 | 581 | Assert.IsNotNull(expression); 582 | Assert.IsNotNull(expression.Compile()); 583 | Assert.AreEqual("((x.Location != null) AndAlso x.Location.CoveredBy(POINT (0 0)))", expression.Body.ToString()); 584 | } 585 | 586 | [TestMethod] 587 | public void BuildWhenDisjointTest() 588 | { 589 | var criteriaExpression = new CriteriaExpression(); 590 | 591 | criteriaExpression.Disjoint(nameof(Customer.Location), new Point(0, 0)); 592 | 593 | var expression = CriteriaBuilder.Build(criteriaExpression); 594 | 595 | Assert.IsNotNull(expression); 596 | Assert.IsNotNull(expression.Compile()); 597 | Assert.AreEqual("((x.Location != null) AndAlso x.Location.Disjoint(POINT (0 0)))", expression.Body.ToString()); 598 | } 599 | 600 | [TestMethod] 601 | public void BuildWhenIntersectsTest() 602 | { 603 | var criteriaExpression = new CriteriaExpression(); 604 | 605 | criteriaExpression.Intersects(nameof(Customer.Location), new Point(0, 0)); 606 | 607 | var expression = CriteriaBuilder.Build(criteriaExpression); 608 | 609 | Assert.IsNotNull(expression); 610 | Assert.IsNotNull(expression.Compile()); 611 | Assert.AreEqual("((x.Location != null) AndAlso x.Location.Intersects(POINT (0 0)))", expression.Body.ToString()); 612 | } 613 | 614 | [TestMethod] 615 | public void BuildWhenWithinTest() 616 | { 617 | var criteriaExpression = new CriteriaExpression(); 618 | 619 | criteriaExpression.Within(nameof(Customer.Location), new Point(0, 0)); 620 | 621 | var expression = CriteriaBuilder.Build(criteriaExpression); 622 | 623 | Assert.IsNotNull(expression); 624 | Assert.IsNotNull(expression.Compile()); 625 | Assert.AreEqual("((x.Location != null) AndAlso x.Location.Within(POINT (0 0)))", expression.Body.ToString()); 626 | } 627 | 628 | [TestMethod] 629 | public void BuildWhenIsWithinDistanceTest() 630 | { 631 | var criteriaExpression = new CriteriaExpression(); 632 | 633 | criteriaExpression.IsWithinDistance(nameof(Customer.Location), new Point(0, 0), 100); 634 | 635 | var expression = CriteriaBuilder.Build(criteriaExpression); 636 | 637 | Assert.IsNotNull(expression); 638 | Assert.IsNotNull(expression.Compile()); 639 | Assert.AreEqual("((x.Location != null) AndAlso x.Location.IsWithinDistance(POINT (0 0), 100))", expression.Body.ToString()); 640 | } 641 | 642 | [TestMethod] 643 | public void BuildWhenReferenceTest() 644 | { 645 | var criteriaExpression = new CriteriaExpression(); 646 | criteriaExpression.Equal("Payment.Id", "value"); 647 | 648 | var expression = CriteriaBuilder.Build(criteriaExpression); 649 | 650 | Assert.IsNotNull(expression); 651 | Assert.IsNotNull(expression.Compile()); 652 | Assert.AreEqual("((x.Payment.Id != null) AndAlso (x.Payment.Id == \"value\"))", expression.Body.ToString()); 653 | } 654 | 655 | [TestMethod] 656 | public void BuildWhenReferenceCollectionTest() 657 | { 658 | var criteriaExpression = new CriteriaExpression(); 659 | criteriaExpression.Equal("Orders[Payment.Id]", "value"); 660 | 661 | var expression = CriteriaBuilder.Build(criteriaExpression); 662 | 663 | Assert.IsNotNull(expression); 664 | Assert.IsNotNull(expression.Compile()); 665 | Assert.AreEqual("x.Orders.Any(i => ((i.Payment.Id != null) AndAlso (i.Payment.Id == \"value\")))", expression.Body.ToString()); 666 | } 667 | 668 | [TestMethod] 669 | public void BuildWhenRefernceAndReferenceCollectionTest() 670 | { 671 | var criteriaExpression = new CriteriaExpression(); 672 | criteriaExpression.Equal("Customer.Orders[Payment.Id]", "value"); 673 | 674 | var expression = CriteriaBuilder.Build(criteriaExpression); 675 | 676 | Assert.IsNotNull(expression); 677 | Assert.IsNotNull(expression.Compile()); 678 | Assert.AreEqual("x.Customer.Orders.Any(i => ((i.Payment.Id != null) AndAlso (i.Payment.Id == \"value\")))", expression.Body.ToString()); 679 | } 680 | 681 | [TestMethod] 682 | public void BuildWhenMultipleCriteriaExpressionsTest() 683 | { 684 | var criteriaExpression1 = new CriteriaExpression(); 685 | criteriaExpression1.Equal("Name", "value"); 686 | 687 | var criteriaExpression2 = new CriteriaExpression(); 688 | criteriaExpression2.Equal("Name", "value2", LogicalType.Or); 689 | criteriaExpression2.Equal("Name", "value3"); 690 | 691 | var expression = CriteriaBuilder.Build([criteriaExpression1, criteriaExpression2]); 692 | 693 | Assert.IsNotNull(expression); 694 | Assert.IsNotNull(expression.Compile()); 695 | Assert.AreEqual("(((x.Name != null) AndAlso (x.Name == \"value\")) AndAlso (((x.Name != null) AndAlso (x.Name == \"value2\")) OrElse ((x.Name != null) AndAlso (x.Name == \"value3\"))))", expression.Body.ToString()); 696 | } 697 | 698 | [TestMethod] 699 | public void BuildWhenPropertyIsNullableAndValueIsNullableTest() 700 | { 701 | Guid? guid = Guid.NewGuid(); 702 | var criteriaExpression = new CriteriaExpression(); 703 | criteriaExpression.Equal("IdNullable", guid); 704 | 705 | var expression = CriteriaBuilder.Build(criteriaExpression); 706 | 707 | Assert.IsNotNull(expression); 708 | Assert.IsNotNull(expression.Compile()); 709 | Assert.AreEqual($"((x.IdNullable != null) AndAlso (x.IdNullable == {guid}))", expression.Body.ToString()); 710 | } 711 | 712 | [TestMethod] 713 | public void BuildWhenPropertyIsNullableAndValueIsNotNullableTest() 714 | { 715 | var guid = Guid.NewGuid(); 716 | var criteriaExpression = new CriteriaExpression(); 717 | criteriaExpression.Equal("IdNullable", guid); 718 | 719 | var expression = CriteriaBuilder.Build(criteriaExpression); 720 | 721 | Assert.IsNotNull(expression); 722 | Assert.IsNotNull(expression.Compile()); 723 | Assert.AreEqual($"((x.IdNullable != null) AndAlso (x.IdNullable == {guid}))", expression.Body.ToString()); 724 | } 725 | 726 | [TestMethod] 727 | public void BuildWhenPropertyIsNotNullableAndValueIsNullableTest() 728 | { 729 | Guid? guid = Guid.NewGuid(); 730 | var criteriaExpression = new CriteriaExpression(); 731 | criteriaExpression.Equal("Id", guid); 732 | 733 | var expression = CriteriaBuilder.Build(criteriaExpression); 734 | 735 | Console.WriteLine(expression.Body.ToString()); 736 | 737 | Assert.IsNotNull(expression); 738 | Assert.IsNotNull(expression.Compile()); 739 | Assert.AreEqual($"(x.Id == {guid})", expression.Body.ToString()); 740 | } 741 | 742 | [TestMethod] 743 | public void BuildWhenPropertyIsNotNullableAndValueIsNotNullableTest() 744 | { 745 | var guid = Guid.NewGuid(); 746 | var criteriaExpression = new CriteriaExpression(); 747 | criteriaExpression.Equal("Id", guid); 748 | 749 | var expression = CriteriaBuilder.Build(criteriaExpression); 750 | 751 | Assert.IsNotNull(expression); 752 | Assert.IsNotNull(expression.Compile()); 753 | Assert.AreEqual($"(x.Id == {guid})", expression.Body.ToString()); 754 | } 755 | 756 | [Flags] 757 | public enum FlagsEnum 758 | { 759 | One = 1 << 0, 760 | Two = 1 << 1 761 | } 762 | public class User 763 | { 764 | public virtual Customer Customer { get; set; } 765 | } 766 | public class Customer 767 | { 768 | public virtual Guid Id { get; set; } 769 | public virtual Guid? IdNullable { get; set; } 770 | public virtual string Name { get; set; } 771 | public virtual int Age { get; set; } 772 | public virtual FlagsEnum Flags { get; set; } = FlagsEnum.One | FlagsEnum.Two; 773 | public virtual IEnumerable Orders { get; set; } 774 | public virtual TimeOnly Time { get; set; } 775 | public virtual DateOnly Date { get; set; } 776 | public virtual DateTime DateTime { get; set; } 777 | public virtual DateTimeOffset DateTimeOffset { get; set; } 778 | public virtual Point Location { get; set; } 779 | } 780 | public class Payment 781 | { 782 | public virtual string Id { get; set; } 783 | } 784 | public class Order 785 | { 786 | public virtual Payment Payment { get; set; } 787 | } 788 | } -------------------------------------------------------------------------------- /.tests/DynamicExpression.Test/CriteriaExpressionTest.cs: -------------------------------------------------------------------------------- 1 | using System.Linq; 2 | using DynamicExpression.Enums; 3 | using Microsoft.VisualStudio.TestTools.UnitTesting; 4 | 5 | namespace DynamicExpression.Test; 6 | 7 | [TestClass] 8 | public class CriteriaExpressionTest 9 | { 10 | [TestMethod] 11 | public void ConstructorWhenEqualTest() 12 | { 13 | var expression = new CriteriaExpression(); 14 | expression.Equal("name", "value"); 15 | 16 | var criteria = expression.Criterias.FirstOrDefault(); 17 | Assert.IsNotNull(criteria); 18 | Assert.AreEqual("name", criteria.Property); 19 | Assert.AreEqual("value", criteria.Value); 20 | Assert.AreEqual(null, criteria.Value2); 21 | Assert.AreEqual(LogicalType.And, criteria.LogicalType); 22 | Assert.AreEqual(OperationType.Equal, criteria.OperationType); 23 | } 24 | 25 | [TestMethod] 26 | public void ConstructorWhenStartsWithTest() 27 | { 28 | var expression = new CriteriaExpression(); 29 | expression.StartsWith("name", "value"); 30 | 31 | var criteria = expression.Criterias.FirstOrDefault(); 32 | Assert.IsNotNull(criteria); 33 | Assert.AreEqual("name", criteria.Property); 34 | Assert.AreEqual("value", criteria.Value); 35 | Assert.AreEqual(null, criteria.Value2); 36 | Assert.AreEqual(LogicalType.And, criteria.LogicalType); 37 | Assert.AreEqual(OperationType.StartsWith, criteria.OperationType); 38 | } 39 | 40 | [TestMethod] 41 | public void ConstructorWhenEndWithTest() 42 | { 43 | var expression = new CriteriaExpression(); 44 | expression.EndsWith("name", "value"); 45 | 46 | var criteria = expression.Criterias.FirstOrDefault(); 47 | Assert.IsNotNull(criteria); 48 | Assert.AreEqual("name", criteria.Property); 49 | Assert.AreEqual("value", criteria.Value); 50 | Assert.AreEqual(null, criteria.Value2); 51 | Assert.AreEqual(LogicalType.And, criteria.LogicalType); 52 | Assert.AreEqual(OperationType.EndsWith, criteria.OperationType); 53 | } 54 | 55 | [TestMethod] 56 | public void ConstructorWhenGreaterThanTest() 57 | { 58 | var expression = new CriteriaExpression(); 59 | expression.GreaterThan("Age", 1); 60 | 61 | var criteria = expression.Criterias.FirstOrDefault(); 62 | Assert.IsNotNull(criteria); 63 | Assert.AreEqual("Age", criteria.Property); 64 | Assert.AreEqual(1, criteria.Value); 65 | Assert.AreEqual(null, criteria.Value2); 66 | Assert.AreEqual(LogicalType.And, criteria.LogicalType); 67 | Assert.AreEqual(OperationType.GreaterThan, criteria.OperationType); 68 | } 69 | 70 | [TestMethod] 71 | public void ConstructorWhenGreaterThanOrEqualTest() 72 | { 73 | var expression = new CriteriaExpression(); 74 | expression.GreaterThanOrEqual("Age", 1); 75 | 76 | var criteria = expression.Criterias.FirstOrDefault(); 77 | Assert.IsNotNull(criteria); 78 | Assert.AreEqual("Age", criteria.Property); 79 | Assert.AreEqual(1, criteria.Value); 80 | Assert.AreEqual(null, criteria.Value2); 81 | Assert.AreEqual(LogicalType.And, criteria.LogicalType); 82 | Assert.AreEqual(OperationType.GreaterThanOrEqual, criteria.OperationType); 83 | } 84 | 85 | [TestMethod] 86 | public void ConstructorWhenLessThanTest() 87 | { 88 | var expression = new CriteriaExpression(); 89 | expression.LessThan("Age", 1); 90 | 91 | var criteria = expression.Criterias.FirstOrDefault(); 92 | Assert.IsNotNull(criteria); 93 | Assert.AreEqual("Age", criteria.Property); 94 | Assert.AreEqual(1, criteria.Value); 95 | Assert.AreEqual(null, criteria.Value2); 96 | Assert.AreEqual(LogicalType.And, criteria.LogicalType); 97 | Assert.AreEqual(OperationType.LessThan, criteria.OperationType); 98 | } 99 | 100 | [TestMethod] 101 | public void ConstructorWhenLessThanOrEqualTest() 102 | { 103 | var expression = new CriteriaExpression(); 104 | expression.LessThanOrEqual("Age", 1); 105 | 106 | var criteria = expression.Criterias.FirstOrDefault(); 107 | Assert.IsNotNull(criteria); 108 | Assert.AreEqual("Age", criteria.Property); 109 | Assert.AreEqual(1, criteria.Value); 110 | Assert.AreEqual(null, criteria.Value2); 111 | Assert.AreEqual(LogicalType.And, criteria.LogicalType); 112 | Assert.AreEqual(OperationType.LessThanOrEqual, criteria.OperationType); 113 | } 114 | 115 | [TestMethod] 116 | public void ConstructorWhenBetweenTest() 117 | { 118 | var expression = new CriteriaExpression(); 119 | expression.Between("Age", 1, 5); 120 | 121 | var criteria = expression.Criterias.FirstOrDefault(); 122 | Assert.IsNotNull(criteria); 123 | Assert.AreEqual("Age", criteria.Property); 124 | Assert.AreEqual(1, criteria.Value); 125 | Assert.AreEqual(5, criteria.Value2); 126 | Assert.AreEqual(LogicalType.And, criteria.LogicalType); 127 | Assert.AreEqual(OperationType.Between, criteria.OperationType); 128 | } 129 | 130 | [TestMethod] 131 | public void ConstructorWhenIsNullTest() 132 | { 133 | var expression = new CriteriaExpression(); 134 | expression.IsNull("Name"); 135 | 136 | var criteria = expression.Criterias.FirstOrDefault(); 137 | Assert.IsNotNull(criteria); 138 | Assert.AreEqual("Name", criteria.Property); 139 | Assert.AreEqual(null, criteria.Value); 140 | Assert.AreEqual(null, criteria.Value2); 141 | Assert.AreEqual(LogicalType.And, criteria.LogicalType); 142 | Assert.AreEqual(OperationType.IsNull, criteria.OperationType); 143 | } 144 | 145 | [TestMethod] 146 | public void ConstructorWhenIsNotNullTest() 147 | { 148 | var expression = new CriteriaExpression(); 149 | expression.IsNotNull("Name"); 150 | 151 | var criteria = expression.Criterias.FirstOrDefault(); 152 | Assert.IsNotNull(criteria); 153 | Assert.AreEqual("Name", criteria.Property); 154 | Assert.AreEqual(null, criteria.Value); 155 | Assert.AreEqual(null, criteria.Value2); 156 | Assert.AreEqual(LogicalType.And, criteria.LogicalType); 157 | Assert.AreEqual(OperationType.IsNotNull, criteria.OperationType); 158 | } 159 | 160 | [TestMethod] 161 | public void ConstructorWhenIsEmptyTest() 162 | { 163 | var expression = new CriteriaExpression(); 164 | expression.IsEmpty("Name"); 165 | 166 | var criteria = expression.Criterias.FirstOrDefault(); 167 | Assert.IsNotNull(criteria); 168 | Assert.AreEqual("Name", criteria.Property); 169 | Assert.AreEqual(null, criteria.Value); 170 | Assert.AreEqual(null, criteria.Value2); 171 | Assert.AreEqual(LogicalType.And, criteria.LogicalType); 172 | Assert.AreEqual(OperationType.IsEmpty, criteria.OperationType); 173 | } 174 | 175 | [TestMethod] 176 | public void ConstructorWhenIsNotEmptyTest() 177 | { 178 | var expression = new CriteriaExpression(); 179 | expression.IsNotEmpty("Name"); 180 | 181 | var criteria = expression.Criterias.FirstOrDefault(); 182 | Assert.IsNotNull(criteria); 183 | Assert.AreEqual("Name", criteria.Property); 184 | Assert.AreEqual(null, criteria.Value); 185 | Assert.AreEqual(null, criteria.Value2); 186 | Assert.AreEqual(LogicalType.And, criteria.LogicalType); 187 | Assert.AreEqual(OperationType.IsNotEmpty, criteria.OperationType); 188 | } 189 | 190 | [TestMethod] 191 | public void ConstructorWhenIsNullOrWhiteSpaceTest() 192 | { 193 | var expression = new CriteriaExpression(); 194 | expression.IsNullOrWhiteSpace("Name"); 195 | 196 | var criteria = expression.Criterias.FirstOrDefault(); 197 | Assert.IsNotNull(criteria); 198 | Assert.AreEqual("Name", criteria.Property); 199 | Assert.AreEqual(null, criteria.Value); 200 | Assert.AreEqual(null, criteria.Value2); 201 | Assert.AreEqual(LogicalType.And, criteria.LogicalType); 202 | Assert.AreEqual(OperationType.IsNullOrWhiteSpace, criteria.OperationType); 203 | } 204 | 205 | [TestMethod] 206 | public void ConstructorWhenIsNotNullOrWhiteSpaceTest() 207 | { 208 | var expression = new CriteriaExpression(); 209 | expression.IsNotNullOrWhiteSpace("Name"); 210 | 211 | var criteria = expression.Criterias.FirstOrDefault(); 212 | Assert.IsNotNull(criteria); 213 | Assert.AreEqual("Name", criteria.Property); 214 | Assert.AreEqual(null, criteria.Value); 215 | Assert.AreEqual(null, criteria.Value2); 216 | Assert.AreEqual(LogicalType.And, criteria.LogicalType); 217 | Assert.AreEqual(OperationType.IsNotNullOrWhiteSpace, criteria.OperationType); 218 | } 219 | 220 | [TestMethod] 221 | public void ConstructorWhenInTest() 222 | { 223 | var expression = new CriteriaExpression(); 224 | var array = new[] { "value", "value2" }; 225 | expression.In("Name", array); 226 | 227 | var criteria = expression.Criterias.FirstOrDefault(); 228 | Assert.IsNotNull(criteria); 229 | Assert.AreEqual("Name", criteria.Property); 230 | Assert.AreEqual(array, criteria.Value); 231 | Assert.AreEqual(null, criteria.Value2); 232 | Assert.AreEqual(LogicalType.And, criteria.LogicalType); 233 | Assert.AreEqual(OperationType.In, criteria.OperationType); 234 | } 235 | 236 | [TestMethod] 237 | public void ConstructorWhenNotInTest() 238 | { 239 | var expression = new CriteriaExpression(); 240 | var array = new[] { "value", "value2" }; 241 | expression.NotIn("Name", array); 242 | 243 | var criteria = expression.Criterias.FirstOrDefault(); 244 | Assert.IsNotNull(criteria); 245 | Assert.AreEqual("Name", criteria.Property); 246 | Assert.AreEqual(array, criteria.Value); 247 | Assert.AreEqual(null, criteria.Value2); 248 | Assert.AreEqual(LogicalType.And, criteria.LogicalType); 249 | Assert.AreEqual(OperationType.NotIn, criteria.OperationType); 250 | } 251 | 252 | [TestMethod] 253 | public void ConstructorWhenContainsTest() 254 | { 255 | var expression = new CriteriaExpression(); 256 | expression.Contains("Name", "value"); 257 | 258 | var criteria = expression.Criterias.FirstOrDefault(); 259 | Assert.IsNotNull(criteria); 260 | Assert.AreEqual("Name", criteria.Property); 261 | Assert.AreEqual("value", criteria.Value); 262 | Assert.AreEqual(null, criteria.Value2); 263 | Assert.AreEqual(LogicalType.And, criteria.LogicalType); 264 | Assert.AreEqual(OperationType.Contains, criteria.OperationType); 265 | } 266 | 267 | [TestMethod] 268 | public void ConstructorWhenNotContainsTest() 269 | { 270 | var expression = new CriteriaExpression(); 271 | expression.NotContains("Name", "value"); 272 | 273 | var criteria = expression.Criterias.FirstOrDefault(); 274 | Assert.IsNotNull(criteria); 275 | Assert.AreEqual("Name", criteria.Property); 276 | Assert.AreEqual("value", criteria.Value); 277 | Assert.AreEqual(null, criteria.Value2); 278 | Assert.AreEqual(LogicalType.And, criteria.LogicalType); 279 | Assert.AreEqual(OperationType.NotContains, criteria.OperationType); 280 | } 281 | } -------------------------------------------------------------------------------- /.tests/DynamicExpression.Test/DynamicExpression.Test.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net9.0 5 | true 6 | 7 | 8 | latest 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /.tests/DynamicExpression.Test/Extensions/QueryableExtensionsTest.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using DynamicExpression.Entities; 5 | using DynamicExpression.Enums; 6 | using DynamicExpression.Extensions; 7 | using Microsoft.VisualStudio.TestTools.UnitTesting; 8 | 9 | namespace DynamicExpression.Test.Extensions; 10 | 11 | [TestClass] 12 | public class QueryableExtensionsTest 13 | { 14 | public class OrderType 15 | { 16 | public virtual T Order { get; set; } 17 | } 18 | 19 | public class Nested 20 | { 21 | public virtual string Name { get; set; } 22 | public virtual OrderType OrderType { get; set; } 23 | } 24 | 25 | [TestMethod] 26 | public void OrderWhenGuidTest() 27 | { 28 | var list = new[] 29 | { 30 | new OrderType { Order = Guid.Parse("fdb28f72-e4bd-4ff1-8e20-799daf045ae8") }, 31 | new OrderType { Order = Guid.Parse("55882908-85e0-4a89-808e-78044ba355e9") } 32 | }; 33 | 34 | var ordering = new Ordering 35 | { 36 | By = "Order", 37 | Direction = OrderingDirection.Asc 38 | }; 39 | 40 | var orderedList = list 41 | .AsQueryable() 42 | .Order(ordering); 43 | 44 | Assert.AreEqual(list[0].Order, orderedList.Last().Order); 45 | Assert.AreEqual(list[1].Order, orderedList.First().Order); 46 | } 47 | 48 | [TestMethod] 49 | public void OrderWhenTimeSpanTest() 50 | { 51 | var list = new[] 52 | { 53 | new OrderType { Order = new TimeSpan(2, 2, 2) }, 54 | new OrderType { Order = new TimeSpan(1, 1, 1) } 55 | }; 56 | 57 | var ordering = new Ordering 58 | { 59 | By = "Order", 60 | Direction = OrderingDirection.Asc 61 | }; 62 | 63 | var orderedList = list 64 | .AsQueryable() 65 | .Order(ordering); 66 | 67 | Assert.AreEqual(list[0].Order, orderedList.Last().Order); 68 | Assert.AreEqual(list[1].Order, orderedList.First().Order); 69 | } 70 | 71 | [TestMethod] 72 | public void OrderWhenDateTimeTest() 73 | { 74 | var list = new[] 75 | { 76 | new OrderType { Order = new DateTime(2, 2, 2) }, 77 | new OrderType { Order = new DateTime(1, 1, 1) } 78 | }; 79 | 80 | var ordering = new Ordering 81 | { 82 | By = "Order", 83 | Direction = OrderingDirection.Asc 84 | }; 85 | 86 | var orderedList = list 87 | .AsQueryable() 88 | .Order(ordering); 89 | 90 | Assert.AreEqual(list[0].Order, orderedList.Last().Order); 91 | Assert.AreEqual(list[1].Order, orderedList.First().Order); 92 | } 93 | 94 | [TestMethod] 95 | public void OrderWhenDateTimeOffsetTest() 96 | { 97 | var list = new[] 98 | { 99 | new OrderType { Order = new DateTimeOffset(2, 2, 2, 2, 2, 2, TimeSpan.Zero) }, 100 | new OrderType { Order = new DateTimeOffset(1, 1, 1, 1, 1, 1, TimeSpan.Zero) } 101 | }; 102 | 103 | var ordering = new Ordering 104 | { 105 | By = "Order", 106 | Direction = OrderingDirection.Asc 107 | }; 108 | 109 | var orderedList = list 110 | .AsQueryable() 111 | .Order(ordering); 112 | 113 | Assert.AreEqual(list[0].Order, orderedList.Last().Order); 114 | Assert.AreEqual(list[1].Order, orderedList.First().Order); 115 | } 116 | 117 | [TestMethod] 118 | public void OrderWhenIntegerTest() 119 | { 120 | var list = new[] 121 | { 122 | new OrderType { Order = 2 }, 123 | new OrderType { Order = 1 } 124 | }; 125 | 126 | var ordering = new Ordering 127 | { 128 | By = "Order", 129 | Direction = OrderingDirection.Asc 130 | }; 131 | 132 | var orderedList = list 133 | .AsQueryable() 134 | .Order(ordering); 135 | 136 | Assert.AreEqual(list[0].Order, orderedList.Last().Order); 137 | Assert.AreEqual(list[1].Order, orderedList.First().Order); 138 | } 139 | 140 | [TestMethod] 141 | public void OrderWhenLongTest() 142 | { 143 | var list = new[] 144 | { 145 | new OrderType { Order = 2L }, 146 | new OrderType { Order = 1L } 147 | }; 148 | 149 | var ordering = new Ordering 150 | { 151 | By = "Order", 152 | Direction = OrderingDirection.Asc 153 | }; 154 | 155 | var orderedList = list 156 | .AsQueryable() 157 | .Order(ordering); 158 | 159 | Assert.AreEqual(list[0].Order, orderedList.Last().Order); 160 | Assert.AreEqual(list[1].Order, orderedList.First().Order); 161 | } 162 | 163 | [TestMethod] 164 | public void OrderWhenFloatTest() 165 | { 166 | var list = new[] 167 | { 168 | new OrderType { Order = 1.2F }, 169 | new OrderType { Order = 1.1F } 170 | }; 171 | 172 | var ordering = new Ordering 173 | { 174 | By = "Order", 175 | Direction = OrderingDirection.Asc 176 | }; 177 | 178 | var orderedList = list 179 | .AsQueryable() 180 | .Order(ordering); 181 | 182 | Assert.AreEqual(list[0].Order, orderedList.Last().Order); 183 | Assert.AreEqual(list[1].Order, orderedList.First().Order); 184 | } 185 | 186 | [TestMethod] 187 | public void OrderWhenDoubleTest() 188 | { 189 | var list = new[] 190 | { 191 | new OrderType { Order = 1.2D }, 192 | new OrderType { Order = 1.1D } 193 | }; 194 | 195 | var ordering = new Ordering 196 | { 197 | By = "Order", 198 | Direction = OrderingDirection.Asc 199 | }; 200 | 201 | var orderedList = list 202 | .AsQueryable() 203 | .Order(ordering); 204 | 205 | Assert.AreEqual(list[0].Order, orderedList.Last().Order); 206 | Assert.AreEqual(list[1].Order, orderedList.First().Order); 207 | } 208 | 209 | [TestMethod] 210 | public void OrderWhenDecimalTest() 211 | { 212 | var list = new[] 213 | { 214 | new OrderType { Order = 1.2M }, 215 | new OrderType { Order = 1.1M } 216 | }; 217 | 218 | var ordering = new Ordering 219 | { 220 | By = "Order", 221 | Direction = OrderingDirection.Asc 222 | }; 223 | 224 | var orderedList = list 225 | .AsQueryable() 226 | .Order(ordering); 227 | 228 | Assert.AreEqual(list[0].Order, orderedList.Last().Order); 229 | Assert.AreEqual(list[1].Order, orderedList.First().Order); 230 | } 231 | 232 | [TestMethod] 233 | public void OrderWhenBoolTest() 234 | { 235 | var list = new[] 236 | { 237 | new OrderType { Order = true }, 238 | new OrderType { Order = false } 239 | }; 240 | 241 | var ordering = new Ordering 242 | { 243 | By = "Order", 244 | Direction = OrderingDirection.Asc 245 | }; 246 | 247 | var orderedList = list 248 | .AsQueryable() 249 | .Order(ordering); 250 | 251 | Assert.AreEqual(list[0].Order, orderedList.Last().Order); 252 | Assert.AreEqual(list[1].Order, orderedList.First().Order); 253 | } 254 | 255 | [TestMethod] 256 | public void OrderWhenEnumTest() 257 | { 258 | var list = new[] 259 | { 260 | new OrderType { Order = DayOfWeek.Friday }, 261 | new OrderType { Order = DayOfWeek.Monday } 262 | }; 263 | 264 | var ordering = new Ordering 265 | { 266 | By = "Order", 267 | Direction = OrderingDirection.Asc 268 | }; 269 | 270 | var orderedList = list 271 | .AsQueryable() 272 | .Order(ordering); 273 | 274 | Assert.AreEqual(list[0].Order, orderedList.Last().Order); 275 | Assert.AreEqual(list[1].Order, orderedList.First().Order); 276 | } 277 | 278 | [TestMethod] 279 | public void OrderWhenEnumNullableTest() 280 | { 281 | var list = new[] 282 | { 283 | new OrderType { Order = DayOfWeek.Friday }, 284 | new OrderType { Order = null } 285 | }; 286 | 287 | var ordering = new Ordering 288 | { 289 | By = "Order", 290 | Direction = OrderingDirection.Asc 291 | }; 292 | 293 | var orderedList = list 294 | .AsQueryable() 295 | .Order(ordering); 296 | 297 | Assert.AreEqual(list[0].Order, orderedList.Last().Order); 298 | Assert.AreEqual(list[1].Order, orderedList.First().Order); 299 | } 300 | 301 | [TestMethod] 302 | public void LimitWhenBoolTest() 303 | { 304 | var list = new[] 305 | { 306 | new object(), 307 | new object(), 308 | new object() 309 | }; 310 | 311 | var pagination = new Pagination 312 | { 313 | Count = 2, 314 | Number = 1 315 | }; 316 | 317 | var limittedist = list 318 | .AsQueryable() 319 | .Limit(pagination); 320 | 321 | Assert.AreEqual(2, limittedist.Count()); 322 | } 323 | 324 | [TestMethod] 325 | public void OrderWhenNestedObjectTest() 326 | { 327 | var list = new List> 328 | { 329 | new() 330 | { 331 | OrderType = new OrderType 332 | { 333 | Order = 11 334 | } 335 | }, 336 | new() 337 | { 338 | OrderType = new OrderType 339 | { 340 | Order = 4 341 | } 342 | }, 343 | new() 344 | { 345 | OrderType = new OrderType 346 | { 347 | Order = 8 348 | } 349 | }, 350 | new() 351 | { 352 | OrderType = new OrderType 353 | { 354 | Order = 2 355 | } 356 | } 357 | }; 358 | 359 | var ordering = new Ordering 360 | { 361 | By = "OrderType.Order", 362 | Direction = OrderingDirection.Asc 363 | }; 364 | 365 | var orderedList = list 366 | .AsQueryable() 367 | .Order(ordering) 368 | .ToArray(); 369 | 370 | Assert.AreEqual(list[3].OrderType.Order, orderedList[0].OrderType.Order); 371 | Assert.AreEqual(list[1].OrderType.Order, orderedList[1].OrderType.Order); 372 | Assert.AreEqual(list[2].OrderType.Order, orderedList[2].OrderType.Order); 373 | Assert.AreEqual(list[0].OrderType.Order, orderedList[3].OrderType.Order); 374 | } 375 | 376 | [TestMethod] 377 | public void OrderWhenThenByTest() 378 | { 379 | var list = new List> 380 | { 381 | new() 382 | { 383 | Name = "D", 384 | OrderType = new OrderType 385 | { 386 | Order = 2 387 | } 388 | }, 389 | new() 390 | { 391 | Name = "B", 392 | OrderType = new OrderType 393 | { 394 | Order = 4 395 | } 396 | }, 397 | new() 398 | { 399 | Name = "C", 400 | OrderType = new OrderType 401 | { 402 | Order = 8 403 | } 404 | }, 405 | new() 406 | { 407 | Name = "A", 408 | OrderType = new OrderType 409 | { 410 | Order = 2 411 | } 412 | } 413 | }; 414 | 415 | var ordering = new Ordering 416 | { 417 | By = "OrderType.Order", 418 | ThenBy = "Name", 419 | Direction = OrderingDirection.Asc 420 | }; 421 | 422 | var orderedList = list 423 | .AsQueryable() 424 | .Order(ordering) 425 | .ToArray(); 426 | 427 | Assert.AreEqual(list[0].Name, orderedList[1].Name); 428 | Assert.AreEqual(list[0].OrderType.Order, orderedList[1].OrderType.Order); 429 | Assert.AreEqual(list[1].Name, orderedList[2].Name); 430 | Assert.AreEqual(list[1].OrderType.Order, orderedList[2].OrderType.Order); 431 | Assert.AreEqual(list[2].Name, orderedList[3].Name); 432 | Assert.AreEqual(list[2].OrderType.Order, orderedList[3].OrderType.Order); 433 | Assert.AreEqual(list[3].Name, orderedList[0].Name); 434 | Assert.AreEqual(list[3].OrderType.Order, orderedList[0].OrderType.Order); 435 | } 436 | } -------------------------------------------------------------------------------- /DynamicExpression.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 17 4 | VisualStudioVersion = 17.0.32112.339 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = ".solution", ".solution", "{EA0E8885-9387-4A54-8DE6-65EB2431C1B9}" 7 | ProjectSection(SolutionItems) = preProject 8 | .gitignore = .gitignore 9 | appveyor.yml = appveyor.yml 10 | icon.jpg = icon.jpg 11 | LICENSE = LICENSE 12 | README.md = README.md 13 | EndProjectSection 14 | EndProject 15 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DynamicExpression", "DynamicExpression\DynamicExpression.csproj", "{B283F4A4-1E7B-4D9A-A767-002C11B2C881}" 16 | EndProject 17 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = ".tests", ".tests", "{8BD321DD-4802-4A62-BA89-615F8565298F}" 18 | EndProject 19 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DynamicExpression.Test", ".tests\DynamicExpression.Test\DynamicExpression.Test.csproj", "{E13EA595-4752-4DD2-9AEE-EAEFDEBD86BF}" 20 | EndProject 21 | Global 22 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 23 | Debug|Any CPU = Debug|Any CPU 24 | Release|Any CPU = Release|Any CPU 25 | EndGlobalSection 26 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 27 | {B283F4A4-1E7B-4D9A-A767-002C11B2C881}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 28 | {B283F4A4-1E7B-4D9A-A767-002C11B2C881}.Debug|Any CPU.Build.0 = Debug|Any CPU 29 | {B283F4A4-1E7B-4D9A-A767-002C11B2C881}.Release|Any CPU.ActiveCfg = Release|Any CPU 30 | {B283F4A4-1E7B-4D9A-A767-002C11B2C881}.Release|Any CPU.Build.0 = Release|Any CPU 31 | {E13EA595-4752-4DD2-9AEE-EAEFDEBD86BF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 32 | {E13EA595-4752-4DD2-9AEE-EAEFDEBD86BF}.Debug|Any CPU.Build.0 = Debug|Any CPU 33 | {E13EA595-4752-4DD2-9AEE-EAEFDEBD86BF}.Release|Any CPU.ActiveCfg = Release|Any CPU 34 | EndGlobalSection 35 | GlobalSection(SolutionProperties) = preSolution 36 | HideSolutionNode = FALSE 37 | EndGlobalSection 38 | GlobalSection(NestedProjects) = preSolution 39 | {E13EA595-4752-4DD2-9AEE-EAEFDEBD86BF} = {8BD321DD-4802-4A62-BA89-615F8565298F} 40 | EndGlobalSection 41 | GlobalSection(ExtensibilityGlobals) = postSolution 42 | SolutionGuid = {88C60CFA-317B-4439-ACA0-4CBE16274F92} 43 | EndGlobalSection 44 | EndGlobal 45 | -------------------------------------------------------------------------------- /DynamicExpression.sln.DotSettings: -------------------------------------------------------------------------------- 1 |  2 | True 3 | ExplicitlyExcluded 4 | DO_NOT_SHOW 5 | DO_NOT_SHOW 6 | WARNING 7 | DO_NOT_SHOW 8 | WARNING 9 | WARNING 10 | WARNING 11 | WARNING 12 | WARNING 13 | WARNING 14 | WARNING 15 | WARNING 16 | WARNING 17 | WARNING 18 | WARNING 19 | WARNING 20 | WARNING 21 | WARNING 22 | WARNING 23 | WARNING 24 | WARNING 25 | WARNING 26 | WARNING 27 | WARNING 28 | WARNING 29 | WARNING 30 | WARNING 31 | WARNING 32 | WARNING 33 | DO_NOT_SHOW 34 | WARNING 35 | DO_NOT_SHOW 36 | DO_NOT_SHOW 37 | DO_NOT_SHOW 38 | DO_NOT_SHOW 39 | DO_NOT_SHOW 40 | WARNING 41 | DO_NOT_SHOW 42 | DO_NOT_SHOW 43 | WARNING 44 | DO_NOT_SHOW 45 | DO_NOT_SHOW 46 | WARNING 47 | DO_NOT_SHOW 48 | WARNING 49 | WARNING 50 | WARNING 51 | WARNING 52 | WARNING 53 | WARNING 54 | WARNING 55 | WARNING 56 | WARNING 57 | WARNING 58 | WARNING 59 | WARNING 60 | WARNING 61 | WARNING 62 | WARNING 63 | DO_NOT_SHOW 64 | DO_NOT_SHOW 65 | DO_NOT_SHOW 66 | WARNING 67 | WARNING 68 | WARNING 69 | DO_NOT_SHOW 70 | DO_NOT_SHOW 71 | WARNING 72 | WARNING 73 | WARNING 74 | WARNING 75 | DoShow 76 | DoShow 77 | DoShow 78 | DoShow 79 | DoShow 80 | DoShow 81 | DoShow 82 | DoShow 83 | DoShow 84 | DoShow 85 | DoShow 86 | DoShow 87 | DoShow 88 | DoShow 89 | DoShow 90 | DoShow 91 | DoShow 92 | DoShow 93 | DoShow 94 | DoShow 95 | DoShow 96 | DoShow 97 | DoShow 98 | DoShow 99 | DoShow 100 | DoShow 101 | DoShow 102 | DoShow 103 | DoShow 104 | DoShow 105 | DoShow 106 | DoShow 107 | DoShow 108 | DoShow 109 | DoShow 110 | DoShow 111 | DoShow 112 | DoShow 113 | DoShow 114 | DoShow 115 | DoShow 116 | DoShow 117 | DoShow 118 | DoShow 119 | DoShow 120 | DoShow 121 | DoShow 122 | DoShow 123 | DoShow 124 | DoShow 125 | DoShow 126 | DoShow 127 | DoShow 128 | DoShow 129 | DoShow 130 | DoShow 131 | DoShow 132 | DoShow 133 | DoShow 134 | DoShow 135 | DoShow 136 | DoShow 137 | DoShow 138 | DoShow 139 | DoShow 140 | DoShow 141 | DoShow 142 | DoShow 143 | DoShow 144 | DoShow 145 | DoShow 146 | DoShow 147 | DoShow 148 | DoShow 149 | DoShow 150 | DoShow 151 | DoShow 152 | DoShow 153 | DoShow 154 | DoShow 155 | DoShow 156 | DoShow 157 | DoShow 158 | DoShow 159 | DoShow 160 | DoShow 161 | DoShow 162 | DoShow 163 | DoShow 164 | DoShow 165 | DoShow 166 | DoShow 167 | DoShow 168 | DoShow 169 | DoShow 170 | DoShow 171 | DoShow 172 | DoShow 173 | DoShow 174 | DoShow 175 | DoShow 176 | DoShow 177 | DoShow 178 | DoShow 179 | DoShow 180 | DoShow 181 | DoShow 182 | DoShow 183 | DoShow 184 | DoShow 185 | DoShow 186 | DoShow 187 | DoShow 188 | DoShow 189 | DoShow 190 | DoShow 191 | DoShow 192 | DoShow 193 | DoShow 194 | DoShow 195 | DoShow 196 | DoShow 197 | DoShow 198 | DoShow 199 | DoShow 200 | DoShow 201 | DoShow 202 | DoShow 203 | DoShow 204 | DoShow 205 | DoShow 206 | DoShow 207 | DoShow 208 | DoShow 209 | DoShow 210 | DoShow 211 | DoShow 212 | DoShow 213 | DoShow 214 | DoShow 215 | DoShow 216 | DoShow 217 | DoShow 218 | DoShow 219 | DoShow 220 | DoShow 221 | DoShow 222 | DoShow 223 | DoShow 224 | DoShow 225 | DoShow 226 | DoShow 227 | DoShow 228 | DoShow 229 | DoShow 230 | True 231 | False 232 | True 233 | <Policy Inspect="True" Prefix="" Suffix="" Style="AA_BB" /> 234 | <Policy Inspect="True" Prefix="" Suffix="" Style="AA_BB" /> 235 | <Policy Inspect="True" Prefix="" Suffix="" Style="AA_BB" /> 236 | <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> 237 | <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> 238 | <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> 239 | <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> 240 | <Policy><Descriptor Staticness="Static" AccessRightKinds="Private" Description="Static readonly fields (private)"><ElementKinds><Kind Name="READONLY_FIELD" /></ElementKinds></Descriptor><Policy Inspect="True" WarnAboutPrefixesAndSuffixes="False" Prefix="" Suffix="" Style="aaBb" /></Policy> 241 | <Policy><Descriptor Staticness="Any" AccessRightKinds="Private" Description="Constant fields (private)"><ElementKinds><Kind Name="CONSTANT_FIELD" /></ElementKinds></Descriptor><Policy Inspect="True" WarnAboutPrefixesAndSuffixes="False" Prefix="" Suffix="" Style="AA_BB" /></Policy> 242 | <Policy><Descriptor Staticness="Instance" AccessRightKinds="Private" Description="Instance fields (private)"><ElementKinds><Kind Name="FIELD" /><Kind Name="READONLY_FIELD" /></ElementKinds></Descriptor><Policy Inspect="True" WarnAboutPrefixesAndSuffixes="False" Prefix="" Suffix="" Style="aaBb" /></Policy> 243 | <Policy><Descriptor Staticness="Any" AccessRightKinds="Protected, ProtectedInternal, Internal, Public, PrivateProtected" Description="Constant fields (not private)"><ElementKinds><Kind Name="CONSTANT_FIELD" /></ElementKinds></Descriptor><Policy Inspect="True" WarnAboutPrefixesAndSuffixes="False" Prefix="" Suffix="" Style="AA_BB" /></Policy> 244 | <Policy><Descriptor Staticness="Any" AccessRightKinds="Any" Description="Local constants"><ElementKinds><Kind Name="LOCAL_CONSTANT" /></ElementKinds></Descriptor><Policy Inspect="True" WarnAboutPrefixesAndSuffixes="False" Prefix="" Suffix="" Style="AA_BB" /></Policy> 245 | <Policy><Descriptor Staticness="Static" AccessRightKinds="Protected, ProtectedInternal, Internal, Public, PrivateProtected" Description="Static readonly fields (not private)"><ElementKinds><Kind Name="READONLY_FIELD" /></ElementKinds></Descriptor><Policy Inspect="True" WarnAboutPrefixesAndSuffixes="False" Prefix="" Suffix="" Style="aaBb" /></Policy> 246 | <Policy><Descriptor Staticness="Static" AccessRightKinds="Private" Description="Static fields (private)"><ElementKinds><Kind Name="FIELD" /></ElementKinds></Descriptor><Policy Inspect="True" WarnAboutPrefixesAndSuffixes="False" Prefix="" Suffix="" Style="aaBb" /></Policy> 247 | True 248 | True 249 | False 250 | 1269 251 | False 252 | False 253 | 12 254 | 12 255 | 9 256 | 2 257 | True 258 | 05/07/2024 17:11:17 259 | 05/07/2024 14:10:14 260 | 5 261 | 0 262 | True 263 | 05/07/2024 16:41:55 264 | True 265 | True 266 | True 267 | True 268 | True 269 | True 270 | [0,185](500,473) 271 | -16,-33 272 | True 273 | InspectionSeverity 274 | 1 275 | True 276 | True 277 | 2023.3 278 | True 279 | True 280 | Error -------------------------------------------------------------------------------- /DynamicExpression/CriteriaBuilder.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Linq.Expressions; 5 | using System.Reflection; 6 | using DynamicExpression.Entities; 7 | using DynamicExpression.Enums; 8 | using DynamicExpression.Extensions; 9 | using NetTopologySuite.Geometries; 10 | 11 | namespace DynamicExpression; 12 | 13 | /// 14 | /// Criteria Builder 15 | /// 16 | public static class CriteriaBuilder 17 | { 18 | /// 19 | /// Builds the , and returns an . 20 | /// 21 | /// Type used in the . 22 | /// The . 23 | /// The 24 | public static Expression> Build(CriteriaExpression criteriaExpression) 25 | where T : class 26 | { 27 | if (criteriaExpression == null) 28 | throw new ArgumentNullException(nameof(criteriaExpression)); 29 | 30 | var parameter = Expression.Parameter(typeof(T), "x"); 31 | var expression = CriteriaBuilder.BuildExpression(criteriaExpression, parameter); 32 | 33 | return Expression.Lambda>(expression ?? Expression.Constant(true), parameter); 34 | } 35 | 36 | /// 37 | /// Builds the 's, and returns an . 38 | /// 39 | /// Type used in the . 40 | /// The 's. 41 | /// The 42 | public static Expression> Build(IEnumerable criteriaExpressions) 43 | where T : class 44 | { 45 | if (criteriaExpressions == null) 46 | throw new ArgumentNullException(nameof(criteriaExpressions)); 47 | 48 | var parameter = Expression.Parameter(typeof(T), "x"); 49 | var expressionCombined = criteriaExpressions 50 | .Select(x => CriteriaBuilder.BuildExpression(x, parameter)) 51 | .Aggregate(null, (current, expression) => expression == null 52 | ? current 53 | : current == null 54 | ? expression 55 | : Expression.AndAlso(current, expression)); 56 | 57 | return Expression.Lambda>(expressionCombined ?? Expression.Constant(true), parameter); 58 | } 59 | 60 | private static Expression GetMember(Expression parameter, string propertyName) 61 | { 62 | if (parameter == null) 63 | throw new ArgumentNullException(nameof(parameter)); 64 | 65 | if (propertyName == null) 66 | throw new ArgumentNullException(nameof(propertyName)); 67 | 68 | while (true) 69 | { 70 | if (propertyName.Contains(".")) 71 | { 72 | var index = propertyName.IndexOf(".", StringComparison.Ordinal); 73 | var param = Expression.Property(parameter, propertyName[..index]); 74 | 75 | parameter = param; 76 | propertyName = propertyName[(index + 1)..]; 77 | 78 | continue; 79 | } 80 | 81 | return Expression.Property(parameter, propertyName); 82 | } 83 | } 84 | private static Expression GetExpression(Expression parameter, Criteria criteria, string propertyName = null) 85 | { 86 | if (parameter == null) 87 | throw new ArgumentNullException(nameof(parameter)); 88 | 89 | if (criteria == null) 90 | throw new ArgumentNullException(nameof(criteria)); 91 | 92 | var name = propertyName ?? criteria.Property; 93 | var member = CriteriaBuilder.GetMember(parameter, name); 94 | var value = Expression.Constant(criteria.Value) as Expression; 95 | var value2 = Expression.Constant(criteria.Value2); 96 | var operationType = criteria.OperationType; 97 | 98 | if (Nullable.GetUnderlyingType(member.Type) != null) 99 | { 100 | if (Nullable.GetUnderlyingType(value.Type) == null) 101 | { 102 | value = Expression.Constant(criteria.Value, member.Type); 103 | value2 = Expression.Constant(criteria.Value2, member.Type); 104 | } 105 | } 106 | else if (Nullable.GetUnderlyingType(value.Type) != null) 107 | { 108 | if (Nullable.GetUnderlyingType(member.Type) == null) 109 | { 110 | value = Expression.Constant(criteria.Value, value.Type); 111 | value2 = Expression.Constant(criteria.Value2, value.Type); 112 | } 113 | } 114 | 115 | if (value.Type.IsEnum) 116 | { 117 | var expression = Expression.Convert(member, Enum.GetUnderlyingType(value.Type)); 118 | value = Expression.Convert(value, Enum.GetUnderlyingType(value.Type)); 119 | 120 | switch (operationType) 121 | { 122 | case OperationType.In: 123 | case OperationType.Contains: 124 | return Expression.Equal(Expression.Or(expression, value), value); 125 | 126 | case OperationType.NotIn: 127 | case OperationType.NotContains: 128 | return Expression.Not(Expression.Equal(Expression.Or(expression, value), value)); 129 | 130 | case OperationType.Equal: 131 | return Expression.Equal(expression, value); 132 | 133 | case OperationType.NotEqual: 134 | return Expression.NotEqual(expression, value); 135 | } 136 | } 137 | else if (value.Type.IsSubclassOf(typeof(Geometry))) 138 | { 139 | switch (operationType) 140 | { 141 | case OperationType.Covers: 142 | var methodCovers = typeof(Geometry).GetRuntimeMethod("Covers", [typeof(Geometry)]); 143 | 144 | if (methodCovers == null) 145 | { 146 | throw new NullReferenceException(nameof(methodCovers)); 147 | } 148 | 149 | return Expression.AndAlso(Expression.NotEqual(member, Expression.Constant(null)), Expression.Call(member, methodCovers, value)); 150 | 151 | case OperationType.Crosses: 152 | var methodCrosses = typeof(Geometry).GetRuntimeMethod("Crosses", [typeof(Geometry)]); 153 | 154 | if (methodCrosses == null) 155 | { 156 | throw new NullReferenceException(nameof(methodCrosses)); 157 | } 158 | 159 | return Expression.AndAlso(Expression.NotEqual(member, Expression.Constant(null)), Expression.Call(member, methodCrosses, value)); 160 | 161 | case OperationType.Touches: 162 | var methodTouches = typeof(Geometry).GetRuntimeMethod("Touches", [typeof(Geometry)]); 163 | 164 | if (methodTouches == null) 165 | { 166 | throw new NullReferenceException(nameof(methodTouches)); 167 | } 168 | 169 | return Expression.AndAlso(Expression.NotEqual(member, Expression.Constant(null)), Expression.Call(member, methodTouches, value)); 170 | 171 | case OperationType.Overlaps: 172 | var methodOverlaps = typeof(Geometry).GetRuntimeMethod("Overlaps", [typeof(Geometry)]); 173 | 174 | if (methodOverlaps == null) 175 | { 176 | throw new NullReferenceException(nameof(methodOverlaps)); 177 | } 178 | 179 | return Expression.AndAlso(Expression.NotEqual(member, Expression.Constant(null)), Expression.Call(member, methodOverlaps, value)); 180 | 181 | case OperationType.CoveredBy: 182 | var methodCoveredBy = typeof(Geometry).GetRuntimeMethod("CoveredBy", [typeof(Geometry)]); 183 | 184 | if (methodCoveredBy == null) 185 | { 186 | throw new NullReferenceException(nameof(methodCoveredBy)); 187 | } 188 | 189 | return Expression.AndAlso(Expression.NotEqual(member, Expression.Constant(null)), Expression.Call(member, methodCoveredBy, value)); 190 | 191 | case OperationType.Disjoint: 192 | var methodDisjoints = typeof(Geometry).GetRuntimeMethod("Disjoint", [typeof(Geometry)]); 193 | 194 | if (methodDisjoints == null) 195 | { 196 | throw new NullReferenceException(nameof(methodDisjoints)); 197 | } 198 | 199 | return Expression.AndAlso(Expression.NotEqual(member, Expression.Constant(null)), Expression.Call(member, methodDisjoints, value)); 200 | 201 | case OperationType.Intersects: 202 | var methodIntersects = typeof(Geometry).GetRuntimeMethod("Intersects", [typeof(Geometry)]); 203 | 204 | if (methodIntersects == null) 205 | { 206 | throw new NullReferenceException(nameof(methodIntersects)); 207 | } 208 | 209 | return Expression.AndAlso(Expression.NotEqual(member, Expression.Constant(null)), Expression.Call(member, methodIntersects, value)); 210 | 211 | case OperationType.Within: 212 | var methodWithin = typeof(Geometry).GetRuntimeMethod("Within", [typeof(Geometry)]); 213 | 214 | if (methodWithin == null) 215 | { 216 | throw new NullReferenceException(nameof(methodWithin)); 217 | } 218 | 219 | return Expression.AndAlso(Expression.NotEqual(member, Expression.Constant(null)), Expression.Call(member, methodWithin, value)); 220 | 221 | case OperationType.IsWithinDistance: 222 | var methodIsWithinDistance = typeof(Geometry).GetRuntimeMethod("IsWithinDistance", [typeof(Geometry), typeof(double)]); 223 | 224 | if (methodIsWithinDistance == null) 225 | { 226 | throw new NullReferenceException(nameof(methodIsWithinDistance)); 227 | } 228 | 229 | return Expression.AndAlso(Expression.NotEqual(member, Expression.Constant(null)), Expression.Call(member, methodIsWithinDistance, value, value2)); 230 | } 231 | } 232 | else 233 | { 234 | switch (operationType) 235 | { 236 | case OperationType.Equal: 237 | if (Nullable.GetUnderlyingType(member.Type) == null && member.Type != typeof(string)) 238 | return Expression.Equal(member, value); 239 | 240 | return Expression.AndAlso(Expression.NotEqual(member, Expression.Constant(null)), Expression.Equal(member, value)); 241 | 242 | case OperationType.NotEqual: 243 | if (Nullable.GetUnderlyingType(member.Type) == null && member.Type != typeof(string)) 244 | return Expression.NotEqual(member, value); 245 | 246 | return Expression.OrElse(Expression.Equal(member, Expression.Constant(null)), Expression.NotEqual(member, value)); 247 | 248 | case OperationType.StartsWith: 249 | var methodStartsWith = typeof(string).GetRuntimeMethod("StartsWith", [typeof(string)]); 250 | 251 | if (methodStartsWith == null) 252 | { 253 | throw new NullReferenceException(nameof(methodStartsWith)); 254 | } 255 | 256 | return Expression.AndAlso(Expression.NotEqual(member, Expression.Constant(null)), Expression.Call(member, methodStartsWith, value)); 257 | 258 | case OperationType.EndsWith: 259 | var methodEndsWith = typeof(string).GetRuntimeMethod("EndsWith", [typeof(string)]); 260 | 261 | if (methodEndsWith == null) 262 | { 263 | throw new NullReferenceException(nameof(methodEndsWith)); 264 | } 265 | 266 | return Expression.AndAlso(Expression.NotEqual(member, Expression.Constant(null)), Expression.Call(member, methodEndsWith, value)); 267 | 268 | case OperationType.GreaterThan: 269 | return Nullable.GetUnderlyingType(member.Type) == null 270 | ? Expression.GreaterThan(member, value) 271 | : Expression.AndAlso(Expression.NotEqual(member, Expression.Constant(null)), Expression.GreaterThan(member, value)); 272 | 273 | case OperationType.GreaterThanOrEqual: 274 | return Nullable.GetUnderlyingType(member.Type) == null 275 | ? Expression.GreaterThanOrEqual(member, value) 276 | : Expression.AndAlso(Expression.NotEqual(member, Expression.Constant(null)), Expression.GreaterThanOrEqual(member, value)); 277 | 278 | case OperationType.LessThan: 279 | return Nullable.GetUnderlyingType(member.Type) == null 280 | ? Expression.LessThan(member, value) 281 | : Expression.AndAlso(Expression.NotEqual(member, Expression.Constant(null)), Expression.LessThan(member, value)); 282 | 283 | case OperationType.LessThanOrEqual: 284 | return Nullable.GetUnderlyingType(member.Type) == null 285 | ? Expression.LessThanOrEqual(member, value) 286 | : Expression.AndAlso(Expression.NotEqual(member, Expression.Constant(null)), Expression.LessThanOrEqual(member, value)); 287 | 288 | case OperationType.Between: 289 | return Nullable.GetUnderlyingType(member.Type) == null 290 | ? Expression.AndAlso(Expression.GreaterThanOrEqual(member, value), Expression.LessThanOrEqual(member, value2)) 291 | : Expression.AndAlso(Expression.NotEqual(member, Expression.Constant(null)), Expression.AndAlso(Expression.GreaterThanOrEqual(member, value), Expression.LessThanOrEqual(member, value2))); 292 | 293 | case OperationType.IsNull: 294 | return Expression.Equal(member, Expression.Constant(null)); 295 | 296 | case OperationType.IsEmpty: 297 | return Expression.Equal(member, Expression.Constant(string.Empty)); 298 | 299 | case OperationType.IsNotNull: 300 | return Expression.NotEqual(member, Expression.Constant(null)); 301 | 302 | case OperationType.IsNotEmpty: 303 | return Expression.NotEqual(member, Expression.Constant(string.Empty)); 304 | 305 | case OperationType.In: 306 | case OperationType.Contains: 307 | { 308 | MethodInfo methodContains; 309 | if (value.Type.IsArrayOrEnumerable()) 310 | { 311 | var constant = (ConstantExpression)value; 312 | 313 | if (constant.Value == null) 314 | { 315 | throw new NullReferenceException(nameof(constant.Value)); 316 | } 317 | 318 | var elementType = value.Type.GetGenericArguments().FirstOrDefault() ?? constant.Type.GetElementType(); 319 | 320 | if (elementType == null) 321 | { 322 | throw new NullReferenceException(nameof(elementType)); 323 | } 324 | 325 | methodContains = typeof(ICollection<>) 326 | .MakeGenericType(elementType) 327 | .GetRuntimeMethod("Contains", [elementType]); 328 | 329 | if (methodContains == null) 330 | { 331 | throw new NullReferenceException(nameof(methodContains)); 332 | } 333 | 334 | return Expression.Call(constant, methodContains, member); 335 | } 336 | 337 | methodContains = typeof(string).GetRuntimeMethod("Contains", [value.Type]); 338 | 339 | if (methodContains == null) 340 | { 341 | throw new NullReferenceException(nameof(methodContains)); 342 | } 343 | 344 | return Expression.Call(member, methodContains, value); 345 | } 346 | 347 | case OperationType.NotIn: 348 | case OperationType.NotContains: 349 | { 350 | MethodInfo methodNotContains; 351 | if (value.Type.IsArrayOrEnumerable()) 352 | { 353 | var constant = (ConstantExpression)value; 354 | 355 | if (constant.Value == null) 356 | { 357 | throw new NullReferenceException(nameof(constant.Value)); 358 | } 359 | 360 | var elementType = constant.Type.GetGenericArguments().FirstOrDefault() ?? constant.Type.GetElementType(); 361 | 362 | if (elementType == null) 363 | { 364 | throw new NullReferenceException(nameof(elementType)); 365 | } 366 | 367 | methodNotContains = typeof(ICollection<>) 368 | .MakeGenericType(elementType) 369 | .GetRuntimeMethod("Contains", [elementType]); 370 | 371 | if (methodNotContains == null) 372 | { 373 | throw new NullReferenceException(nameof(methodNotContains)); 374 | } 375 | 376 | return Expression.Not(Expression.Call(constant, methodNotContains, member)); 377 | } 378 | 379 | methodNotContains = typeof(string).GetRuntimeMethod("Contains", [value.Type]); 380 | 381 | if (methodNotContains == null) 382 | { 383 | throw new NullReferenceException(nameof(methodNotContains)); 384 | } 385 | 386 | return Expression.Not(Expression.Call(member, methodNotContains, value)); 387 | } 388 | 389 | case OperationType.IsNullOrWhiteSpace: 390 | var methodTrim = typeof(string).GetRuntimeMethod("Trim", Type.EmptyTypes); 391 | 392 | if (methodTrim == null) 393 | { 394 | throw new NullReferenceException(nameof(methodTrim)); 395 | } 396 | 397 | return Expression.OrElse( 398 | Expression.Equal(member, Expression.Constant(null)), 399 | Expression.Equal(Expression.Call(member, methodTrim), Expression.Constant(string.Empty))); 400 | 401 | case OperationType.IsNotNullOrWhiteSpace: 402 | var methodTrim2 = typeof(string).GetRuntimeMethod("Trim", Type.EmptyTypes); 403 | 404 | if (methodTrim2 == null) 405 | { 406 | throw new NullReferenceException(nameof(methodTrim2)); 407 | } 408 | 409 | return Expression.AndAlso( 410 | Expression.NotEqual(member, Expression.Constant(null)), 411 | Expression.NotEqual(Expression.Call(member, methodTrim2), Expression.Constant(string.Empty))); 412 | 413 | default: 414 | throw new ArgumentOutOfRangeException(); 415 | } 416 | } 417 | 418 | throw new NotSupportedException($"'{operationType}' is not supported by '{value.Type}' "); 419 | } 420 | private static Expression BuildExpression(CriteriaExpression criteriaExpression, Expression parameter) 421 | { 422 | var prevLogicalType = LogicalType.And; 423 | 424 | Expression expression = null; 425 | foreach (var criteria in criteriaExpression.Criterias) 426 | { 427 | Expression innerExpression; 428 | if (criteria.Property.Contains("[") && criteria.Property.Contains("]")) 429 | { 430 | var startArray = criteria.Property.IndexOf("[", StringComparison.Ordinal); 431 | var finishArray = criteria.Property.IndexOf("]", StringComparison.Ordinal); 432 | var baseName = criteria.Property[..startArray]; 433 | var lastIndexOfDot = baseName.LastIndexOf(".", StringComparison.Ordinal); 434 | 435 | Expression paramNested; 436 | if (lastIndexOfDot > 0) 437 | { 438 | paramNested = CriteriaBuilder.GetMember(parameter, baseName[..lastIndexOfDot]); 439 | baseName = baseName[(lastIndexOfDot + 1)..]; 440 | } 441 | else 442 | { 443 | paramNested = parameter; 444 | } 445 | 446 | var name = criteria.Property.Substring(startArray + 1, finishArray - startArray - 1); 447 | var property = paramNested.Type.GetRuntimeProperty(baseName); 448 | 449 | if (property == null) 450 | { 451 | throw new NullReferenceException(nameof(property)); 452 | } 453 | 454 | var type = property.PropertyType.GenericTypeArguments[0]; 455 | var methodAny = typeof(Enumerable).GetRuntimeMethods().First(x => x.Name == "Any" && x.GetParameters().Length == 2).MakeGenericMethod(type); 456 | var memberAny = CriteriaBuilder.GetMember(paramNested, baseName); 457 | var parameterAny = Expression.Parameter(type, "i"); 458 | var expressionAny = CriteriaBuilder.GetExpression(parameterAny, criteria, name); 459 | var expr2 = Expression.Lambda(expressionAny, parameterAny); 460 | 461 | innerExpression = Expression.Call(methodAny, memberAny, expr2); 462 | } 463 | else 464 | { 465 | innerExpression = CriteriaBuilder.GetExpression(parameter, criteria); 466 | } 467 | 468 | expression = expression == null 469 | ? innerExpression 470 | : prevLogicalType == LogicalType.And 471 | ? Expression.AndAlso(expression, innerExpression) 472 | : Expression.OrElse(expression, innerExpression); 473 | 474 | prevLogicalType = criteria.LogicalType; 475 | } 476 | 477 | return expression; 478 | } 479 | } -------------------------------------------------------------------------------- /DynamicExpression/CriteriaExpression.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using DynamicExpression.Entities; 4 | using DynamicExpression.Enums; 5 | using NetTopologySuite.Geometries; 6 | 7 | namespace DynamicExpression; 8 | 9 | /// 10 | /// Criteria Expression. 11 | /// 12 | public class CriteriaExpression 13 | { 14 | /// 15 | /// Criterias. 16 | /// 17 | public virtual List Criterias { get; } = []; 18 | 19 | /// 20 | /// Add filter. 21 | /// 22 | /// The type of the property. 23 | /// The property name. 24 | /// The value. 25 | /// The . 26 | public virtual void Equal(string property, TType value, LogicalType logicalType = LogicalType.And) 27 | { 28 | if (property == null) 29 | throw new ArgumentNullException(nameof(property)); 30 | 31 | this.By(property, OperationType.Equal, value, default, logicalType); 32 | } 33 | 34 | /// 35 | /// Add filter. 36 | /// 37 | /// The type of the property. 38 | /// The property name. 39 | /// The value. 40 | /// The . 41 | public virtual void NotEqual(string property, TType value, LogicalType logicalType = LogicalType.And) 42 | { 43 | if (property == null) 44 | throw new ArgumentNullException(nameof(property)); 45 | 46 | this.By(property, OperationType.NotEqual, value, default, logicalType); 47 | } 48 | 49 | /// 50 | /// Add filter. 51 | /// 52 | /// The type of the property. 53 | /// The property name. 54 | /// The value. 55 | /// The . 56 | public virtual void StartsWith(string property, TType value, LogicalType logicalType = LogicalType.And) 57 | { 58 | if (property == null) 59 | throw new ArgumentNullException(nameof(property)); 60 | 61 | this.By(property, OperationType.StartsWith, value, default, logicalType); 62 | } 63 | 64 | /// 65 | /// Add filter. 66 | /// 67 | /// The type of the property. 68 | /// The property name. 69 | /// The value. 70 | /// The . 71 | public virtual void EndsWith(string property, TType value, LogicalType logicalType = LogicalType.And) 72 | { 73 | if (property == null) 74 | throw new ArgumentNullException(nameof(property)); 75 | 76 | this.By(property, OperationType.EndsWith, value, default, logicalType); 77 | } 78 | 79 | /// 80 | /// Add filter. 81 | /// 82 | /// The type of the property. 83 | /// The property name. 84 | /// The value. 85 | /// The . 86 | public virtual void GreaterThan(string property, TType value, LogicalType logicalType = LogicalType.And) 87 | { 88 | if (property == null) 89 | throw new ArgumentNullException(nameof(property)); 90 | 91 | this.By(property, OperationType.GreaterThan, value, default, logicalType); 92 | } 93 | 94 | /// 95 | /// Add filter. 96 | /// 97 | /// The type of the property. 98 | /// The property name. 99 | /// The value. 100 | /// The . 101 | public virtual void GreaterThanOrEqual(string property, TType value, LogicalType logicalType = LogicalType.And) 102 | { 103 | if (property == null) 104 | throw new ArgumentNullException(nameof(property)); 105 | 106 | this.By(property, OperationType.GreaterThanOrEqual, value, default, logicalType); 107 | } 108 | 109 | /// 110 | /// Add filter. 111 | /// 112 | /// The type of the property. 113 | /// The property name. 114 | /// The value. 115 | /// The . 116 | public virtual void LessThan(string property, TType value, LogicalType logicalType = LogicalType.And) 117 | { 118 | if (property == null) 119 | throw new ArgumentNullException(nameof(property)); 120 | 121 | this.By(property, OperationType.LessThan, value, default, logicalType); 122 | } 123 | 124 | /// 125 | /// Add filter. 126 | /// 127 | /// The type of the property. 128 | /// The property name. 129 | /// The value. 130 | /// The . 131 | public virtual void LessThanOrEqual(string property, TType value, LogicalType logicalType = LogicalType.And) 132 | { 133 | if (property == null) 134 | throw new ArgumentNullException(nameof(property)); 135 | 136 | this.By(property, OperationType.LessThanOrEqual, value, default, logicalType); 137 | } 138 | 139 | /// 140 | /// Add filter. 141 | /// 142 | /// The type of the property. 143 | /// The property name. 144 | /// The value. 145 | /// The value2. 146 | /// The . 147 | public virtual void Between(string property, TType value, TType value2, LogicalType logicalType = LogicalType.And) 148 | { 149 | if (property == null) 150 | throw new ArgumentNullException(nameof(property)); 151 | 152 | this.By(property, OperationType.Between, value, value2, logicalType); 153 | } 154 | 155 | /// 156 | /// Add filter. 157 | /// 158 | /// The type of the property. 159 | /// The property name. 160 | /// The . 161 | public virtual void IsNull(string property, LogicalType logicalType = LogicalType.And) 162 | { 163 | if (property == null) 164 | throw new ArgumentNullException(nameof(property)); 165 | 166 | this.By(property, OperationType.IsNull, default, default, logicalType); 167 | } 168 | 169 | /// 170 | /// Add filter. 171 | /// 172 | /// The type of the property. 173 | /// The property name. 174 | /// The . 175 | public virtual void IsNotNull(string property, LogicalType logicalType = LogicalType.And) 176 | { 177 | if (property == null) 178 | throw new ArgumentNullException(nameof(property)); 179 | 180 | this.By(property, OperationType.IsNotNull, default, default, logicalType); 181 | } 182 | 183 | /// 184 | /// Add filter. 185 | /// 186 | /// The type of the property. 187 | /// The property name. 188 | /// The . 189 | public virtual void IsEmpty(string property, LogicalType logicalType = LogicalType.And) 190 | { 191 | if (property == null) 192 | throw new ArgumentNullException(nameof(property)); 193 | 194 | this.By(property, OperationType.IsEmpty, default, default, logicalType); 195 | } 196 | 197 | /// 198 | /// Add filter. 199 | /// 200 | /// The type of the property. 201 | /// The property name. 202 | /// The . 203 | public virtual void IsNotEmpty(string property, LogicalType logicalType = LogicalType.And) 204 | { 205 | if (property == null) 206 | throw new ArgumentNullException(nameof(property)); 207 | 208 | this.By(property, OperationType.IsNotEmpty, default, default, logicalType); 209 | } 210 | 211 | /// 212 | /// Add filter. 213 | /// 214 | /// The type of the property. 215 | /// The property name. 216 | /// The . 217 | public virtual void IsNullOrWhiteSpace(string property, LogicalType logicalType = LogicalType.And) 218 | { 219 | if (property == null) 220 | throw new ArgumentNullException(nameof(property)); 221 | 222 | this.By(property, OperationType.IsNullOrWhiteSpace, default, default, logicalType); 223 | } 224 | 225 | /// 226 | /// Add filter. 227 | /// 228 | /// The type of the property. 229 | /// The property name. 230 | /// The . 231 | public virtual void IsNotNullOrWhiteSpace(string property, LogicalType logicalType = LogicalType.And) 232 | { 233 | if (property == null) 234 | throw new ArgumentNullException(nameof(property)); 235 | 236 | this.By(property, OperationType.IsNotNullOrWhiteSpace, default, default, logicalType); 237 | } 238 | 239 | /// 240 | /// Add filter. 241 | /// 242 | /// The type of the property. 243 | /// The property name. 244 | /// The value. 245 | /// The . 246 | public virtual void In(string property, TType value, LogicalType logicalType = LogicalType.And) 247 | { 248 | if (property == null) 249 | throw new ArgumentNullException(nameof(property)); 250 | 251 | this.By(property, OperationType.In, value, default, logicalType); 252 | } 253 | 254 | /// 255 | /// Add filter. 256 | /// 257 | /// The type of the property. 258 | /// The property name. 259 | /// The value. 260 | /// The . 261 | public virtual void NotIn(string property, TType value, LogicalType logicalType = LogicalType.And) 262 | { 263 | if (property == null) 264 | throw new ArgumentNullException(nameof(property)); 265 | 266 | this.By(property, OperationType.NotIn, value, default, logicalType); 267 | } 268 | 269 | /// 270 | /// Add filter. 271 | /// 272 | /// The type of the property. 273 | /// The property name. 274 | /// The value. 275 | /// The . 276 | public virtual void Contains(string property, TType value, LogicalType logicalType = LogicalType.And) 277 | { 278 | if (property == null) 279 | throw new ArgumentNullException(nameof(property)); 280 | 281 | this.By(property, OperationType.Contains, value, default, logicalType); 282 | } 283 | 284 | /// 285 | /// Add filter. 286 | /// 287 | /// The type of the property. 288 | /// The property name. 289 | /// The value. 290 | /// The . 291 | public virtual void NotContains(string property, TType value, LogicalType logicalType = LogicalType.And) 292 | { 293 | if (property == null) 294 | throw new ArgumentNullException(nameof(property)); 295 | 296 | this.By(property, OperationType.NotContains, value, default, logicalType); 297 | } 298 | 299 | /// 300 | /// Add filter. 301 | /// 302 | /// The type of the property. 303 | /// The property name. 304 | /// The value. 305 | /// The . 306 | public virtual void Covers(string property, TType value, LogicalType logicalType = LogicalType.And) 307 | { 308 | if (property == null) 309 | throw new ArgumentNullException(nameof(property)); 310 | 311 | this.By(property, OperationType.Covers, value, default, logicalType); 312 | } 313 | 314 | /// 315 | /// Add filter. 316 | /// 317 | /// The type of the property. 318 | /// The property name. 319 | /// The value. 320 | /// The . 321 | public virtual void Crosses(string property, TType value, LogicalType logicalType = LogicalType.And) 322 | { 323 | if (property == null) 324 | throw new ArgumentNullException(nameof(property)); 325 | 326 | this.By(property, OperationType.Crosses, value, default, logicalType); 327 | } 328 | 329 | /// 330 | /// Add filter. 331 | /// 332 | /// The type of the property. 333 | /// The property name. 334 | /// The value. 335 | /// The . 336 | public virtual void Touches(string property, TType value, LogicalType logicalType = LogicalType.And) 337 | { 338 | if (property == null) 339 | throw new ArgumentNullException(nameof(property)); 340 | 341 | this.By(property, OperationType.Touches, value, default, logicalType); 342 | } 343 | 344 | /// 345 | /// Add filter. 346 | /// 347 | /// The type of the property. 348 | /// The property name. 349 | /// The value. 350 | /// The . 351 | public virtual void Overlaps(string property, TType value, LogicalType logicalType = LogicalType.And) 352 | { 353 | if (property == null) 354 | throw new ArgumentNullException(nameof(property)); 355 | 356 | this.By(property, OperationType.Overlaps, value, default, logicalType); 357 | } 358 | 359 | /// 360 | /// Add filter. 361 | /// 362 | /// The type of the property. 363 | /// The property name. 364 | /// The value. 365 | /// The . 366 | public virtual void CoveredBy(string property, TType value, LogicalType logicalType = LogicalType.And) 367 | { 368 | if (property == null) 369 | throw new ArgumentNullException(nameof(property)); 370 | 371 | this.By(property, OperationType.CoveredBy, value, default, logicalType); 372 | } 373 | 374 | /// 375 | /// Add filter. 376 | /// 377 | /// The type of the property. 378 | /// The property name. 379 | /// The value. 380 | /// The . 381 | public virtual void Disjoint(string property, TType value, LogicalType logicalType = LogicalType.And) 382 | { 383 | if (property == null) 384 | throw new ArgumentNullException(nameof(property)); 385 | 386 | this.By(property, OperationType.Disjoint, value, default, logicalType); 387 | } 388 | 389 | /// 390 | /// Add filter. 391 | /// 392 | /// The type of the property. 393 | /// The property name. 394 | /// The value. 395 | /// The . 396 | public virtual void Intersects(string property, TType value, LogicalType logicalType = LogicalType.And) 397 | { 398 | if (property == null) 399 | throw new ArgumentNullException(nameof(property)); 400 | 401 | this.By(property, OperationType.Intersects, value, default, logicalType); 402 | } 403 | 404 | /// 405 | /// Add filter. 406 | /// 407 | /// The type of the property. 408 | /// The property name. 409 | /// The value. 410 | /// The . 411 | public virtual void Within(string property, TType value, LogicalType logicalType = LogicalType.And) 412 | where TType : Geometry 413 | { 414 | if (property == null) 415 | throw new ArgumentNullException(nameof(property)); 416 | 417 | this.By(property, OperationType.Within, value, logicalType); 418 | } 419 | 420 | /// 421 | /// Add filter. 422 | /// 423 | /// The type of the property. 424 | /// The property name. 425 | /// The value. 426 | /// The distance. 427 | /// The . 428 | public virtual void IsWithinDistance(string property, TType value, double distance, LogicalType logicalType = LogicalType.And) 429 | where TType : Geometry 430 | { 431 | if (property == null) 432 | throw new ArgumentNullException(nameof(property)); 433 | 434 | this.By(property, OperationType.IsWithinDistance, value, distance, logicalType); 435 | } 436 | 437 | private void By(string property, OperationType operationType, TType value, object value2 = default, LogicalType logicalType = LogicalType.And) 438 | { 439 | if (property == null) 440 | throw new ArgumentNullException(nameof(property)); 441 | 442 | var criteria = new Criteria(property, operationType, value, value2, logicalType); 443 | 444 | this.Criterias 445 | .Add(criteria); 446 | } 447 | } -------------------------------------------------------------------------------- /DynamicExpression/DynamicExpression.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | net9.0 4 | true 5 | AnyCPU 6 | 9.0.1.0 7 | 9.0.1.0 8 | 9.0.1.0 9 | latest 10 | Michael Vivet 11 | Dynamic Expression 12 | LICENSE 13 | Turn criteria models into Linq expressions. 14 | Easily construct lambda expressions dynamically. Turn models into database queries, using a simple fluent implementation. 15 | Lampda, Expression, Linq, Dynamic, Criteria, Query, Paging, Pagination, Sort, Sorting 16 | README.md 17 | 18 | - Added Geometry types from nettopologysuite for geo-spatial criteria. 19 | 20 | https://raw.githubusercontent.com/vivet/DynamicExpression/master/LICENSE 21 | LICENSE 22 | true 23 | icon.jpg 24 | 25 | https://github.com/vivet/DynamicExpression 26 | Github 27 | https://github.com/vivet/DynamicExpression.git 28 | Michael Vivet 29 | 30 | 31 | true 32 | $(MSBuildThisFileDirectory)\bin\$(Configuration)\DynamicExpression.xml 33 | true 34 | true 35 | true 36 | snupkg 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | -------------------------------------------------------------------------------- /DynamicExpression/Entities/Criteria.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using DynamicExpression.Enums; 5 | using DynamicExpression.Extensions; 6 | using NetTopologySuite.Geometries; 7 | 8 | namespace DynamicExpression.Entities; 9 | 10 | /// 11 | /// Criteria. 12 | /// 13 | public class Criteria 14 | { 15 | /// 16 | /// Value. 17 | /// 18 | public object Value { get; set; } 19 | 20 | /// 21 | /// Value2. 22 | /// 23 | public object Value2 { get; set; } 24 | 25 | /// 26 | /// Property. 27 | /// 28 | public string Property { get; set; } 29 | 30 | /// 31 | /// Logical Type. 32 | /// 33 | public LogicalType LogicalType { get; set; } 34 | 35 | /// 36 | /// Operation Type. 37 | /// 38 | public OperationType OperationType { get; set; } 39 | } 40 | 41 | /// 42 | public class Criteria : Criteria 43 | { 44 | /// 45 | /// Constructor. 46 | /// 47 | /// The property name. 48 | /// The . 49 | /// The value. 50 | /// the value2 51 | /// The . 52 | public Criteria(string property, OperationType operationType, TType value, object value2, LogicalType logicalType = LogicalType.And) 53 | { 54 | if (property == null) 55 | throw new ArgumentNullException(nameof(property)); 56 | 57 | var type = typeof(TType); 58 | var isSupported = this.GetSupportedOperationTypes(type).Contains(operationType); 59 | 60 | if (!isSupported) 61 | throw new InvalidOperationException(operationType.ToString()); 62 | 63 | this.Property = property; 64 | this.Value = value; 65 | this.Value2 = value2; 66 | this.LogicalType = logicalType; 67 | this.OperationType = operationType; 68 | } 69 | 70 | private IEnumerable GetSupportedOperationTypes(Type type) 71 | { 72 | if (type == null) 73 | throw new ArgumentNullException(nameof(type)); 74 | 75 | var operationTypes = new Dictionary> 76 | { 77 | { "Text", [typeof(string), typeof(char)] }, 78 | { "Number", [typeof(int), typeof(uint), typeof(byte), typeof(sbyte), typeof(short), typeof(ushort), typeof(long), typeof(ulong), typeof(float), typeof(double), typeof(decimal)] }, 79 | { "Boolean", [typeof(bool)] }, 80 | { "Date", [typeof(TimeOnly), typeof(DateOnly), typeof(DateTime), typeof(DateTimeOffset)] }, 81 | { "Nullable", [typeof(Nullable<>)] }, 82 | { "Guid", [typeof(Guid)] }, 83 | { "Spatial", [typeof(Geometry), typeof(Point), typeof(LineString), typeof(LinearRing), typeof(Polygon), typeof(MultiPoint), typeof(MultiLineString), typeof(MultiPolygon), typeof(GeometryCollection)] } 84 | }; 85 | 86 | if (type.IsArrayOrEnumerable()) 87 | { 88 | return 89 | [ 90 | OperationType.In, 91 | OperationType.NotIn, 92 | OperationType.Contains, 93 | OperationType.NotContains 94 | ]; 95 | } 96 | 97 | var operationType = type.IsEnum 98 | ? "Enum" 99 | : operationTypes.FirstOrDefault(x => x.Value.Any(y => y.Name == type.Name)).Key; 100 | 101 | switch (operationType) 102 | { 103 | case "Text": 104 | return 105 | [ 106 | OperationType.Equal, 107 | OperationType.NotEqual, 108 | OperationType.StartsWith, 109 | OperationType.EndsWith, 110 | OperationType.IsEmpty, 111 | OperationType.IsNotEmpty, 112 | OperationType.IsNull, 113 | OperationType.IsNotNull, 114 | OperationType.IsNullOrWhiteSpace, 115 | OperationType.IsNotNullOrWhiteSpace, 116 | OperationType.IsEmpty, 117 | OperationType.IsNotEmpty, 118 | OperationType.In, 119 | OperationType.NotIn, 120 | OperationType.Contains, 121 | OperationType.NotContains 122 | ]; 123 | 124 | case "Date": 125 | case "Number": 126 | return 127 | [ 128 | OperationType.Equal, 129 | OperationType.NotEqual, 130 | OperationType.GreaterThan, 131 | OperationType.GreaterThanOrEqual, 132 | OperationType.LessThan, 133 | OperationType.LessThanOrEqual, 134 | OperationType.Between 135 | ]; 136 | 137 | case "Boolean": 138 | case "Guid": 139 | return 140 | [ 141 | OperationType.Equal, 142 | OperationType.NotEqual 143 | ]; 144 | 145 | case "Enum": 146 | return 147 | [ 148 | OperationType.Equal, 149 | OperationType.NotEqual, 150 | OperationType.In, 151 | OperationType.NotIn, 152 | OperationType.Contains, 153 | OperationType.NotContains 154 | ]; 155 | 156 | case "Nullable": 157 | return new[] 158 | { 159 | OperationType.IsNull, 160 | OperationType.IsNotNull 161 | } 162 | .Union(this.GetSupportedOperationTypes(Nullable.GetUnderlyingType(type))) 163 | .Distinct(); 164 | 165 | case "Spatial": 166 | return 167 | [ 168 | OperationType.Covers, 169 | OperationType.Crosses, 170 | OperationType.Touches, 171 | OperationType.Overlaps, 172 | OperationType.CoveredBy, 173 | OperationType.Disjoint, 174 | OperationType.Intersects, 175 | OperationType.Within, 176 | OperationType.IsWithinDistance 177 | ]; 178 | 179 | default: 180 | throw new ArgumentOutOfRangeException(nameof(type)); 181 | } 182 | } 183 | } -------------------------------------------------------------------------------- /DynamicExpression/Entities/Ordering.cs: -------------------------------------------------------------------------------- 1 | using System.ComponentModel; 2 | using DynamicExpression.Enums; 3 | 4 | namespace DynamicExpression.Entities; 5 | 6 | /// 7 | /// Ordering. 8 | /// 9 | public class Ordering 10 | { 11 | /// 12 | /// By, 13 | /// 14 | [DefaultValue("Id")] 15 | public virtual string By { get; set; } = "Id"; 16 | 17 | /// 18 | /// By, 19 | /// 20 | public virtual string ThenBy { get; set; } 21 | 22 | /// 23 | /// Direction. 24 | /// 25 | [DefaultValue(OrderingDirection.Asc)] 26 | public virtual OrderingDirection Direction { get; set; } = OrderingDirection.Asc; 27 | } -------------------------------------------------------------------------------- /DynamicExpression/Entities/Pagination.cs: -------------------------------------------------------------------------------- 1 | using System.ComponentModel; 2 | using System.ComponentModel.DataAnnotations; 3 | 4 | namespace DynamicExpression.Entities; 5 | 6 | /// 7 | /// Pagination. 8 | /// 9 | public class Pagination 10 | { 11 | /// 12 | /// Max Pagination. 13 | /// 14 | public const int MAX_PAGINATION = 25000; 15 | 16 | /// 17 | /// Number. 18 | /// 19 | [DefaultValue(1)] 20 | [Range(1, int.MaxValue)] 21 | public virtual int Number { get; set; } = 1; 22 | 23 | /// 24 | /// Count. 25 | /// 26 | [DefaultValue(25)] 27 | [Range(1, Pagination.MAX_PAGINATION)] 28 | public virtual int Count { get; set; } = 25; 29 | 30 | /// 31 | /// Skip. 32 | /// Skips a number of items. 33 | /// If set, then it Overrides . 34 | /// 35 | [Range(0, int.MaxValue)] 36 | public virtual int? Skip { get; set; } 37 | } -------------------------------------------------------------------------------- /DynamicExpression/Entities/Query.cs: -------------------------------------------------------------------------------- 1 | using DynamicExpression.Interfaces; 2 | 3 | namespace DynamicExpression.Entities; 4 | 5 | /// 6 | public class Query : IQuery 7 | { 8 | /// 9 | public virtual Ordering Order { get; set; } = new(); 10 | 11 | /// 12 | public virtual Pagination Paging { get; set; } = new(); 13 | } 14 | 15 | /// 16 | public class Query : Query, IQuery 17 | where TCriteria : IQueryCriteria, new() 18 | { 19 | /// 20 | public virtual TCriteria Criteria { get; set; } = new(); 21 | } -------------------------------------------------------------------------------- /DynamicExpression/Enums/LogicalType.cs: -------------------------------------------------------------------------------- 1 | namespace DynamicExpression.Enums; 2 | 3 | /// 4 | /// Logical Type. 5 | /// 6 | public enum LogicalType 7 | { 8 | /// 9 | /// And. 10 | /// 11 | And, 12 | 13 | /// 14 | /// Or. 15 | /// 16 | Or 17 | } -------------------------------------------------------------------------------- /DynamicExpression/Enums/OperationType.cs: -------------------------------------------------------------------------------- 1 | namespace DynamicExpression.Enums; 2 | 3 | /// 4 | /// Operation Type. 5 | /// 6 | public enum OperationType 7 | { 8 | /// 9 | /// Equal. 10 | /// 11 | Equal, 12 | 13 | /// 14 | /// Not Equal. 15 | /// 16 | NotEqual, 17 | 18 | /// 19 | /// Starts With. 20 | /// 21 | StartsWith, 22 | 23 | /// 24 | /// Ends With. 25 | /// 26 | EndsWith, 27 | 28 | /// 29 | /// GreaterThan. 30 | /// 31 | GreaterThan, 32 | 33 | /// 34 | /// GreaterThanOrEqual. 35 | /// 36 | GreaterThanOrEqual, 37 | 38 | /// 39 | /// LessThan. 40 | /// 41 | LessThan, 42 | 43 | /// 44 | /// LessThanOrEqual. 45 | /// 46 | LessThanOrEqual, 47 | 48 | /// 49 | /// Between. 50 | /// 51 | Between, 52 | 53 | /// 54 | /// IsNull. 55 | /// 56 | IsNull, 57 | 58 | /// 59 | /// IsNullOrWhiteSpace. 60 | /// 61 | IsNullOrWhiteSpace, 62 | 63 | /// 64 | /// IsNotNull. 65 | /// 66 | IsNotNull, 67 | 68 | /// 69 | /// IsNotNullOrWhiteSpace. 70 | /// 71 | IsNotNullOrWhiteSpace, 72 | 73 | /// 74 | /// IsEmpty. 75 | /// 76 | IsEmpty, 77 | 78 | /// 79 | /// IsNotEmpty. 80 | /// 81 | IsNotEmpty, 82 | 83 | /// 84 | /// In. 85 | /// 86 | In, 87 | 88 | /// 89 | /// Not In. 90 | /// 91 | NotIn, 92 | 93 | /// 94 | /// Contains. 95 | /// 96 | Contains, 97 | 98 | /// 99 | /// Not Contains. 100 | /// 101 | NotContains, 102 | 103 | /// 104 | /// Covers. 105 | /// 106 | Covers, 107 | 108 | /// 109 | /// Crosses. 110 | /// 111 | Crosses, 112 | 113 | /// 114 | /// Touches. 115 | /// 116 | Touches, 117 | 118 | /// 119 | /// Overlaps. 120 | /// 121 | Overlaps, 122 | 123 | /// 124 | /// Covered By. 125 | /// 126 | CoveredBy, 127 | 128 | /// 129 | /// Disjoint. 130 | /// 131 | Disjoint, 132 | 133 | /// 134 | /// Intersects. 135 | /// 136 | Intersects, 137 | 138 | /// 139 | /// Within. 140 | /// 141 | Within, 142 | 143 | /// 144 | /// Is Within Distance. 145 | /// 146 | IsWithinDistance 147 | } -------------------------------------------------------------------------------- /DynamicExpression/Enums/OrderingDirection.cs: -------------------------------------------------------------------------------- 1 | namespace DynamicExpression.Enums; 2 | 3 | /// 4 | /// Ordering Direction. 5 | /// 6 | public enum OrderingDirection 7 | { 8 | /// 9 | /// Ascending. 10 | /// 11 | Asc, 12 | 13 | /// 14 | /// Descending. 15 | /// 16 | Desc 17 | } -------------------------------------------------------------------------------- /DynamicExpression/Extensions/QueryableExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | using System.Linq.Expressions; 4 | using DynamicExpression.Entities; 5 | using DynamicExpression.Enums; 6 | using DynamicExpression.Interfaces; 7 | 8 | namespace DynamicExpression.Extensions; 9 | 10 | /// 11 | /// Queryable Extensions. 12 | /// 13 | public static class QueryableExtensions 14 | { 15 | /// 16 | /// Adds order and limit clauses to the based on the passed . 17 | /// 18 | /// The type used in the . 19 | /// The . 20 | /// The . 21 | /// The . 22 | public static IQueryable Where(this IQueryable source, IQuery query) 23 | where T : class 24 | { 25 | if (source == null) 26 | throw new ArgumentNullException(nameof(source)); 27 | 28 | if (query == null) 29 | throw new ArgumentNullException(nameof(query)); 30 | 31 | return source 32 | .Order(query.Order) 33 | .Limit(query.Paging); 34 | } 35 | 36 | /// 37 | /// Adds where clause to the based on the passed . 38 | /// 39 | /// The type used in the . 40 | /// The . 41 | /// The . 42 | /// The . 43 | public static IQueryable Where(this IQueryable source, IQueryCriteria queryCriteria) 44 | where T : class 45 | { 46 | if (source == null) 47 | throw new ArgumentNullException(nameof(source)); 48 | 49 | if (queryCriteria == null) 50 | throw new ArgumentNullException(nameof(queryCriteria)); 51 | 52 | var criteria = queryCriteria.GetExpressions(); 53 | var expression = CriteriaBuilder.Build(criteria); 54 | 55 | return source 56 | .Where(expression); 57 | } 58 | 59 | /// 60 | /// Adds where, order and limit clauses to the based on the passed . 61 | /// 62 | /// The type used in the . 63 | /// The . 64 | /// The . 65 | /// The . 66 | public static IQueryable Where(this IQueryable source, IQuery queryCriteria) 67 | where T : class 68 | { 69 | if (source == null) 70 | throw new ArgumentNullException(nameof(source)); 71 | 72 | if (queryCriteria == null) 73 | throw new ArgumentNullException(nameof(queryCriteria)); 74 | 75 | var criteria = queryCriteria.Criteria.GetExpressions(); 76 | var expression = CriteriaBuilder.Build(criteria); 77 | 78 | return source 79 | .Where(expression) 80 | .Order(queryCriteria.Order) 81 | .Limit(queryCriteria.Paging); 82 | } 83 | 84 | /// 85 | /// Adds order by clause to the based on the passed 86 | /// 87 | /// The type used in the . 88 | /// The . 89 | /// The . 90 | /// The . 91 | public static IQueryable Order(this IQueryable source, Ordering ordering) 92 | where T : class 93 | { 94 | if (source == null) 95 | throw new ArgumentNullException(nameof(source)); 96 | 97 | if (ordering == null) 98 | throw new ArgumentNullException(nameof(ordering)); 99 | 100 | var parameter = Expression.Parameter(typeof(T)); 101 | var propertyBy = ordering.By 102 | .Split('.') 103 | .Aggregate(parameter, Expression.Property); 104 | 105 | var expressionBy = Expression.Lambda>(Expression.Convert(propertyBy, typeof(object)), parameter); 106 | 107 | Expression> expressionThenBy = null; 108 | if (ordering.ThenBy != null) 109 | { 110 | var propertyThenBy = ordering.ThenBy 111 | .Split('.') 112 | .Aggregate(parameter, Expression.Property); 113 | 114 | expressionThenBy = Expression.Lambda>(Expression.Convert(propertyThenBy, typeof(object)), parameter); 115 | } 116 | 117 | return ordering.Direction == OrderingDirection.Asc 118 | ? expressionThenBy == null 119 | ? source.OrderBy(expressionBy) 120 | : source.OrderBy(expressionBy).ThenBy(expressionThenBy) 121 | : expressionThenBy == null 122 | ? source.OrderByDescending(expressionBy) 123 | : source.OrderByDescending(expressionBy).ThenByDescending(expressionThenBy); 124 | } 125 | 126 | /// 127 | /// Adds skip and take clauses to the based on the passed . 128 | /// 129 | /// The type used in the . 130 | /// The . 131 | /// The . 132 | /// The . 133 | public static IQueryable Limit(this IQueryable source, Pagination pagination) 134 | where T : class 135 | { 136 | if (source == null) 137 | throw new ArgumentNullException(nameof(source)); 138 | 139 | if (pagination == null) 140 | throw new ArgumentNullException(nameof(pagination)); 141 | 142 | var skip = pagination.Skip; 143 | var count = pagination.Count; 144 | var number = pagination.Number; 145 | 146 | return source 147 | .Skip(skip ?? (number - 1) * count) 148 | .Take(count); 149 | } 150 | } -------------------------------------------------------------------------------- /DynamicExpression/Extensions/ServiceCollectionExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using DynamicExpression.Interfaces; 3 | using DynamicExpression.ModelBinders; 4 | using Microsoft.Extensions.DependencyInjection; 5 | 6 | namespace DynamicExpression.Extensions; 7 | 8 | /// 9 | /// Service Collection Extensions. 10 | /// 11 | public static class ServiceCollectionExtensions 12 | { 13 | /// 14 | /// Adds and to the . 15 | /// 16 | /// The . 17 | /// The . 18 | public static IServiceCollection AddQueryModelBinders(this IServiceCollection services) 19 | { 20 | if (services == null) 21 | throw new ArgumentNullException(nameof(services)); 22 | 23 | services 24 | .AddMvc(x => 25 | { 26 | var queryModelBinderProvider = new QueryModelBinderProvider(); 27 | 28 | x.ModelBinderProviders 29 | .Insert(0, queryModelBinderProvider); 30 | }); 31 | 32 | return services; 33 | } 34 | } -------------------------------------------------------------------------------- /DynamicExpression/Extensions/TypeExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections; 3 | 4 | namespace DynamicExpression.Extensions; 5 | 6 | /// 7 | /// Type Extensions. 8 | /// 9 | public static class TypeExtensions 10 | { 11 | /// 12 | /// Is Array Or Enumerable. 13 | /// 14 | /// The . 15 | /// Whether the . 16 | public static bool IsArrayOrEnumerable(this Type type) 17 | { 18 | if (type == null) 19 | throw new ArgumentNullException(nameof(type)); 20 | 21 | return type.GetInterface(nameof(IEnumerable)) != null && type != typeof(string); 22 | } 23 | } -------------------------------------------------------------------------------- /DynamicExpression/Interfaces/IQuery.cs: -------------------------------------------------------------------------------- 1 | using DynamicExpression.Entities; 2 | 3 | namespace DynamicExpression.Interfaces; 4 | 5 | /// 6 | /// Query interface. 7 | /// 8 | public interface IQuery 9 | { 10 | /// 11 | /// Order. 12 | /// 13 | Ordering Order { get; set; } 14 | 15 | /// 16 | /// Paging. 17 | /// 18 | Pagination Paging { get; set; } 19 | } 20 | 21 | /// 22 | /// Query (generic) interface. 23 | /// 24 | /// The type of . 25 | public interface IQuery : IQuery 26 | where TCriteria : IQueryCriteria 27 | { 28 | /// 29 | /// Criteria. 30 | /// 31 | TCriteria Criteria { get; set; } 32 | } -------------------------------------------------------------------------------- /DynamicExpression/Interfaces/IQueryCriteria.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | namespace DynamicExpression.Interfaces; 4 | 5 | /// 6 | /// Query Criteria interface. 7 | /// 8 | public interface IQueryCriteria 9 | { 10 | /// 11 | /// Gets the collection of 's. 12 | /// 13 | /// The 's. 14 | IList GetExpressions(); 15 | } -------------------------------------------------------------------------------- /DynamicExpression/ModelBinders/Const/Constants.cs: -------------------------------------------------------------------------------- 1 | using Newtonsoft.Json; 2 | using Newtonsoft.Json.Converters; 3 | 4 | namespace DynamicExpression.ModelBinders.Const; 5 | 6 | /// 7 | /// Constants 8 | /// 9 | internal static class Constants 10 | { 11 | /// 12 | /// Json Serializer Settings. 13 | /// 14 | internal static readonly JsonSerializerSettings jsonSerializerSettings = new() 15 | { 16 | NullValueHandling = NullValueHandling.Ignore, 17 | ReferenceLoopHandling = ReferenceLoopHandling.Ignore, 18 | PreserveReferencesHandling = PreserveReferencesHandling.None, 19 | Converters = 20 | { 21 | new StringEnumConverter() 22 | } 23 | }; 24 | } -------------------------------------------------------------------------------- /DynamicExpression/ModelBinders/Extensions/StreamExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading.Tasks; 3 | using System.IO; 4 | using System.Threading; 5 | 6 | namespace DynamicExpression.ModelBinders.Extensions; 7 | 8 | /// 9 | /// Stream Extensions. 10 | /// 11 | public static class StreamExtensions 12 | { 13 | /// 14 | /// Reads all bytes in the and returns the content as string. 15 | /// 16 | /// The . 17 | /// The . 18 | /// The content as string. 19 | public static async Task ReadAllAsync(this Stream stream, CancellationToken cancellationToken = default) 20 | { 21 | if (stream == null) 22 | throw new ArgumentNullException(nameof(stream)); 23 | 24 | using var streamReader = new StreamReader(stream); 25 | 26 | return await streamReader 27 | .ReadToEndAsync(cancellationToken); 28 | } 29 | } -------------------------------------------------------------------------------- /DynamicExpression/ModelBinders/QueryModelBinder.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | using System.Reflection; 4 | using System.Threading.Tasks; 5 | using DynamicExpression.Entities; 6 | using DynamicExpression.Enums; 7 | using DynamicExpression.Interfaces; 8 | using DynamicExpression.ModelBinders.Const; 9 | using DynamicExpression.ModelBinders.Extensions; 10 | using Microsoft.AspNetCore.Http; 11 | using Microsoft.AspNetCore.Mvc.ModelBinding; 12 | using Newtonsoft.Json; 13 | 14 | namespace DynamicExpression.ModelBinders; 15 | 16 | /// 17 | public class QueryModelBinder : IModelBinder 18 | { 19 | /// 20 | public virtual async Task BindModelAsync(ModelBindingContext bindingContext) 21 | { 22 | if (bindingContext == null) 23 | throw new ArgumentNullException(nameof(bindingContext)); 24 | 25 | var request = bindingContext.ActionContext.HttpContext.Request; 26 | 27 | var requestBody = await request.Body 28 | .ReadAllAsync(); 29 | 30 | var query = string.IsNullOrEmpty(requestBody) 31 | ? new Query() 32 | : JsonConvert.DeserializeObject(requestBody, Constants.jsonSerializerSettings); 33 | 34 | var pagingCount = this.GetPaginationCount(request); 35 | var pagingNumber = this.GetPaginationNumber(request); 36 | var pagingSkip = this.GetPaginationSkip(request); 37 | var orderingBy = this.GetOrderingBy(request); 38 | var orderingDirection = this.GetOrderingDirection(request); 39 | 40 | query.Paging.Count = pagingCount ?? query.Paging.Count; 41 | query.Paging.Number = pagingNumber ?? query.Paging.Number; 42 | query.Paging.Skip = pagingSkip ?? query.Paging.Skip; 43 | query.Order.By = orderingBy ?? query.Order.By ?? "Id"; 44 | query.Order.Direction = orderingDirection ?? query.Order.Direction; 45 | 46 | bindingContext.Result = ModelBindingResult.Success(query); 47 | } 48 | 49 | /// 50 | /// Get Ordering By. 51 | /// 52 | /// The 53 | /// The ordering by. 54 | protected string GetOrderingBy(HttpRequest request) 55 | { 56 | if (request == null) 57 | throw new ArgumentNullException(nameof(request)); 58 | 59 | var orderBy = request.Query[$"{nameof(IQuery.Order)}.{nameof(Ordering.By)}"].FirstOrDefault(); 60 | 61 | return orderBy; 62 | } 63 | 64 | /// 65 | /// Get Ordering Direction. 66 | /// 67 | /// The 68 | /// The ordering direction. 69 | protected OrderingDirection? GetOrderingDirection(HttpRequest request) 70 | { 71 | if (request == null) 72 | throw new ArgumentNullException(nameof(request)); 73 | 74 | var success = Enum.TryParse(request.Query[$"{nameof(IQuery.Order)}.{nameof(Ordering.Direction)}"].FirstOrDefault(), true, out var direction); 75 | if (!success) 76 | { 77 | return null; 78 | } 79 | 80 | return direction; 81 | } 82 | 83 | /// 84 | /// Get Pagination Count. 85 | /// 86 | /// The 87 | /// The pagination count 88 | protected int? GetPaginationCount(HttpRequest request) 89 | { 90 | if (request == null) 91 | throw new ArgumentNullException(nameof(request)); 92 | 93 | var success = int.TryParse(request.Query[$"{nameof(IQuery.Paging)}.{nameof(Pagination.Count)}"].FirstOrDefault(), out var count); 94 | if (!success) 95 | { 96 | return null; 97 | } 98 | 99 | return count; 100 | } 101 | 102 | /// 103 | /// Get Pagination Number. 104 | /// 105 | /// The 106 | /// The pagination number. 107 | protected int? GetPaginationNumber(HttpRequest request) 108 | { 109 | if (request == null) 110 | throw new ArgumentNullException(nameof(request)); 111 | 112 | var success = int.TryParse(request.Query[$"{nameof(IQuery.Paging)}.{nameof(Pagination.Number)}"].FirstOrDefault(), out var number); 113 | if (!success) 114 | { 115 | return null; 116 | } 117 | 118 | return number; 119 | } 120 | 121 | /// 122 | /// Get Pagination Skip. 123 | /// 124 | /// The 125 | /// The pagination skip. 126 | protected int? GetPaginationSkip(HttpRequest request) 127 | { 128 | if (request == null) 129 | throw new ArgumentNullException(nameof(request)); 130 | 131 | var success = int.TryParse(request.Query[$"{nameof(IQuery.Paging)}.{nameof(Pagination.Skip)}"].FirstOrDefault(), out var skip); 132 | if (!success) 133 | { 134 | return null; 135 | } 136 | 137 | return skip; 138 | } 139 | } 140 | 141 | /// 142 | public class QueryModelBinder : QueryModelBinder 143 | where TCriteria : class, IQueryCriteria, new() 144 | { 145 | /// 146 | public override async Task BindModelAsync(ModelBindingContext bindingContext) 147 | { 148 | if (bindingContext == null) 149 | throw new ArgumentNullException(nameof(bindingContext)); 150 | 151 | var request = bindingContext.ActionContext.HttpContext.Request; 152 | 153 | var requestBody = await request.Body 154 | .ReadAllAsync(); 155 | 156 | var query = string.IsNullOrEmpty(requestBody) 157 | ? new Query() 158 | : JsonConvert.DeserializeObject>(requestBody, Constants.jsonSerializerSettings); 159 | 160 | query.Paging.Count = this.GetPaginationCount(request) ?? query.Paging.Count; 161 | query.Paging.Number = this.GetPaginationNumber(request) ?? query.Paging.Number; 162 | query.Paging.Skip = this.GetPaginationSkip(request) ?? query.Paging.Skip; 163 | 164 | query.Order.By = this.GetOrderingBy(request) ?? query.Order.By ?? "Id"; 165 | query.Order.Direction = this.GetOrderingDirection(request) ?? query.Order.Direction; 166 | 167 | query.Criteria = this.GetCriteria(request, query); 168 | 169 | bindingContext.Result = ModelBindingResult.Success(query); 170 | } 171 | 172 | /// 173 | /// Returns the Criteria of type , from the . 174 | /// 175 | /// The . 176 | /// The query. 177 | /// The Criteria of type . 178 | protected virtual TCriteria GetCriteria(HttpRequest httpRequest, Query query) 179 | { 180 | if (httpRequest == null) 181 | throw new ArgumentNullException(nameof(httpRequest)); 182 | 183 | var criteria = new TCriteria(); 184 | 185 | typeof(TCriteria) 186 | .GetProperties(BindingFlags.GetProperty | BindingFlags.Public | BindingFlags.Instance) 187 | .ToList() 188 | .ForEach(x => 189 | { 190 | var bodyValue = x.GetValue(query.Criteria); 191 | 192 | if (bodyValue != null) 193 | { 194 | x.SetValue(criteria, bodyValue); 195 | 196 | return; 197 | } 198 | 199 | var success = httpRequest.Query.TryGetValue($"{nameof(Criteria)}.{x.Name}", out var values); 200 | if (!success) 201 | { 202 | return; 203 | } 204 | 205 | var value = values.FirstOrDefault(); 206 | if (value == null) 207 | { 208 | return; 209 | } 210 | 211 | if (x.PropertyType == typeof(TimeSpan) || x.PropertyType == typeof(TimeSpan?)) 212 | { 213 | x.SetValue(criteria, TimeSpan.Parse(value)); 214 | } 215 | else if (x.PropertyType == typeof(TimeOnly) || x.PropertyType == typeof(TimeOnly?)) 216 | { 217 | x.SetValue(criteria, TimeOnly.Parse(value)); 218 | } 219 | else if (x.PropertyType == typeof(DateOnly) || x.PropertyType == typeof(DateOnly?)) 220 | { 221 | x.SetValue(criteria, DateOnly.Parse(value)); 222 | } 223 | else if (x.PropertyType == typeof(DateTime) || x.PropertyType == typeof(DateTime?)) 224 | { 225 | x.SetValue(criteria, DateTime.Parse(value)); 226 | } 227 | else if (x.PropertyType == typeof(DateTimeOffset) || x.PropertyType == typeof(DateTimeOffset?)) 228 | { 229 | x.SetValue(criteria, DateTimeOffset.Parse(value)); 230 | } 231 | else if (x.PropertyType == typeof(Guid) || x.PropertyType == typeof(Guid?)) 232 | { 233 | x.SetValue(criteria, Guid.Parse(value)); 234 | } 235 | else 236 | { 237 | x.SetValue(criteria, value); 238 | } 239 | }); 240 | 241 | return criteria; 242 | } 243 | } -------------------------------------------------------------------------------- /DynamicExpression/ModelBinders/QueryModelBinderProvider.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using DynamicExpression.Entities; 3 | using DynamicExpression.Interfaces; 4 | using Microsoft.AspNetCore.Mvc.ModelBinding; 5 | 6 | namespace DynamicExpression.ModelBinders; 7 | 8 | /// 9 | public class QueryModelBinderProvider : IModelBinderProvider 10 | { 11 | /// 12 | public IModelBinder GetBinder(ModelBinderProviderContext context) 13 | { 14 | if (context == null) 15 | throw new ArgumentNullException(nameof(context)); 16 | 17 | var modelType = context.Metadata.ModelType; 18 | 19 | if (modelType.IsGenericType && (modelType.GetGenericTypeDefinition() == typeof(IQuery<>) || modelType.GetGenericTypeDefinition() == typeof(Query<>))) 20 | { 21 | var types = modelType.GetGenericArguments(); 22 | var genericType = typeof(QueryModelBinder<>).MakeGenericType(types); 23 | 24 | return (IModelBinder)Activator.CreateInstance(genericType); 25 | } 26 | 27 | if (modelType == typeof(IQuery) || modelType == typeof(Query)) 28 | { 29 | var genericType = typeof(QueryModelBinder); 30 | 31 | return (IModelBinder)Activator.CreateInstance(genericType); 32 | } 33 | 34 | return null; 35 | } 36 | } -------------------------------------------------------------------------------- /DynamicExpression/Properties/InternalsVisibleTo.cs: -------------------------------------------------------------------------------- 1 | using System.Runtime.CompilerServices; 2 | 3 | [assembly: InternalsVisibleTo("DynamicExpression.Test")] -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Michael Vivet 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Dynamic Expression 2 | [![Build status](https://ci.appveyor.com/api/projects/status/lv6dki9f4o7ck88b/branch/master?svg=true)](https://ci.appveyor.com/project/vivet/dynamicexpression/branch/master) 3 | [![NuGet](https://img.shields.io/nuget/dt/DynamicExpression.svg)](https://www.nuget.org/packages/DynamicExpression/) 4 | [![NuGet](https://img.shields.io/nuget/v/DynamicExpression.svg)](https://www.nuget.org/packages/DynamicExpression/) 5 | 6 | Easyly, construct lambda expressions dynamically, and turn criteria models into Linq queries. Additionally the library implements a query object model, defining the criteria (optional) and properties for pagination and ordering, to use directly in querying methods. 7 | 8 | Feel free to contribute, throw questions and report issues. I usually respond fast (24-48 hours). 9 | 10 | ##### Table of Contents: 11 | * [Query](#query) 12 | * [Query Criteria](#query-criteria) 13 | * [Criteria Expression Operations](#criteria-expression-operations) 14 | * [Linq Extensions](#linq-extensions) 15 | 16 | #### Query 17 | The query object model has a generic and a non-generic implementations. 18 | The ```Query``` is used when no filtering is required, but pagination and ordering is still needed, while the ```Query``` is used when custom filter expressions should be applied. 19 | 20 | #### Query Criteria 21 | The query criteria derives from the interface ```IQueryCriteria```, and implements a single method ```GetExpressions()```, where ```TModel``` defines the model type of the linq statement the critera expression is converted into. Additionally the query critiera implementation contains properties for each wanted criteria. In the method body of ```GetExpressions()``` build the ```CriteriaExpression```, defining logical operations and mapping model and criteria properties. 22 | 23 | Simple example. 24 | ```csharp 25 | public class MyModel 26 | { 27 | public string MyProperty { get; set; } 28 | } 29 | 30 | public class MyQueryCriteria : IQueryCriteria 31 | { 32 | public string MyCriteria { get; set; } 33 | 34 | public virtual IList GetExpressions() 35 | where TEntity : class 36 | { 37 | var expression = new CriteriaExpression(); 38 | 39 | expression 40 | .Equal("MyProperty", this.MyCriteria); 41 | 42 | return new[] { expression }; 43 | } 44 | } 45 | ``` 46 | 47 | Another example, combining two criteria properties to one model property. 48 | ```csharp 49 | public class MyModel 50 | { 51 | public DateTime CreatedAt { get; set; } 52 | } 53 | 54 | public class MyQueryCriteria : IQueryCriteria 55 | { 56 | public DateTimeOffset? AfterAt { get; set; } 57 | public DateTimeOffset? BeforeAt { get; set; } 58 | 59 | public override IList GetExpressions() 60 | where TEntity : class 61 | { 62 | var expression = base.GetExpression(); 63 | 64 | if (this.BeforeAt.HasValue) 65 | { 66 | expression.LessThanOrEqual("CreatedAt", this.BeforeAt); 67 | } 68 | 69 | if (this.AfterAt.HasValue) 70 | { 71 | expression.GreaterThanOrEqual("CreatedAt", this.AfterAt); 72 | } 73 | 74 | return new[] { expression }; 75 | } 76 | } 77 | ``` 78 | 79 | #### Criteria Expression Operations 80 | When constructing the ```CriteriaExpression``` the following methods are available. 81 | Note, that not all operations are valid on all data types, but it should be apparent. 82 | * Equal 83 | * NotEqual 84 | * Contains 85 | * Not Contains 86 | * StartsWith 87 | * EndsWith 88 | * GreaterThan 89 | * GreaterThanOrEqual 90 | * LessThan 91 | * LessThanOrEqual 92 | * Between 93 | * IsNull 94 | * IsNotNull 95 | * IsNullOrWhiteSpace 96 | * IsNotNullOrWhiteSpace 97 | * IsEmpty 98 | * IsNotEmpty 99 | 100 | #### Linq Extensions 101 | The library comes with a few ```IQueryable``` extension methods. They serve to convert the ```CriteriaExpression``` returned by the ```GetExpression()```, into a valid linq expression. 102 | 103 | The list below shows the extension methods 104 | * ```IQueryable Order(IOrdering)``` 105 | * ```IQueryable Limit(IPagination)``` 106 | * ```IQueryable Where(IQueryCriteria)``` 107 | 108 | Using the ```IQueryCriteria``` implementation from above, applying the criteria expression would look like this. 109 | ```csharp 110 | var criteria = new MyQueryCriteria(); 111 | 112 | var result = myQueryable 113 | .Where(criteria) 114 | ``` 115 | The other extensions methods applies pagination and ordering to the query, ```IQueryable```. 116 | Ordering supports using '.' to navigate to nested properties. 117 | 118 | #### Query Model Binders 119 | Add Query and Query Criteria to MVC. 120 | 121 | ```csharp 122 | services 123 | .AddQueryModelBinders(); 124 | ``` 125 | -------------------------------------------------------------------------------- /appveyor.yml: -------------------------------------------------------------------------------- 1 | version: 9.0.1.{build} 2 | skip_tags: true 3 | max_jobs: 1 4 | image: Visual Studio 2022 5 | configuration: Release 6 | platform: Any CPU 7 | force_https_clone: true 8 | nuget: 9 | account_feed: false 10 | project_feed: false 11 | environment: 12 | NUGET_HOST: https://www.nuget.org 13 | NUGET_APIKEY: 14 | secure: GmL+s4FAMc4kMD/O+LST8XV2QXxjvpjV0muni1WttUxjwaQRXDwyXDRHpE3RvEl5 15 | build: off 16 | build_script: 17 | - ps: >- 18 | dotnet build .\DynamicExpression.sln; 19 | test: off 20 | test_script: 21 | - ps: >- 22 | dotnet test .\.tests\DynamicExpression.Test\DynamicExpression.Test.csproj 23 | artifacts: 24 | - path: '**\*.nupkg' 25 | name: NuGet Package 26 | - path: '**\*.snupkg' 27 | name: NuGet Symbol Package 28 | deploy: 29 | - provider: NuGet 30 | server: $(NUGET_HOST) 31 | api_key: $(NUGET_APIKEY) 32 | on: 33 | branch: master 34 | -------------------------------------------------------------------------------- /icon.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vivet/DynamicExpression/293969ece97e68ded55e636db05d1d8354c85b72/icon.jpg --------------------------------------------------------------------------------