From 6e6f179552c1d3025037e17cbb7114c33098b030 Mon Sep 17 00:00:00 2001 From: AliReZa Sabouri <7004080+alirezanet@users.noreply.github.com> Date: Tue, 14 Dec 2021 10:35:52 +0330 Subject: [PATCH] Next (v2.4.7) (#57) * Add new overload for ApplyPaging with page and pageSize * update to 2.4.7 * fix warning declared but never used * use file scope namespace * add Microsoft.CodeAnalysis.CSharp.Scripting Benchmark * add comments for setup parts * add link to the benchmark class * change gridify benchmark row color * Add IsValid - fix #48 * fix IsValid example * add invalid filter example --- benchmark/Benchmark.csproj | 1 + benchmark/GridifyMapperUsages.cs | 219 ++- .../LibraryComparisionFilteringBenchmark.cs | 223 ++- benchmark/Program.cs | 21 +- benchmark/QueryBuilderBuildBenchmark.cs | 145 +- benchmark/QueryBuilderEvaluatorBenchmark.cs | 65 +- benchmark/TestClass.cs | 57 +- docs/contribution/README.md | 15 +- docs/guide/README.md | 31 +- docs/guide/getting-started.md | 1 + docs/guide/gridifyQuery.md | 37 + docs/guide/queryBuilder.md | 1 + .../Gridify.EntityFramework.csproj | 2 +- src/Gridify/GMap.cs | 39 +- src/Gridify/Gridify.csproj | 2 +- src/Gridify/GridifyExtensions.cs | 889 +++++---- src/Gridify/GridifyFilteringException.cs | 9 +- src/Gridify/GridifyMapper.cs | 287 ++- src/Gridify/GridifyMapperConfiguration.cs | 33 +- src/Gridify/GridifyMapperException.cs | 13 +- src/Gridify/GridifyOrderingException.cs | 9 +- src/Gridify/GridifyQuery.cs | 33 +- src/Gridify/GridifyQueryException.cs | 13 +- src/Gridify/IGMap.cs | 15 +- src/Gridify/IGridifyFiltering.cs | 11 +- src/Gridify/IGridifyMapper.cs | 37 +- src/Gridify/IGridifyOrdering.cs | 9 +- src/Gridify/IGridifyPagination.cs | 11 +- src/Gridify/IGridifyQuery.cs | 7 +- src/Gridify/IQueryBuilder.cs | 441 ++--- src/Gridify/Paging.cs | 39 +- src/Gridify/PredicateBuilder.cs | 77 +- src/Gridify/QueryBuilder.cs | 490 ++--- src/Gridify/QueryablePaging.cs | 31 +- src/Gridify/Syntax/BinaryExpressionSyntax.cs | 37 +- src/Gridify/Syntax/ExpressionSyntax.cs | 7 +- src/Gridify/Syntax/FieldExpressionSyntax.cs | 41 +- src/Gridify/Syntax/GridifyTypeBuilder.cs | 161 +- src/Gridify/Syntax/Lexer.cs | 301 ++- .../Syntax/ParenthesizedExpressionSyntax.cs | 37 +- src/Gridify/Syntax/Parser.cs | 240 ++- .../Syntax/ReplaceExpressionVisitor.cs | 29 +- src/Gridify/Syntax/SyntaxKind.cs | 65 +- src/Gridify/Syntax/SyntaxNode.cs | 11 +- src/Gridify/Syntax/SyntaxToken.cs | 37 +- src/Gridify/Syntax/SyntaxTree.cs | 29 +- .../Syntax/SyntaxTreeToQueryConvertor.cs | 775 ++++---- src/Gridify/Syntax/ValueExpressionSyntax.cs | 33 +- .../DatabaseFixture.cs | 53 +- .../GridifyEntityFrameworkTests.cs | 151 +- .../MyDbContext.cs | 33 +- .../GridifyEntityFrameworkSqlTests.cs | 111 +- .../Interceptors.cs | 205 +- .../MyDbContext.cs | 37 +- test/Gridify.Tests/GridifyExtensionsShould.cs | 1735 +++++++++-------- test/Gridify.Tests/GridifyMapperShould.cs | 115 +- .../GridifyNestedCollectionTests.cs | 497 +++-- test/Gridify.Tests/Issue36Tests.cs | 123 +- test/Gridify.Tests/QueryBuilderShould.cs | 191 +- test/Gridify.Tests/TestClass.cs | 59 +- 60 files changed, 4322 insertions(+), 4104 deletions(-) diff --git a/benchmark/Benchmark.csproj b/benchmark/Benchmark.csproj index 034107fe..e353df47 100644 --- a/benchmark/Benchmark.csproj +++ b/benchmark/Benchmark.csproj @@ -9,6 +9,7 @@ + diff --git a/benchmark/GridifyMapperUsages.cs b/benchmark/GridifyMapperUsages.cs index d29d38c6..f276fc5e 100644 --- a/benchmark/GridifyMapperUsages.cs +++ b/benchmark/GridifyMapperUsages.cs @@ -7,128 +7,127 @@ using Gridify; using Gridify.Tests; -namespace Benchmarks +namespace Benchmarks; + +[MemoryDiagnoser] +[RPlotExporter] +[Orderer(SummaryOrderPolicy.FastestToSlowest)] +public class GridifyMapperUsages { - [MemoryDiagnoser] - [RPlotExporter] - [Orderer(SummaryOrderPolicy.FastestToSlowest)] - public class GridifyMapperUsages - { - private static readonly Consumer Consumer = new(); - private TestClass[] _data; - private Func compiled1; - private Func compiled2; - private Func compiled3; + private static readonly Consumer Consumer = new(); + private TestClass[] _data; + private Func compiled1; + private Func compiled2; + private Func compiled3; - private IQueryable Ds => _data.AsQueryable(); - private IEnumerable EnumerableDs => _data.ToList(); - private IGridifyMapper ggm { get; set; } + private IQueryable Ds => _data.AsQueryable(); + private IEnumerable EnumerableDs => _data.ToList(); + private IGridifyMapper ggm { get; set; } - [GlobalSetup] - public void Setup() - { - _data = GetSampleData().ToArray(); + [GlobalSetup] + public void Setup() + { + _data = GetSampleData().ToArray(); - ggm = new GridifyMapper().GenerateMappings(); + ggm = new GridifyMapper().GenerateMappings(); - // compiled query (this is not included in our readme benchmarks) - var gq1 = new GridifyQuery() { Filter = "Name=*a" }; - var gq2 = new GridifyQuery() { Filter = "Id>5" }; - var gq3 = new GridifyQuery() { Filter = "Name=Ali" }; - compiled1 = gq1.GetFilteringExpression(ggm).Compile(); - compiled2 = gq2.GetFilteringExpression(ggm).Compile(); - compiled3 = gq3.GetFilteringExpression(ggm).Compile(); - } + // compiled query (this is not included in our readme benchmarks) + var gq1 = new GridifyQuery() { Filter = "Name=*a" }; + var gq2 = new GridifyQuery() { Filter = "Id>5" }; + var gq3 = new GridifyQuery() { Filter = "Name=Ali" }; + compiled1 = gq1.GetFilteringExpression(ggm).Compile(); + compiled2 = gq2.GetFilteringExpression(ggm).Compile(); + compiled3 = gq3.GetFilteringExpression(ggm).Compile(); + } - [Benchmark(Baseline = true)] - public void NativeLinQ() - { - Ds.Where(q => q.Name.Contains("a")).Consume(Consumer); - Ds.Where(q => q.Id > 5).Consume(Consumer); - Ds.Where(q => q.Name == "Ali").Consume(Consumer); - } - [Benchmark] - public void Gridify_GlobalMapper() - { - Ds.ApplyFiltering("Name=*a", ggm).Consume(Consumer); - Ds.ApplyFiltering("Id>5", ggm).Consume(Consumer); - Ds.ApplyFiltering("Name=Ali", ggm).Consume(Consumer); - } - [Benchmark] - public void Gridify_SingleMapper_Generated() - { - var gm = new GridifyMapper().GenerateMappings(); - Ds.ApplyFiltering("Name=*a", gm).Consume(Consumer); - Ds.ApplyFiltering("Id>5", gm).Consume(Consumer); - Ds.ApplyFiltering("Name=Ali", gm).Consume(Consumer); - } + [Benchmark(Baseline = true)] + public void NativeLinQ() + { + Ds.Where(q => q.Name.Contains("a")).Consume(Consumer); + Ds.Where(q => q.Id > 5).Consume(Consumer); + Ds.Where(q => q.Name == "Ali").Consume(Consumer); + } + [Benchmark] + public void Gridify_GlobalMapper() + { + Ds.ApplyFiltering("Name=*a", ggm).Consume(Consumer); + Ds.ApplyFiltering("Id>5", ggm).Consume(Consumer); + Ds.ApplyFiltering("Name=Ali", ggm).Consume(Consumer); + } + [Benchmark] + public void Gridify_SingleMapper_Generated() + { + var gm = new GridifyMapper().GenerateMappings(); + Ds.ApplyFiltering("Name=*a", gm).Consume(Consumer); + Ds.ApplyFiltering("Id>5", gm).Consume(Consumer); + Ds.ApplyFiltering("Name=Ali", gm).Consume(Consumer); + } - [Benchmark] - public void Gridify_SingleMapper_Manual() - { - var gm = new GridifyMapper() - .AddMap("name") - .AddMap("id"); + [Benchmark] + public void Gridify_SingleMapper_Manual() + { + var gm = new GridifyMapper() + .AddMap("name") + .AddMap("id"); - Ds.ApplyFiltering("Name=*a", gm).Consume(Consumer); - Ds.ApplyFiltering("Id>5", gm).Consume(Consumer); - Ds.ApplyFiltering("Name=Ali", gm).Consume(Consumer); - } + Ds.ApplyFiltering("Name=*a", gm).Consume(Consumer); + Ds.ApplyFiltering("Id>5", gm).Consume(Consumer); + Ds.ApplyFiltering("Name=Ali", gm).Consume(Consumer); + } - [Benchmark] - public void Gridify_NoMapper() - { - Ds.ApplyFiltering("Name=*a").Consume(Consumer); - Ds.ApplyFiltering("Id>5").Consume(Consumer); - Ds.ApplyFiltering("Name=Ali").Consume(Consumer); - } - [Benchmark] - public void Gridify_EachAction_Generated() - { - Ds.ApplyFiltering("Name=*a", new GridifyMapper().GenerateMappings()).Consume(Consumer); - Ds.ApplyFiltering("Id>5", new GridifyMapper().GenerateMappings()).Consume(Consumer); - Ds.ApplyFiltering("Name=Ali", new GridifyMapper().GenerateMappings()).Consume(Consumer); - } + [Benchmark] + public void Gridify_NoMapper() + { + Ds.ApplyFiltering("Name=*a").Consume(Consumer); + Ds.ApplyFiltering("Id>5").Consume(Consumer); + Ds.ApplyFiltering("Name=Ali").Consume(Consumer); + } + [Benchmark] + public void Gridify_EachAction_Generated() + { + Ds.ApplyFiltering("Name=*a", new GridifyMapper().GenerateMappings()).Consume(Consumer); + Ds.ApplyFiltering("Id>5", new GridifyMapper().GenerateMappings()).Consume(Consumer); + Ds.ApplyFiltering("Name=Ali", new GridifyMapper().GenerateMappings()).Consume(Consumer); + } - // [Benchmark] - // public void GridifyCompiled() - // { - // EnumerableDs.Where(compiled1).Consume(Consumer); - // EnumerableDs.Where(compiled2).Consume(Consumer); - // EnumerableDs.Where(compiled3).Consume(Consumer); - // } + // [Benchmark] + // public void GridifyCompiled() + // { + // EnumerableDs.Where(compiled1).Consume(Consumer); + // EnumerableDs.Where(compiled2).Consume(Consumer); + // EnumerableDs.Where(compiled3).Consume(Consumer); + // } - public static IEnumerable GetSampleData() - { - var lst = new List(); - lst.Add(new TestClass(1, "John", null, Guid.NewGuid(), DateTime.Now)); - lst.Add(new TestClass(2, "Bob", null, Guid.NewGuid(), DateTime.UtcNow)); - lst.Add(new TestClass(3, "Jack", (TestClass)lst[0].Clone(), Guid.Empty, DateTime.Now.AddDays(2))); - lst.Add(new TestClass(4, "Rose", null, Guid.Parse("e2cec5dd-208d-4bb5-a852-50008f8ba366"))); - lst.Add(new TestClass(5, "Ali", null)); - lst.Add(new TestClass(6, "Hamid", (TestClass)lst[0].Clone(), Guid.Parse("de12bae1-93fa-40e4-92d1-2e60f95b468c"))); - lst.Add(new TestClass(7, "Hasan", (TestClass)lst[1].Clone())); - lst.Add(new TestClass(8, "Farhad", (TestClass)lst[2].Clone(), Guid.Empty)); - lst.Add(new TestClass(9, "Sara", null)); - lst.Add(new TestClass(10, "Jorge", null)); - lst.Add(new TestClass(11, "joe", null)); - lst.Add(new TestClass(12, "jimmy", (TestClass)lst[0].Clone())); - lst.Add(new TestClass(13, "Nazanin", null)); - lst.Add(new TestClass(14, "Reza", null)); - lst.Add(new TestClass(15, "Korosh", (TestClass)lst[0].Clone())); - lst.Add(new TestClass(16, "Kamran", (TestClass)lst[1].Clone())); - lst.Add(new TestClass(17, "Saeid", (TestClass)lst[2].Clone())); - lst.Add(new TestClass(18, "jessi==ca", null)); - lst.Add(new TestClass(19, "Ped=ram", null)); - lst.Add(new TestClass(20, "Peyman!", null)); - lst.Add(new TestClass(21, "Fereshte", null)); - lst.Add(new TestClass(22, "LIAM", null)); - lst.Add(new TestClass(22, @"\Liam", null)); - lst.Add(new TestClass(23, "LI | AM", null)); - lst.Add(new TestClass(24, "(LI,AM)", null)); - return lst; - } + public static IEnumerable GetSampleData() + { + var lst = new List(); + lst.Add(new TestClass(1, "John", null, Guid.NewGuid(), DateTime.Now)); + lst.Add(new TestClass(2, "Bob", null, Guid.NewGuid(), DateTime.UtcNow)); + lst.Add(new TestClass(3, "Jack", (TestClass)lst[0].Clone(), Guid.Empty, DateTime.Now.AddDays(2))); + lst.Add(new TestClass(4, "Rose", null, Guid.Parse("e2cec5dd-208d-4bb5-a852-50008f8ba366"))); + lst.Add(new TestClass(5, "Ali", null)); + lst.Add(new TestClass(6, "Hamid", (TestClass)lst[0].Clone(), Guid.Parse("de12bae1-93fa-40e4-92d1-2e60f95b468c"))); + lst.Add(new TestClass(7, "Hasan", (TestClass)lst[1].Clone())); + lst.Add(new TestClass(8, "Farhad", (TestClass)lst[2].Clone(), Guid.Empty)); + lst.Add(new TestClass(9, "Sara", null)); + lst.Add(new TestClass(10, "Jorge", null)); + lst.Add(new TestClass(11, "joe", null)); + lst.Add(new TestClass(12, "jimmy", (TestClass)lst[0].Clone())); + lst.Add(new TestClass(13, "Nazanin", null)); + lst.Add(new TestClass(14, "Reza", null)); + lst.Add(new TestClass(15, "Korosh", (TestClass)lst[0].Clone())); + lst.Add(new TestClass(16, "Kamran", (TestClass)lst[1].Clone())); + lst.Add(new TestClass(17, "Saeid", (TestClass)lst[2].Clone())); + lst.Add(new TestClass(18, "jessi==ca", null)); + lst.Add(new TestClass(19, "Ped=ram", null)); + lst.Add(new TestClass(20, "Peyman!", null)); + lst.Add(new TestClass(21, "Fereshte", null)); + lst.Add(new TestClass(22, "LIAM", null)); + lst.Add(new TestClass(22, @"\Liam", null)); + lst.Add(new TestClass(23, "LI | AM", null)); + lst.Add(new TestClass(24, "(LI,AM)", null)); + return lst; } } diff --git a/benchmark/LibraryComparisionFilteringBenchmark.cs b/benchmark/LibraryComparisionFilteringBenchmark.cs index a861238c..e48b2145 100644 --- a/benchmark/LibraryComparisionFilteringBenchmark.cs +++ b/benchmark/LibraryComparisionFilteringBenchmark.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.Linq; using System.Linq.Dynamic.Core; +using System.Linq.Expressions; using BenchmarkDotNet.Attributes; using BenchmarkDotNet.Engines; using BenchmarkDotNet.Order; @@ -9,103 +10,128 @@ using Fop.FopExpression; using Gridify; using Gridify.Tests; +using Microsoft.CodeAnalysis.CSharp.Scripting; +using Microsoft.CodeAnalysis.Scripting; using Microsoft.Extensions.Options; using Sieve.Models; using Sieve.Services; -namespace Benchmarks +namespace Benchmarks; + +[MemoryDiagnoser] +[RPlotExporter] +[Orderer(SummaryOrderPolicy.FastestToSlowest)] +public class LibraryComparisionFilteringBenchmark { - [MemoryDiagnoser] - [RPlotExporter] - [Orderer(SummaryOrderPolicy.FastestToSlowest)] - public class LibraryComparisionFilteringBenchmark + private static readonly Consumer Consumer = new(); + private TestClass[] _data; + private ScriptOptions _options; + private SieveProcessor _processor; + private GridifyMapper _gm; + + private IQueryable Ds => _data.AsQueryable(); + + [GlobalSetup] + public void Setup() + { + _data = GetSampleData().ToArray(); + + // Sieve + _processor = new SieveProcessor(new OptionsWrapper(new SieveOptions())); + + // gridify + _gm = new GridifyMapper(true); + + // CSharpScripting + _options = ScriptOptions.Default.AddReferences(typeof(TestClass).Assembly); + } + + + [Benchmark(Baseline = true)] + public void Native_LINQ() + { + Ds.Where(q => q.Name.Contains('a')).Consume(Consumer); + Ds.Where(q => q.Id > 5).Consume(Consumer); + Ds.Where(q => q.Name == "Ali").Consume(Consumer); + } + + [Benchmark] + public void Gridify() + { + Ds.ApplyFiltering("Name=*a", _gm).Consume(Consumer); + Ds.ApplyFiltering("Id>5", _gm).Consume(Consumer); + Ds.ApplyFiltering("Name=Ali", _gm).Consume(Consumer); + } + + [Benchmark] + public void Fop() + { + // fop doesn't have filtering only feature? + Ds.ApplyFop(FopExpressionBuilder.Build("Name~=a", "Name", 1, 1000)).Item1.Consume(Consumer); + Ds.ApplyFop(FopExpressionBuilder.Build("Id>5", "Name", 1, 1000)).Item1.Consume(Consumer); + Ds.ApplyFop(FopExpressionBuilder.Build("Name==Ali", "Name", 1, 1000)).Item1.Consume(Consumer); + } + + [Benchmark] + public void DynamicLinq() + { + Ds.Where("Name.Contains(@0)", "a").Consume(Consumer); + Ds.Where("Id > (@0)", "5").Consume(Consumer); + Ds.Where("Name==(@0)", "Ali").Consume(Consumer); + } + + [Benchmark] + public void CSharp_Scripting() + { + // Is there any non-async way to do this? + Ds.Where(CSharpScript.EvaluateAsync>> + ("q => q.Name.Contains('a')", _options).Result).Consume(Consumer); + + Ds.Where(CSharpScript.EvaluateAsync>> + ("q => q.Id > 5", _options).Result).Consume(Consumer); + + Ds.Where(CSharpScript.EvaluateAsync>> + ("q => q.Name == \"Ali\"", _options).Result).Consume(Consumer); + } + + [Benchmark] + public void Sieve() + { + _processor.Apply(new SieveModel { Filters = "Name@=a" }, Ds, applySorting: false, applyPagination: false).Consume(Consumer); + _processor.Apply(new SieveModel { Filters = "Id>5" }, Ds, applySorting: false, applyPagination: false).Consume(Consumer); + _processor.Apply(new SieveModel { Filters = "Name==Ali" }, Ds, applySorting: false, applyPagination: false).Consume(Consumer); + } + + + public static IEnumerable GetSampleData() { - private static readonly Consumer Consumer = new(); - private TestClass[] _data; - - private IQueryable Ds => _data.AsQueryable(); - - [GlobalSetup] - public void Setup() - { - _data = GetSampleData().ToArray(); - } - - - [Benchmark(Baseline = true)] - public void NativeLinQ() - { - Ds.Where(q => q.Name.Contains('a')).Consume(Consumer); - Ds.Where(q => q.Id > 5).Consume(Consumer); - Ds.Where(q => q.Name == "Ali").Consume(Consumer); - } - - [Benchmark] - public void Gridify() - { - var gm = new GridifyMapper().GenerateMappings(); - Ds.ApplyFiltering("Name=*a", gm).Consume(Consumer); - Ds.ApplyFiltering("Id>5", gm).Consume(Consumer); - Ds.ApplyFiltering("Name=Ali", gm).Consume(Consumer); - } - - [Benchmark] - public void Fop() - { - // fop doesn't have filtering only feature - Ds.ApplyFop(FopExpressionBuilder.Build("Name~=a", "Name", 1, 1000)).Item1.Consume(Consumer); - Ds.ApplyFop(FopExpressionBuilder.Build("Id>5", "Name", 1, 1000)).Item1.Consume(Consumer); - Ds.ApplyFop(FopExpressionBuilder.Build("Name==Ali", "Name", 1, 1000)).Item1.Consume(Consumer); - } - - [Benchmark] - public void DynamicLinQ() - { - Ds.Where("Name.Contains(@0)", "a").Consume(Consumer); - Ds.Where("Id > (@0)", "5").Consume(Consumer); - Ds.Where("Name==(@0)", "Ali").Consume(Consumer); - } - - [Benchmark] - public void Sieve() - { - var processor = new SieveProcessor(new OptionsWrapper(new SieveOptions())); - processor.Apply(new SieveModel { Filters = "Name@=a" }, Ds, applySorting: false, applyPagination: false).Consume(Consumer); - processor.Apply(new SieveModel { Filters = "Id>5" }, Ds, applySorting: false, applyPagination: false).Consume(Consumer); - processor.Apply(new SieveModel { Filters = "Name==Ali" }, Ds, applySorting: false, applyPagination: false).Consume(Consumer); - } - - - public static IEnumerable GetSampleData() - { - var lst = new List(); - lst.Add(new TestClass(1, "John", null, Guid.NewGuid(), DateTime.Now)); - lst.Add(new TestClass(2, "Bob", null, Guid.NewGuid(), DateTime.UtcNow)); - lst.Add(new TestClass(3, "Jack", (TestClass)lst[0].Clone(), Guid.Empty, DateTime.Now.AddDays(2))); - lst.Add(new TestClass(4, "Rose", null, Guid.Parse("e2cec5dd-208d-4bb5-a852-50008f8ba366"))); - lst.Add(new TestClass(5, "Ali", null)); - lst.Add(new TestClass(6, "Hamid", (TestClass)lst[0].Clone(), Guid.Parse("de12bae1-93fa-40e4-92d1-2e60f95b468c"))); - lst.Add(new TestClass(7, "Hasan", (TestClass)lst[1].Clone())); - lst.Add(new TestClass(8, "Farhad", (TestClass)lst[2].Clone(), Guid.Empty)); - lst.Add(new TestClass(9, "Sara", null)); - lst.Add(new TestClass(10, "Jorge", null)); - lst.Add(new TestClass(11, "joe", null)); - lst.Add(new TestClass(12, "jimmy", (TestClass)lst[0].Clone())); - lst.Add(new TestClass(13, "Nazanin", null)); - lst.Add(new TestClass(14, "Reza", null)); - lst.Add(new TestClass(15, "Korosh", (TestClass)lst[0].Clone())); - lst.Add(new TestClass(16, "Kamran", (TestClass)lst[1].Clone())); - lst.Add(new TestClass(17, "Saeid", (TestClass)lst[2].Clone())); - lst.Add(new TestClass(18, "jessi==ca", null)); - lst.Add(new TestClass(19, "Ped=ram", null)); - lst.Add(new TestClass(20, "Peyman!", null)); - lst.Add(new TestClass(21, "Fereshte", null)); - lst.Add(new TestClass(22, "LIAM", null)); - lst.Add(new TestClass(22, @"\Liam", null)); - lst.Add(new TestClass(23, "LI | AM", null)); - lst.Add(new TestClass(24, "(LI,AM)", null)); - return lst; - } + var lst = new List(); + lst.Add(new TestClass(1, "John", null, Guid.NewGuid(), DateTime.Now)); + lst.Add(new TestClass(2, "Bob", null, Guid.NewGuid(), DateTime.UtcNow)); + lst.Add(new TestClass(3, "Jack", (TestClass)lst[0].Clone(), Guid.Empty, DateTime.Now.AddDays(2))); + lst.Add(new TestClass(4, "Rose", null, Guid.Parse("e2cec5dd-208d-4bb5-a852-50008f8ba366"))); + lst.Add(new TestClass(5, "Ali", null)); + lst.Add(new TestClass(6, "Hamid", (TestClass)lst[0].Clone(), Guid.Parse("de12bae1-93fa-40e4-92d1-2e60f95b468c"))); + lst.Add(new TestClass(7, "Hasan", (TestClass)lst[1].Clone())); + lst.Add(new TestClass(8, "Farhad", (TestClass)lst[2].Clone(), Guid.Empty)); + lst.Add(new TestClass(9, "Sara", null)); + lst.Add(new TestClass(10, "Jorge", null)); + lst.Add(new TestClass(11, "joe", null)); + lst.Add(new TestClass(12, "jimmy", (TestClass)lst[0].Clone())); + lst.Add(new TestClass(13, "Nazanin", null)); + lst.Add(new TestClass(14, "Reza", null)); + lst.Add(new TestClass(15, "Korosh", (TestClass)lst[0].Clone())); + lst.Add(new TestClass(16, "Kamran", (TestClass)lst[1].Clone())); + lst.Add(new TestClass(17, "Saeid", (TestClass)lst[2].Clone())); + lst.Add(new TestClass(18, "jessi==ca", null)); + lst.Add(new TestClass(19, "Ped=ram", null)); + lst.Add(new TestClass(20, "Peyman!", null)); + lst.Add(new TestClass(21, "Fereshte", null)); + lst.Add(new TestClass(22, "LIAM", null)); + lst.Add(new TestClass(22, @"\Liam", null)); + lst.Add(new TestClass(23, "LI | AM", null)); + lst.Add(new TestClass(24, "(LI,AM)", null)); + return lst; } } @@ -116,10 +142,11 @@ public static IEnumerable GetSampleData() // DefaultJob : .NET 6.0.0 (6.0.21.52210), X64 RyuJIT // // -// | Method | Mean | Error | StdDev | Ratio | RatioSD | Gen 0 | Gen 1 | Allocated | -// |------------ |-----------:|---------:|---------:|------:|--------:|--------:|--------:|----------:| -// | NativeLinQ | 823.8 us | 11.18 us | 9.91 us | 1.00 | 0.00 | 4.8828 | 1.9531 | 35 KB | -// | Gridify | 853.1 us | 13.88 us | 12.98 us | 1.03 | 0.02 | 6.8359 | 2.9297 | 43 KB | -// | DynamicLinQ | 967.3 us | 6.65 us | 5.55 us | 1.17 | 0.01 | 19.5313 | 9.7656 | 123 KB | -// | Sieve | 1,275.2 us | 5.62 us | 4.70 us | 1.55 | 0.02 | 7.8125 | 3.9063 | 55 KB | -// | Fop | 3,480.2 us | 55.81 us | 52.21 us | 4.23 | 0.06 | 54.6875 | 27.3438 | 343 KB | +// | Method | Mean | Error | StdDev | Ratio | RatioSD | Gen 0 | Gen 1 | Allocated | +// |----------------- |-------------:|------------:|------------:|-------:|--------:|----------:|----------:|----------:| +// | Native_LINQ | 806.3 us | 4.89 us | 4.57 us | 1.00 | 0.00 | 4.8828 | 1.9531 | 35 KB | +// | Gridify | 839.6 us | 5.69 us | 4.75 us | 1.04 | 0.01 | 5.8594 | 2.9297 | 39 KB | +// | DynamicLinq | 973.8 us | 8.65 us | 6.75 us | 1.21 | 0.01 | 19.5313 | 9.7656 | 123 KB | +// | Sieve | 1,299.7 us | 12.74 us | 11.29 us | 1.61 | 0.02 | 7.8125 | 3.9063 | 53 KB | +// | Fop | 3,498.6 us | 29.45 us | 26.11 us | 4.34 | 0.03 | 54.6875 | 27.3438 | 348 KB | +// | CSharp_Scripting | 231,510.6 us | 4,406.95 us | 4,122.26 us | 287.13 | 5.12 | 3000.0000 | 1000.0000 | 24,198 KB | diff --git a/benchmark/Program.cs b/benchmark/Program.cs index 3d62ac0a..63793e7e 100644 --- a/benchmark/Program.cs +++ b/benchmark/Program.cs @@ -1,17 +1,16 @@ using System; using BenchmarkDotNet.Running; -namespace Benchmarks +namespace Benchmarks; + +public class Program { - public class Program + private static void Main() { - private static void Main() - { - BenchmarkRunner.Run(); - // BenchmarkRunner.Run(); - // BenchmarkRunner.Run(); - // BenchmarkRunner.Run(); - Console.Read(); - } + BenchmarkRunner.Run(); + // BenchmarkRunner.Run(); + // BenchmarkRunner.Run(); + // BenchmarkRunner.Run(); + Console.Read(); } -} +} \ No newline at end of file diff --git a/benchmark/QueryBuilderBuildBenchmark.cs b/benchmark/QueryBuilderBuildBenchmark.cs index c7e46486..20284e22 100644 --- a/benchmark/QueryBuilderBuildBenchmark.cs +++ b/benchmark/QueryBuilderBuildBenchmark.cs @@ -7,94 +7,93 @@ using Gridify; using Gridify.Tests; -namespace Benchmarks +namespace Benchmarks; + +[MemoryDiagnoser] +[RPlotExporter] +[Orderer(SummaryOrderPolicy.FastestToSlowest)] +public class QueryBuilderBuildBenchmark { - [MemoryDiagnoser] - [RPlotExporter] - [Orderer(SummaryOrderPolicy.FastestToSlowest)] - public class QueryBuilderBuildBenchmark - { - private readonly IEnumerable _data; - private static readonly Consumer Consumer = new(); - private readonly Func, IEnumerable> BuildCompiledFuc; - private readonly Func, IQueryable> BuildFunc; - private readonly Func BuildFilteringExpressionFunc; - private readonly Func, Paging> BuildWithPagingCompiledFunc; - private readonly Func, Paging> BuildWithPagingFunc; + private readonly IEnumerable _data; + private static readonly Consumer Consumer = new(); + private readonly Func, IEnumerable> BuildCompiledFuc; + private readonly Func, IQueryable> BuildFunc; + private readonly Func BuildFilteringExpressionFunc; + private readonly Func, Paging> BuildWithPagingCompiledFunc; + private readonly Func, Paging> BuildWithPagingFunc; - public QueryBuilderBuildBenchmark() - { - _data = LibraryComparisionFilteringBenchmark.GetSampleData().ToArray(); + public QueryBuilderBuildBenchmark() + { + _data = LibraryComparisionFilteringBenchmark.GetSampleData().ToArray(); - var builder = new QueryBuilder() - .AddCondition("id>2") - .AddCondition("name=*a"); + var builder = new QueryBuilder() + .AddCondition("id>2") + .AddCondition("name=*a"); - BuildCompiledFuc = builder.BuildCompiled(); - BuildFunc = builder.Build(); - BuildFilteringExpressionFunc = builder.BuildFilteringExpression().Compile(); - BuildWithPagingCompiledFunc = builder.BuildWithPagingCompiled(); - BuildWithPagingFunc = builder.BuildWithPaging(); + BuildCompiledFuc = builder.BuildCompiled(); + BuildFunc = builder.Build(); + BuildFilteringExpressionFunc = builder.BuildFilteringExpression().Compile(); + BuildWithPagingCompiledFunc = builder.BuildWithPagingCompiled(); + BuildWithPagingFunc = builder.BuildWithPaging(); - TestOutputs(); - } + TestOutputs(); + } - [Benchmark(Baseline = true)] // this method is only for filtering operations - public void BuildFilteringExpression() - { - _data.Where(BuildFilteringExpressionFunc).Consume(Consumer); - } + [Benchmark(Baseline = true)] // this method is only for filtering operations + public void BuildFilteringExpression() + { + _data.Where(BuildFilteringExpressionFunc).Consume(Consumer); + } - [Benchmark] - public void Build() - { - BuildFunc(_data.AsQueryable()).Consume(Consumer); - } + [Benchmark] + public void Build() + { + BuildFunc(_data.AsQueryable()).Consume(Consumer); + } - [Benchmark] - public void BuildCompiled() - { - BuildCompiledFuc(_data).Consume(Consumer); - } + [Benchmark] + public void BuildCompiled() + { + BuildCompiledFuc(_data).Consume(Consumer); + } - [Benchmark] - public void BuildWithPaging() - { - BuildWithPagingFunc(_data.AsQueryable()).Data.Consume(Consumer); - } + [Benchmark] + public void BuildWithPaging() + { + BuildWithPagingFunc(_data.AsQueryable()).Data.Consume(Consumer); + } - [Benchmark] - public void BuildWithPagingCompiled() - { - BuildWithPagingCompiledFunc(_data.AsQueryable()).Data.Consume(Consumer); - } + [Benchmark] + public void BuildWithPagingCompiled() + { + BuildWithPagingCompiledFunc(_data.AsQueryable()).Data.Consume(Consumer); + } - private void TestOutputs() + private void TestOutputs() + { + if (AllSame(BuildCompiledFuc(_data).Count(), BuildFunc(_data.AsQueryable()).Count(), _data.Where(BuildFilteringExpressionFunc).Count()) && + AllSame(BuildCompiledFuc(_data).First().Id, BuildFunc(_data.AsQueryable()).First().Id, + _data.Where(BuildFilteringExpressionFunc).First().Id) && + AllSame(BuildCompiledFuc(_data).Last().Id, BuildFunc(_data.AsQueryable()).Last().Id, + _data.Where(BuildFilteringExpressionFunc).Last().Id) && + BuildCompiledFuc(_data).Count() < 2) { - if (AllSame(BuildCompiledFuc(_data).Count(), BuildFunc(_data.AsQueryable()).Count(), _data.Where(BuildFilteringExpressionFunc).Count()) && - AllSame(BuildCompiledFuc(_data).First().Id, BuildFunc(_data.AsQueryable()).First().Id, - _data.Where(BuildFilteringExpressionFunc).First().Id) && - AllSame(BuildCompiledFuc(_data).Last().Id, BuildFunc(_data.AsQueryable()).Last().Id, - _data.Where(BuildFilteringExpressionFunc).Last().Id) && - BuildCompiledFuc(_data).Count() < 2) - { - throw new Exception("MISS MATCH OUTPUT"); - } + throw new Exception("MISS MATCH OUTPUT"); } + } - private static bool AllSame(params T[] items) + private static bool AllSame(params T[] items) + { + var first = true; + T comparand = default; + foreach (var i in items) { - var first = true; - T comparand = default; - foreach (var i in items) - { - if (first) comparand = i; - else if (!i.Equals(comparand)) return false; - first = false; - } - - return true; + if (first) comparand = i; + else if (!i.Equals(comparand)) return false; + first = false; } + + return true; } -} +} \ No newline at end of file diff --git a/benchmark/QueryBuilderEvaluatorBenchmark.cs b/benchmark/QueryBuilderEvaluatorBenchmark.cs index f3a0cbbf..76ee63b1 100644 --- a/benchmark/QueryBuilderEvaluatorBenchmark.cs +++ b/benchmark/QueryBuilderEvaluatorBenchmark.cs @@ -7,43 +7,42 @@ using Gridify; using Gridify.Tests; -namespace Benchmarks +namespace Benchmarks; + +[MemoryDiagnoser] +[RPlotExporter] +[Orderer(SummaryOrderPolicy.FastestToSlowest)] +public class QueryBuilderEvaluatorBenchmark { - [MemoryDiagnoser] - [RPlotExporter] - [Orderer(SummaryOrderPolicy.FastestToSlowest)] - public class QueryBuilderEvaluatorBenchmark + private readonly IEnumerable _data; + // private static readonly Consumer Consumer = new(); + private readonly Func,bool> BuildEvaluatorFunc; + private readonly Func,bool> BuildCompiledEvaluatorFunc; + + public QueryBuilderEvaluatorBenchmark() + { + _data = LibraryComparisionFilteringBenchmark.GetSampleData().ToArray(); + + var builder = new QueryBuilder() + .AddCondition("id>2") + .AddCondition("name=*a"); + + BuildEvaluatorFunc = builder.BuildEvaluator(); + BuildCompiledEvaluatorFunc = builder.BuildCompiledEvaluator(); + } + + [Benchmark] + public void BuildEvaluator() { - private readonly IEnumerable _data; - // private static readonly Consumer Consumer = new(); - private readonly Func,bool> BuildEvaluatorFunc; - private readonly Func,bool> BuildCompiledEvaluatorFunc; - - public QueryBuilderEvaluatorBenchmark() - { - _data = LibraryComparisionFilteringBenchmark.GetSampleData().ToArray(); - - var builder = new QueryBuilder() - .AddCondition("id>2") - .AddCondition("name=*a"); - - BuildEvaluatorFunc = builder.BuildEvaluator(); - BuildCompiledEvaluatorFunc = builder.BuildCompiledEvaluator(); - } - - [Benchmark] - public void BuildEvaluator() - { - BuildEvaluatorFunc(_data.AsQueryable()); - } - - [Benchmark] - public void BuildCompiledEvaluator() - { - BuildCompiledEvaluatorFunc(_data); - } + BuildEvaluatorFunc(_data.AsQueryable()); + } + [Benchmark] + public void BuildCompiledEvaluator() + { + BuildCompiledEvaluatorFunc(_data); } + } /* Last Run: diff --git a/benchmark/TestClass.cs b/benchmark/TestClass.cs index e6a64406..7f05827e 100644 --- a/benchmark/TestClass.cs +++ b/benchmark/TestClass.cs @@ -1,40 +1,39 @@ using System; using Sieve.Attributes; -namespace Gridify.Tests +namespace Gridify.Tests; + +public class TestClass : ICloneable { - public class TestClass : ICloneable + public TestClass() { - public TestClass() - { - } + } - public TestClass(int id, string name, TestClass classProp, Guid myGuid = default, DateTime? date = default) - { - Id = id; - Name = name; - ChildClass = classProp; - MyGuid = myGuid; - MyDateTime = date; - } - [Sieve(CanFilter = true, CanSort = true)] - public int Id { get; set; } - [Sieve(CanFilter = true, CanSort = true)] - public string Name { get; set; } - public TestClass ChildClass { get; set; } - public DateTime? MyDateTime { get; set; } - public Guid MyGuid { get; set; } + public TestClass(int id, string name, TestClass classProp, Guid myGuid = default, DateTime? date = default) + { + Id = id; + Name = name; + ChildClass = classProp; + MyGuid = myGuid; + MyDateTime = date; + } + [Sieve(CanFilter = true, CanSort = true)] + public int Id { get; set; } + [Sieve(CanFilter = true, CanSort = true)] + public string Name { get; set; } + public TestClass ChildClass { get; set; } + public DateTime? MyDateTime { get; set; } + public Guid MyGuid { get; set; } - public object Clone() + public object Clone() + { + return new TestClass { - return new TestClass - { - Id = Id, - Name = Name, - ChildClass = ChildClass != null ? (TestClass) ChildClass.Clone() : null, - MyGuid = MyGuid - }; - } + Id = Id, + Name = Name, + ChildClass = ChildClass != null ? (TestClass) ChildClass.Clone() : null, + MyGuid = MyGuid + }; } } \ No newline at end of file diff --git a/docs/contribution/README.md b/docs/contribution/README.md index 97d01106..a9d5fe97 100644 --- a/docs/contribution/README.md +++ b/docs/contribution/README.md @@ -6,11 +6,10 @@ start contributing to the project by submitting pull requests ## Todos -- :zap: Improve the documentation site -- :zap: Improve code documentation (summary) -- :zap: Add project Security Policy -- :zap: Validation method [#48](https://github.com/alirezanet/Gridify/issues/48) -- :zap: Add Support EF.Function.FreeText [#42](https://github.com/alirezanet/Gridify/issues/42) +- :zap: Improve the documentation site +- :zap: Improve code documentation (summary) +- :zap: Add project Security Policy +- :zap: Add Support EF.Function.FreeText [#42](https://github.com/alirezanet/Gridify/issues/42) ## Documentation @@ -44,6 +43,6 @@ check out the [github contributing guide](https://git-scm.com/book/en/v2/GitHub- ## Contributors -- [AliReZa Sabouri](https://github.com/alirezanet) -- [Alireza Arabshahi](https://github.com/AlirezaArabshahi) -- Add your name +- [AliReZa Sabouri](https://github.com/alirezanet) +- [Alireza Arabshahi](https://github.com/AlirezaArabshahi) +- Add your name diff --git a/docs/guide/README.md b/docs/guide/README.md index 328c625b..19947e1a 100644 --- a/docs/guide/README.md +++ b/docs/guide/README.md @@ -17,18 +17,31 @@ Be sure to check out these examples Filtering is the most expensive feature in gridify. the following benchmark is comparing filtering in the most known dynamic linq libraries. As you can see, gridify has the closest result to the native linq. -| Method | Mean | Error | Ratio | Gen 0 | Gen 1 | Allocated | -|------------ |-----------:|---------:|------:|--------:|--------:|----------:| -| NativeLinQ | 823.8 us | 11.18 us | 1.00 | 4.8828 | 1.9531 | 35 KB | -| ***Gridify** | 853.1 us | 13.88 us | **1.03** | 6.8359 | 2.9297 | 43 KB | -| DynamicLinQ | 967.3 us | 6.65 us | 1.17 | 19.5313 | 9.7656 | 123 KB | -| Sieve | 1,275.2 us | 5.62 us | 1.55 | 7.8125 | 3.9063 | 55 KB | -| Fop | 3,480.2 us | 55.81 us | 4.23 | 54.6875 | 27.3438 | 343 KB | +| Method | Mean | Error | Ratio | Gen 0 | Gen 1 | Allocated | +|------------------|-------------:|------------:|-------:|----------:|----------:|----------:| +| Native LINQ | 806.3 us | 4.89 us | 1.00 | 4.8828 | 1.9531 | 35 KB | +| Gridify | 839.6 us | 5.69 us | 1.04 | 5.8594 | 2.9297 | 39 KB | +| DynamicLinq | 973.8 us | 8.65 us | 1.21 | 19.5313 | 9.7656 | 123 KB | +| Sieve | 1,299.7 us | 12.74 us | 1.61 | 7.8125 | 3.9063 | 53 KB | +| Fop | 3,498.6 us | 29.45 us | 4.34 | 54.6875 | 27.3438 | 348 KB | +| CSharp Scripting | 231,510.6 us | 4,406.95 us | 287.13 | 3000.0000 | 1000.0000 | 24,198 KB | + ::: details BenchmarkDotNet=v0.13.1, OS=Windows 10.0.22000 11th Gen Intel Core i5-11400F 2.60GHz, 1 CPU, 12 logical and 6 physical cores .NET SDK=6.0.100 - [Host] : .NET 6.0.0 (6.0.21.52210), X64 RyuJIT - DefaultJob : .NET 6.0.0 (6.0.21.52210), X64 RyuJIT +[Host] : .NET 6.0.0 (6.0.21.52210), X64 RyuJIT +DefaultJob : .NET 6.0.0 (6.0.21.52210), X64 RyuJIT + +This Benchmark is available [Here](https://github.com/alirezanet/Gridify/blob/master/benchmark/LibraryComparisionFilteringBenchmark.cs) ::: + + + + + diff --git a/docs/guide/getting-started.md b/docs/guide/getting-started.md index 81b75f82..50826406 100644 --- a/docs/guide/getting-started.md +++ b/docs/guide/getting-started.md @@ -1,3 +1,4 @@ + # Getting Started There are two packages available for gridify in the nuget repository. diff --git a/docs/guide/gridifyQuery.md b/docs/guide/gridifyQuery.md index 02a686bf..4575ad33 100644 --- a/docs/guide/gridifyQuery.md +++ b/docs/guide/gridifyQuery.md @@ -1,4 +1,5 @@ # GridifyQuery + GridifyQuery is a simple class for configuring Filtering, Ordering and Paging. ``` csharp @@ -14,7 +15,41 @@ var gq = new GridifyQuery() Paging result = personsRepo.Gridify(gq); ``` +## IsValid + +This extension method, checks if the GridifyQuery (Filter, OrderBy) is valid to use with a custom mapper or the auto generated mapper and returns true or false. + +``` csharp +var gq = new GridifyQuery() { Filter = "name=John" , OrderBy = "Age" }; +// true +bool isValid = gq.IsValid(); +``` + +``` csharp +var gq = new GridifyQuery() { Filter = "NonExist=John" , OrderBy = "Age" }; +// false (NonExist is not a property of Person) +bool isValid = gq.IsValid(); +``` + +``` csharp +var gq = new GridifyQuery() { Filter = "@name=!" , OrderBy = "Age" }; +// false (this is not a valid filter) +bool isValid = gq.IsValid(); +``` + +Optionally you can pass a custom mapper to check if the GridifyQuery is valid for that mapper. + +``` csharp +var mapper = new GridifyMapper() + .AddMap("name", q => q.Name); +var gq = new GridifyQuery() { Filter = "name=John" , OrderBy = "Age" }; + +// false (Age is not mapped) +bool isValid = gq.IsValid(mapper); +``` + ## GetFilteringExpression + This extension method, creates a lambda expression using the `GridifyQuery.Filter` property that you can use it in the LINQ `Where` method to filter the data. ``` csharp{2} @@ -22,3 +57,5 @@ var gq = new GridifyQuery() { Filter = "name=John" }; Expression> expression = gq.GetFilteringExpression(); var result = personsRepo.Where(expression); ``` + + diff --git a/docs/guide/queryBuilder.md b/docs/guide/queryBuilder.md index 64396ca9..9f33b2b8 100644 --- a/docs/guide/queryBuilder.md +++ b/docs/guide/queryBuilder.md @@ -13,6 +13,7 @@ The QueryBuilder class is really useful if you want to manually build your query | AddMap | Add a single Map to existing mapper | | RemoveMap | Remove a single Map from existing mapper | | ConfigureDefaultMapper | Configuring default mapper when we didn't use AddMapper method | +| IsValid | Validates Condition, OrderBy, Query , Mapper and returns a bool | | Build | Applies filtering ordering and paging to a queryable context | | BuildCompiled | Compiles the expressions and returns a delegate for applying filtering ordering and paging to a enumerable collection | | BuildFilteringExpression | Returns filtering expression that can be compiled for later use for enumerable collections | diff --git a/src/Gridify.EntityFramework/Gridify.EntityFramework.csproj b/src/Gridify.EntityFramework/Gridify.EntityFramework.csproj index 4dcfe053..0c289386 100644 --- a/src/Gridify.EntityFramework/Gridify.EntityFramework.csproj +++ b/src/Gridify.EntityFramework/Gridify.EntityFramework.csproj @@ -2,7 +2,7 @@ netstandard2.0 Gridify.EntityFramework - 2.4.6 + 2.4.7 Alireza Sabouri TuxTeam Gridify (EntityFramework), Easy and optimized way to apply Filtering, Sorting, and Pagination using text-based data. diff --git a/src/Gridify/GMap.cs b/src/Gridify/GMap.cs index 13e9b538..ebd3ad83 100644 --- a/src/Gridify/GMap.cs +++ b/src/Gridify/GMap.cs @@ -2,28 +2,27 @@ using System.Linq.Expressions; using System.Text.RegularExpressions; -namespace Gridify +namespace Gridify; + +public class GMap : IGMap { - public class GMap : IGMap - { - public string From { get; set; } - public LambdaExpression To { get; set; } - public Func? Convertor { get; set; } + public string From { get; set; } + public LambdaExpression To { get; set; } + public Func? Convertor { get; set; } - public GMap(string from, Expression> to, Func? convertor = null) - { - From = from; - To = to; - Convertor = convertor; - } + public GMap(string from, Expression> to, Func? convertor = null) + { + From = from; + To = to; + Convertor = convertor; + } - internal bool IsNestedCollection() => Regex.IsMatch(To.ToString(), @"\.Select\s*\("); + internal bool IsNestedCollection() => Regex.IsMatch(To.ToString(), @"\.Select\s*\("); - public GMap(string from, Expression> to, Func? convertor = null) - { - From = from; - To = to; - Convertor = convertor; - } + public GMap(string from, Expression> to, Func? convertor = null) + { + From = from; + To = to; + Convertor = convertor; } -} +} \ No newline at end of file diff --git a/src/Gridify/Gridify.csproj b/src/Gridify/Gridify.csproj index e3eabebc..1df5711a 100644 --- a/src/Gridify/Gridify.csproj +++ b/src/Gridify/Gridify.csproj @@ -3,7 +3,7 @@ netstandard2.0 Gridify - 2.4.6 + 2.4.7 Alireza Sabouri TuxTeam Gridify, Easy and optimized way to apply Filtering, Sorting, and Pagination using text-based data. diff --git a/src/Gridify/GridifyExtensions.cs b/src/Gridify/GridifyExtensions.cs index b9ae386f..054ba288 100644 --- a/src/Gridify/GridifyExtensions.cs +++ b/src/Gridify/GridifyExtensions.cs @@ -7,507 +7,570 @@ [assembly: InternalsVisibleTo("Gridify.EntityFramework")] -namespace Gridify -{ - public static partial class GridifyExtensions - { - internal static bool EntityFrameworkCompatibilityLayer { get; set; } - public static int DefaultPageSize { get; set; } = 20; - - #region "Private" +namespace Gridify; - /// - /// Set default Page number and PageSize if its not already set in gridifyQuery - /// - /// query and paging configuration - /// returns a IGridifyPagination with valid PageSize and Page - private static IGridifyPagination FixPagingData(this IGridifyPagination gridifyPagination) - { - // set default for page number - if (gridifyPagination.Page <= 0) - gridifyPagination.Page = 1; - - // set default for PageSize - if (gridifyPagination.PageSize <= 0) - gridifyPagination.PageSize = DefaultPageSize; +public static partial class GridifyExtensions +{ + internal static bool EntityFrameworkCompatibilityLayer { get; set; } + public static int DefaultPageSize { get; set; } = 20; - return gridifyPagination; - } + #region "Private" - #endregion + /// + /// Set default Page number and PageSize if its not already set in gridifyQuery + /// + /// query and paging configuration + /// returns a IGridifyPagination with valid PageSize and Page + private static IGridifyPagination FixPagingData(this IGridifyPagination gridifyPagination) + { + // set default for page number + if (gridifyPagination.Page <= 0) + gridifyPagination.Page = 1; - public static Expression> GetFilteringExpression(this IGridifyFiltering gridifyFiltering, IGridifyMapper? mapper = null) - { - if (string.IsNullOrWhiteSpace(gridifyFiltering.Filter)) - throw new GridifyQueryException("Filter is not defined"); + // set default for PageSize + if (gridifyPagination.PageSize <= 0) + gridifyPagination.PageSize = DefaultPageSize; - var syntaxTree = SyntaxTree.Parse(gridifyFiltering.Filter!); + return gridifyPagination; + } - if (syntaxTree.Diagnostics.Any()) - throw new GridifyFilteringException(syntaxTree.Diagnostics.Last()!); + #endregion - mapper = mapper.FixMapper(syntaxTree); - var (queryExpression, _) = ExpressionToQueryConvertor.GenerateQuery(syntaxTree.Root, mapper); - if (queryExpression == null) throw new GridifyQueryException("Can not create expression with current data"); - return queryExpression; - } + public static Expression> GetFilteringExpression(this IGridifyFiltering gridifyFiltering, IGridifyMapper? mapper = null) + { + if (string.IsNullOrWhiteSpace(gridifyFiltering.Filter)) + throw new GridifyQueryException("Filter is not defined"); - public static IEnumerable>> GetOrderingExpressions(this IGridifyOrdering gridifyOrdering, - IGridifyMapper? mapper = null) - { - if (string.IsNullOrWhiteSpace(gridifyOrdering.OrderBy)) - throw new GridifyQueryException("OrderBy is not defined or not Found"); + var syntaxTree = SyntaxTree.Parse(gridifyFiltering.Filter!); + if (syntaxTree.Diagnostics.Any()) + throw new GridifyFilteringException(syntaxTree.Diagnostics.Last()!); - var members = ParseOrderings(gridifyOrdering.OrderBy!).Select(q => q.memberName).ToList(); - if (mapper is null) - { - foreach (var member in members) - { - Expression>? exp = null; - try - { - exp = GridifyMapper.CreateExpression(member); - } - catch (Exception) - { - // skip if there is no mappings available - } - - if (exp != null) yield return exp; - } - } - else - { - foreach (var member in members.Where(mapper.HasMap)) - { - yield return mapper.GetExpression(member)!; - } - } - } + mapper = mapper.FixMapper(syntaxTree); + var (queryExpression, _) = ExpressionToQueryConvertor.GenerateQuery(syntaxTree.Root, mapper); + if (queryExpression == null) throw new GridifyQueryException("Can not create expression with current data"); + return queryExpression; + } - #region "Public" + public static IEnumerable>> GetOrderingExpressions(this IGridifyOrdering gridifyOrdering, + IGridifyMapper? mapper = null) + { + if (string.IsNullOrWhiteSpace(gridifyOrdering.OrderBy)) + throw new GridifyQueryException("OrderBy is not defined or not Found"); - /// - /// create and return a default GridifyMapper - /// - /// type to set mappings - /// returns an auto generated GridifyMapper - private static IGridifyMapper GetDefaultMapper() - { - return new GridifyMapper().GenerateMappings(); - } - /// - /// if given mapper was null this function creates default generated mapper - /// - /// a GridifyMapper that can be null - /// optional syntaxTree to Lazy mapping generation - /// type to set mappings - /// return back mapper or new generated mapper if it was null - private static IGridifyMapper FixMapper(this IGridifyMapper? mapper, SyntaxTree syntaxTree) + var members = ParseOrderings(gridifyOrdering.OrderBy!).Select(q => q.memberName).ToList(); + if (mapper is null) { - if (mapper != null) return mapper; - - mapper = new GridifyMapper(); - foreach (var field in syntaxTree.Root.Descendants() - .Where(q => q.Kind == SyntaxKind.FieldExpression) - .Cast()) + foreach (var member in members) { + Expression>? exp = null; try { - mapper.AddMap(field.FieldToken.Text); + exp = GridifyMapper.CreateExpression(member); } catch (Exception) { - if (!mapper.Configuration.IgnoreNotMappedFields) - throw new GridifyMapperException($"Property '{field.FieldToken.Text}' not found."); + // skip if there is no mappings available } - } - return mapper; + if (exp != null) yield return exp; + } } - - private static IEnumerable Descendants(this SyntaxNode root) + else { - var nodes = new Stack(new[] { root }); - while (nodes.Any()) + foreach (var member in members.Where(mapper.HasMap)) { - SyntaxNode node = nodes.Pop(); - yield return node; - foreach (var n in node.GetChildren()) nodes.Push(n); + yield return mapper.GetExpression(member)!; } } + } - /// - /// adds Filtering,Ordering And Paging to the query - /// - /// the original(target) queryable object - /// the configuration to apply paging, filtering and ordering - /// this is an optional parameter to apply filtering and ordering using a custom mapping configuration - /// type of target entity - /// returns user query after applying filtering, ordering and paging - public static IQueryable ApplyFilteringOrderingPaging(this IQueryable query, IGridifyQuery? gridifyQuery, - IGridifyMapper? mapper = null) - { - if (gridifyQuery == null) return query; + #region "Public" - query = query.ApplyFiltering(gridifyQuery, mapper); - query = query.ApplyOrdering(gridifyQuery, mapper); - query = query.ApplyPaging(gridifyQuery); - return query; - } + /// + /// create and return a default GridifyMapper + /// + /// type to set mappings + /// returns an auto generated GridifyMapper + private static IGridifyMapper GetDefaultMapper() + { + return new GridifyMapper().GenerateMappings(); + } + + /// + /// if given mapper was null this function creates default generated mapper + /// + /// a GridifyMapper that can be null + /// optional syntaxTree to Lazy mapping generation + /// type to set mappings + /// return back mapper or new generated mapper if it was null + private static IGridifyMapper FixMapper(this IGridifyMapper? mapper, SyntaxTree syntaxTree) + { + if (mapper != null) return mapper; - /// - /// adds Ordering to the query - /// - /// the original(target) queryable object - /// the configuration to apply ordering - /// this is an optional parameter to apply ordering using a custom mapping configuration - /// if you already have an ordering with start with ThenBy, new orderings will add on top of your orders - /// type of target entity - /// returns user query after applying Ordering - public static IQueryable ApplyOrdering(this IQueryable query, IGridifyOrdering? gridifyOrdering, IGridifyMapper? mapper = null, - bool startWithThenBy = false) + mapper = new GridifyMapper(); + foreach (var field in syntaxTree.Root.Descendants() + .Where(q => q.Kind == SyntaxKind.FieldExpression) + .Cast()) { - if (gridifyOrdering == null) return query; - return string.IsNullOrWhiteSpace(gridifyOrdering.OrderBy) - ? query - : ProcessOrdering(query, gridifyOrdering.OrderBy!, startWithThenBy, mapper); + try + { + mapper.AddMap(field.FieldToken.Text); + } + catch (Exception) + { + if (!mapper.Configuration.IgnoreNotMappedFields) + throw new GridifyMapperException($"Property '{field.FieldToken.Text}' not found."); + } } - /// - /// adds Ordering to the query - /// - /// the original(target) queryable object - /// the ordering fields - /// this is an optional parameter to apply ordering using a custom mapping configuration - /// if you already have an ordering with start with ThenBy, new orderings will add on top of your orders - /// type of target entity - /// returns user query after applying Ordering - public static IQueryable ApplyOrdering(this IQueryable query, string orderBy, IGridifyMapper? mapper = null, - bool startWithThenBy = false) + return mapper; + } + + private static IEnumerable Descendants(this SyntaxNode root) + { + var nodes = new Stack(new[] { root }); + while (nodes.Any()) { - return string.IsNullOrWhiteSpace(orderBy) - ? query - : ProcessOrdering(query, orderBy, startWithThenBy, mapper); + SyntaxNode node = nodes.Pop(); + yield return node; + foreach (var n in node.GetChildren()) nodes.Push(n); } + } + + /// + /// adds Filtering,Ordering And Paging to the query + /// + /// the original(target) queryable object + /// the configuration to apply paging, filtering and ordering + /// this is an optional parameter to apply filtering and ordering using a custom mapping configuration + /// type of target entity + /// returns user query after applying filtering, ordering and paging + public static IQueryable ApplyFilteringOrderingPaging(this IQueryable query, IGridifyQuery? gridifyQuery, + IGridifyMapper? mapper = null) + { + if (gridifyQuery == null) return query; + + query = query.ApplyFiltering(gridifyQuery, mapper); + query = query.ApplyOrdering(gridifyQuery, mapper); + query = query.ApplyPaging(gridifyQuery); + return query; + } + + /// + /// adds Ordering to the query + /// + /// the original(target) queryable object + /// the configuration to apply ordering + /// this is an optional parameter to apply ordering using a custom mapping configuration + /// if you already have an ordering with start with ThenBy, new orderings will add on top of your orders + /// type of target entity + /// returns user query after applying Ordering + public static IQueryable ApplyOrdering(this IQueryable query, IGridifyOrdering? gridifyOrdering, IGridifyMapper? mapper = null, + bool startWithThenBy = false) + { + if (gridifyOrdering == null) return query; + return string.IsNullOrWhiteSpace(gridifyOrdering.OrderBy) + ? query + : ProcessOrdering(query, gridifyOrdering.OrderBy!, startWithThenBy, mapper); + } + + /// + /// adds Ordering to the query + /// + /// the original(target) queryable object + /// the ordering fields + /// this is an optional parameter to apply ordering using a custom mapping configuration + /// if you already have an ordering with start with ThenBy, new orderings will add on top of your orders + /// type of target entity + /// returns user query after applying Ordering + public static IQueryable ApplyOrdering(this IQueryable query, string orderBy, IGridifyMapper? mapper = null, + bool startWithThenBy = false) + { + return string.IsNullOrWhiteSpace(orderBy) + ? query + : ProcessOrdering(query, orderBy, startWithThenBy, mapper); + } + + public static IQueryable ApplySelect(this IQueryable query, string props, IGridifyMapper? mapper = null) + { + if (string.IsNullOrWhiteSpace(props)) + return (IQueryable)query; + + if (mapper is null) + mapper = new GridifyMapper(true); + + var exp = mapper.GetExpression(props); + var result = query.Select(exp); + + + return result; + } - public static IQueryable ApplySelect(this IQueryable query, string props, IGridifyMapper? mapper = null) + /// + /// Validates Filter and/or OrderBy with Mappings + /// + /// gridify query with (Filter or OrderBy) + /// the gridify mapper that you want to use with, this is optional + /// + /// + public static bool IsValid(this IGridifyQuery gridifyQuery, IGridifyMapper? mapper = null) + { + return ((IGridifyFiltering)gridifyQuery).IsValid(mapper) && + ((IGridifyOrdering)gridifyQuery).IsValid(mapper); + } + + public static bool IsValid(this IGridifyFiltering filtering, IGridifyMapper? mapper = null) + { + if (string.IsNullOrWhiteSpace(filtering.Filter)) return true; + try { - if (string.IsNullOrWhiteSpace(props)) - return (IQueryable)query; + var parser = new Parser(filtering.Filter!); + var syntaxTree = parser.Parse(); + if (syntaxTree.Diagnostics.Any()) + return false; - if (mapper is null) - mapper = new GridifyMapper(true); + var fieldExpressions = syntaxTree.Root.Descendants() + .Where(q=> q.Kind is SyntaxKind.FieldExpression) + .Cast().ToList(); - var exp = mapper.GetExpression(props); - var result = query.Select(exp); + mapper ??= new GridifyMapper(true); - return result; + if (fieldExpressions.Any(field => !mapper.HasMap(field.FieldToken.Text))) + return false; } + catch (Exception) + { + return false; + } + + return true; + } - private static IQueryable ProcessOrdering(IQueryable query, string orderings, bool startWithThenBy, IGridifyMapper? mapper) + public static bool IsValid(this IGridifyOrdering ordering, IGridifyMapper? mapper = null) + { + if (string.IsNullOrWhiteSpace(ordering.OrderBy)) return true; + try + { + var orders = ParseOrderings(ordering.OrderBy!).ToList(); + mapper ??= new GridifyMapper(true); + if (orders.Any(order => !mapper.HasMap(order.memberName))) + return false; + } + catch (Exception) { - var isFirst = !startWithThenBy; + return false; + } + + return true; + } + + private static IQueryable ProcessOrdering(IQueryable query, string orderings, bool startWithThenBy, IGridifyMapper? mapper) + { + var isFirst = !startWithThenBy; - var orders = ParseOrderings(orderings).ToList(); + var orders = ParseOrderings(orderings).ToList(); - // build the mapper if it is null - if (mapper is null) + // build the mapper if it is null + if (mapper is null) + { + mapper = new GridifyMapper(); + foreach (var (member, _) in orders) { - mapper = new GridifyMapper(); - foreach (var (member, _) in orders) + try { - try - { - mapper.AddMap(member); - } - catch (Exception e) - { - if (!mapper.Configuration.IgnoreNotMappedFields) - throw new GridifyMapperException($"Mapping '{member}' not found"); - } + mapper.AddMap(member); + } + catch (Exception) + { + if (!mapper.Configuration.IgnoreNotMappedFields) + throw new GridifyMapperException($"Mapping '{member}' not found"); } } + } - foreach (var (member, isAscending) in orders) + foreach (var (member, isAscending) in orders) + { + if (!mapper.HasMap(member)) { - if (!mapper.HasMap(member)) - { - // skip if there is no mappings available - if (mapper.Configuration.IgnoreNotMappedFields) - continue; + // skip if there is no mappings available + if (mapper.Configuration.IgnoreNotMappedFields) + continue; - throw new GridifyMapperException($"Mapping '{member}' not found"); - } - - if (isFirst) - { - query = query.OrderByMember(mapper.GetExpression(member), isAscending); - isFirst = false; - } - else - query = query.ThenByMember(mapper.GetExpression(member), isAscending); + throw new GridifyMapperException($"Mapping '{member}' not found"); } - return query; + if (isFirst) + { + query = query.OrderByMember(mapper.GetExpression(member), isAscending); + isFirst = false; + } + else + query = query.ThenByMember(mapper.GetExpression(member), isAscending); } - private static IEnumerable<(string memberName, bool isAsc)> ParseOrderings(string orderings) + return query; + } + + private static IEnumerable<(string memberName, bool isAsc)> ParseOrderings(string orderings) + { + foreach (var field in orderings.Split(',')) { - foreach (var field in orderings.Split(',')) + var orderingExp = field.Trim(); + if (orderingExp.Contains(" ")) { - var orderingExp = field.Trim(); - if (orderingExp.Contains(" ")) + var spliced = orderingExp.Split(new[] { ' ', '\t' }, StringSplitOptions.RemoveEmptyEntries); + var isAsc = spliced.Last() switch { - var spliced = orderingExp.Split(new[] { ' ', '\t' }, StringSplitOptions.RemoveEmptyEntries); - var isAsc = spliced.Last() switch - { - "desc" => false, - "asc" => true, - _ => throw new GridifyOrderingException("Invalid keyword. expected 'desc' or 'asc'") - }; - yield return (spliced.First(), isAsc); - } - else - yield return (orderingExp, true); + "desc" => false, + "asc" => true, + _ => throw new GridifyOrderingException("Invalid keyword. expected 'desc' or 'asc'") + }; + yield return (spliced.First(), isAsc); } + else + yield return (orderingExp, true); } + } - /// - /// adds Ordering to the query - /// - /// the original(target) queryable object - /// the configuration to apply ordering - /// select group member for ordering - /// // need to be more specific - /// - /// this is an optional parameter to apply ordering using a custom mapping configuration - /// type of target entity - /// type of target property - /// returns user query after applying Ordering - public static IQueryable ApplyOrdering(this IQueryable query, IGridifyOrdering? gridifyOrdering, - Expression> groupOrder, bool isGroupOrderAscending = true, - IGridifyMapper? mapper = null) - { - if (gridifyOrdering == null) return query; - - if (string.IsNullOrWhiteSpace(gridifyOrdering.OrderBy)) - return query; - - query = isGroupOrderAscending - ? query.OrderBy(groupOrder) - : query.OrderByDescending(groupOrder); + /// + /// adds Ordering to the query + /// + /// the original(target) queryable object + /// the configuration to apply ordering + /// select group member for ordering + /// // need to be more specific + /// + /// this is an optional parameter to apply ordering using a custom mapping configuration + /// type of target entity + /// type of target property + /// returns user query after applying Ordering + public static IQueryable ApplyOrdering(this IQueryable query, IGridifyOrdering? gridifyOrdering, + Expression> groupOrder, bool isGroupOrderAscending = true, + IGridifyMapper? mapper = null) + { + if (gridifyOrdering == null) return query; - query = ProcessOrdering(query, gridifyOrdering.OrderBy!, true, mapper); + if (string.IsNullOrWhiteSpace(gridifyOrdering.OrderBy)) return query; - } - public static IQueryable ApplyPaging(this IQueryable query, IGridifyPagination? gridifyPagination) - { - if (gridifyPagination == null) return query; - gridifyPagination = gridifyPagination.FixPagingData(); - return query.Skip((gridifyPagination.Page - 1) * gridifyPagination.PageSize).Take(gridifyPagination.PageSize); - } + query = isGroupOrderAscending + ? query.OrderBy(groupOrder) + : query.OrderByDescending(groupOrder); - public static IQueryable> ApplyPaging(this IQueryable> query, IGridifyPagination? gridifyPagination) - { - if (gridifyPagination == null) return query; - gridifyPagination = gridifyPagination.FixPagingData(); - return query.Skip((gridifyPagination.Page - 1) * gridifyPagination.PageSize).Take(gridifyPagination.PageSize); - } + query = ProcessOrdering(query, gridifyOrdering.OrderBy!, true, mapper); + return query; + } - public static IQueryable ApplyFiltering(this IQueryable query, IGridifyFiltering? gridifyFiltering, IGridifyMapper? mapper = null) - { - if (gridifyFiltering == null) return query; - return string.IsNullOrWhiteSpace(gridifyFiltering.Filter) ? query : ApplyFiltering(query, gridifyFiltering.Filter, mapper); - } + public static IQueryable ApplyPaging(this IQueryable query, int page, int pageSize) + { + return query.Skip((page - 1) * pageSize).Take(pageSize); + } - public static IQueryable ApplyFiltering(this IQueryable query, string? filter, IGridifyMapper? mapper = null) - { - if (string.IsNullOrWhiteSpace(filter)) - return query; + public static IQueryable ApplyPaging(this IQueryable query, IGridifyPagination? gridifyPagination) + { + if (gridifyPagination == null) return query; + gridifyPagination = gridifyPagination.FixPagingData(); + return query.Skip((gridifyPagination.Page - 1) * gridifyPagination.PageSize).Take(gridifyPagination.PageSize); + } - var syntaxTree = SyntaxTree.Parse(filter!); + public static IQueryable> ApplyPaging(this IQueryable> query, IGridifyPagination? gridifyPagination) + { + if (gridifyPagination == null) return query; + gridifyPagination = gridifyPagination.FixPagingData(); + return query.Skip((gridifyPagination.Page - 1) * gridifyPagination.PageSize).Take(gridifyPagination.PageSize); + } - if (syntaxTree.Diagnostics.Any()) - throw new GridifyFilteringException(syntaxTree.Diagnostics.Last()!); + public static IQueryable ApplyFiltering(this IQueryable query, IGridifyFiltering? gridifyFiltering, IGridifyMapper? mapper = null) + { + if (gridifyFiltering == null) return query; + return string.IsNullOrWhiteSpace(gridifyFiltering.Filter) ? query : ApplyFiltering(query, gridifyFiltering.Filter, mapper); + } - mapper = mapper.FixMapper(syntaxTree); + public static IQueryable ApplyFiltering(this IQueryable query, string? filter, IGridifyMapper? mapper = null) + { + if (string.IsNullOrWhiteSpace(filter)) + return query; - var (queryExpression, _) = ExpressionToQueryConvertor.GenerateQuery(syntaxTree.Root, mapper); + var syntaxTree = SyntaxTree.Parse(filter!); - query = query.Where(queryExpression); + if (syntaxTree.Diagnostics.Any()) + throw new GridifyFilteringException(syntaxTree.Diagnostics.Last()!); - return query; - } + mapper = mapper.FixMapper(syntaxTree); - public static IQueryable ApplyFilteringAndOrdering(this IQueryable query, IGridifyQuery? gridifyQuery, - IGridifyMapper? mapper = null) - { - query = query.ApplyFiltering(gridifyQuery, mapper); - query = query.ApplyOrdering(gridifyQuery, mapper); - return query; - } + var (queryExpression, _) = ExpressionToQueryConvertor.GenerateQuery(syntaxTree.Root, mapper); - public static Expression> CreateQuery(this SyntaxTree syntaxTree, IGridifyMapper? mapper = null) - { - mapper = mapper.FixMapper(syntaxTree); - var exp = ExpressionToQueryConvertor.GenerateQuery(syntaxTree.Root, mapper).Expression; - if (exp == null) throw new GridifyQueryException("Invalid SyntaxTree."); - return exp; - } + query = query.Where(queryExpression); - public static IQueryable ApplyOrderingAndPaging(this IQueryable query, IGridifyQuery? gridifyQuery, IGridifyMapper? mapper = null) - { - query = query.ApplyOrdering(gridifyQuery, mapper); - query = query.ApplyPaging(gridifyQuery); - return query; - } + return query; + } - /// - /// gets a query or collection, - /// adds filtering, - /// Get totalItems Count - /// adds ordering and paging - /// return QueryablePaging with TotalItems and an IQueryable Query - /// - /// the original(target) queryable object - /// the configuration to apply paging, filtering and ordering - /// this is an optional parameter to apply filtering and ordering using a custom mapping configuration - /// type of target entity - /// returns a QueryablePaging after applying filtering, ordering and paging - public static QueryablePaging GridifyQueryable(this IQueryable query, IGridifyQuery? gridifyQuery, IGridifyMapper? mapper = null) - { - query = query.ApplyFiltering(gridifyQuery, mapper); - var count = query.Count(); - query = query.ApplyOrdering(gridifyQuery, mapper); - query = query.ApplyPaging(gridifyQuery); - return new QueryablePaging(count, query); - } + public static IQueryable ApplyFilteringAndOrdering(this IQueryable query, IGridifyQuery? gridifyQuery, + IGridifyMapper? mapper = null) + { + query = query.ApplyFiltering(gridifyQuery, mapper); + query = query.ApplyOrdering(gridifyQuery, mapper); + return query; + } - /// - /// gets a query or collection, - /// adds filtering, ordering and paging - /// loads filtered and sorted data - /// return pagination ready result - /// - /// the original(target) queryable object - /// the configuration to apply paging, filtering and ordering - /// this is an optional parameter to apply filtering and ordering using a custom mapping configuration - /// type of target entity - /// returns a loaded Paging after applying filtering, ordering and paging - /// - public static Paging Gridify(this IQueryable query, IGridifyQuery? gridifyQuery, IGridifyMapper? mapper = null) - { - var (count, queryable) = query.GridifyQueryable(gridifyQuery, mapper); - return new Paging(count, queryable.ToList()); - } + public static Expression> CreateQuery(this SyntaxTree syntaxTree, IGridifyMapper? mapper = null) + { + mapper = mapper.FixMapper(syntaxTree); + var exp = ExpressionToQueryConvertor.GenerateQuery(syntaxTree.Root, mapper).Expression; + if (exp == null) throw new GridifyQueryException("Invalid SyntaxTree."); + return exp; + } - /// - /// gets a query or collection, - /// adds filtering, ordering and paging - /// loads filtered and sorted data - /// return pagination ready result - /// - /// the original(target) queryable object - /// the configuration to apply paging, filtering and ordering - /// this is an optional parameter to apply filtering and ordering using a custom mapping configuration - /// type of target entity - /// returns a loaded Paging after applying filtering, ordering and paging - /// - public static Paging Gridify(this IQueryable query, Action queryOption, IGridifyMapper? mapper = null) - { - var gridifyQuery = new GridifyQuery(); - queryOption.Invoke(gridifyQuery); - var (count, queryable) = query.GridifyQueryable(gridifyQuery, mapper); - return new Paging(count, queryable.ToList()); - } + public static IQueryable ApplyOrderingAndPaging(this IQueryable query, IGridifyQuery? gridifyQuery, IGridifyMapper? mapper = null) + { + query = query.ApplyOrdering(gridifyQuery, mapper); + query = query.ApplyPaging(gridifyQuery); + return query; + } - #endregion - - /// - /// Supports sorting of a given member as an expression when type is not known. It solves problem with LINQ to Entities unable to - /// cast different types as 'System.DateTime', 'System.DateTime?' to type 'System.Object'. - /// LINQ to Entities only supports casting Entity Data Model primitive types. - /// - /// entity type - /// query to apply sorting on. - /// the member expression to apply - /// the sort order to apply - /// Query with sorting applied as IOrderedQueryable of type T - private static IOrderedQueryable OrderByMember( - this IQueryable query, - Expression> expression, - bool isSortAsc) - { - if (expression.Body is not UnaryExpression body) return isSortAsc ? query.OrderBy(expression) : query.OrderByDescending(expression); + /// + /// gets a query or collection, + /// adds filtering, + /// Get totalItems Count + /// adds ordering and paging + /// return QueryablePaging with TotalItems and an IQueryable Query + /// + /// the original(target) queryable object + /// the configuration to apply paging, filtering and ordering + /// this is an optional parameter to apply filtering and ordering using a custom mapping configuration + /// type of target entity + /// returns a QueryablePaging after applying filtering, ordering and paging + public static QueryablePaging GridifyQueryable(this IQueryable query, IGridifyQuery? gridifyQuery, IGridifyMapper? mapper = null) + { + query = query.ApplyFiltering(gridifyQuery, mapper); + var count = query.Count(); + query = query.ApplyOrdering(gridifyQuery, mapper); + query = query.ApplyPaging(gridifyQuery); + return new QueryablePaging(count, query); + } - if (body.Operand is MemberExpression memberExpression) - { - return - (IOrderedQueryable) - query.Provider.CreateQuery( - Expression.Call( - typeof(Queryable), - isSortAsc ? "OrderBy" : "OrderByDescending", - new[] { typeof(T), memberExpression.Type }, - query.Expression, - Expression.Lambda(memberExpression, expression.Parameters))); - } + /// + /// gets a query or collection, + /// adds filtering, ordering and paging + /// loads filtered and sorted data + /// return pagination ready result + /// + /// the original(target) queryable object + /// the configuration to apply paging, filtering and ordering + /// this is an optional parameter to apply filtering and ordering using a custom mapping configuration + /// type of target entity + /// returns a loaded Paging after applying filtering, ordering and paging + /// + public static Paging Gridify(this IQueryable query, IGridifyQuery? gridifyQuery, IGridifyMapper? mapper = null) + { + var (count, queryable) = query.GridifyQueryable(gridifyQuery, mapper); + return new Paging(count, queryable.ToList()); + } - return isSortAsc ? query.OrderBy(expression) : query.OrderByDescending(expression); - } + /// + /// gets a query or collection, + /// adds filtering, ordering and paging + /// loads filtered and sorted data + /// return pagination ready result + /// + /// the original(target) queryable object + /// the configuration to apply paging, filtering and ordering + /// this is an optional parameter to apply filtering and ordering using a custom mapping configuration + /// type of target entity + /// returns a loaded Paging after applying filtering, ordering and paging + /// + public static Paging Gridify(this IQueryable query, Action queryOption, IGridifyMapper? mapper = null) + { + var gridifyQuery = new GridifyQuery(); + queryOption.Invoke(gridifyQuery); + var (count, queryable) = query.GridifyQueryable(gridifyQuery, mapper); + return new Paging(count, queryable.ToList()); + } + + #endregion + + /// + /// Supports sorting of a given member as an expression when type is not known. It solves problem with LINQ to Entities unable to + /// cast different types as 'System.DateTime', 'System.DateTime?' to type 'System.Object'. + /// LINQ to Entities only supports casting Entity Data Model primitive types. + /// + /// entity type + /// query to apply sorting on. + /// the member expression to apply + /// the sort order to apply + /// Query with sorting applied as IOrderedQueryable of type T + private static IOrderedQueryable OrderByMember( + this IQueryable query, + Expression> expression, + bool isSortAsc) + { + if (expression.Body is not UnaryExpression body) return isSortAsc ? query.OrderBy(expression) : query.OrderByDescending(expression); - /// - /// Supports sorting of a given member as an expression when type is not known. It solves problem with LINQ to Entities unable to - /// cast different types as 'System.DateTime', 'System.DateTime?' to type 'System.Object'. - /// LINQ to Entities only supports casting Entity Data Model primitive types. - /// - /// entity type - /// query to apply sorting on. - /// the member expression to apply - /// the sort order to apply - /// Query with sorting applied as IOrderedQueryable of type T - public static IOrderedQueryable ThenByMember( - this IQueryable query, - Expression> expression, - bool isSortAsc) + if (body.Operand is MemberExpression memberExpression) { - return ((IOrderedQueryable)query).ThenByMember(expression, isSortAsc); + return + (IOrderedQueryable) + query.Provider.CreateQuery( + Expression.Call( + typeof(Queryable), + isSortAsc ? "OrderBy" : "OrderByDescending", + new[] { typeof(T), memberExpression.Type }, + query.Expression, + Expression.Lambda(memberExpression, expression.Parameters))); } - /// - /// Supports sorting of a given member as an expression when type is not known. It solves problem with LINQ to Entities unable to - /// cast different types as 'System.DateTime', 'System.DateTime?' to type 'System.Object'. - /// LINQ to Entities only supports casting Entity Data Model primitive types. - /// - /// entity type - /// query to apply sorting on. - /// the member expression to apply - /// the sort order to apply - /// Query with sorting applied as IOrderedQueryable of type T - private static IOrderedQueryable ThenByMember( - this IOrderedQueryable query, - Expression> expression, - bool isSortAsc) - { - if (expression.Body is not UnaryExpression body) return isSortAsc ? query.ThenBy(expression) : query.ThenByDescending(expression); - if (body.Operand is MemberExpression memberExpression) - { - return - (IOrderedQueryable) - query.Provider.CreateQuery( - Expression.Call( - typeof(Queryable), - isSortAsc ? "ThenBy" : "ThenByDescending", - new[] { typeof(T), memberExpression.Type }, - query.Expression, - Expression.Lambda(memberExpression, expression.Parameters))); - } + return isSortAsc ? query.OrderBy(expression) : query.OrderByDescending(expression); + } - return isSortAsc ? query.ThenBy(expression) : query.ThenByDescending(expression); + /// + /// Supports sorting of a given member as an expression when type is not known. It solves problem with LINQ to Entities unable to + /// cast different types as 'System.DateTime', 'System.DateTime?' to type 'System.Object'. + /// LINQ to Entities only supports casting Entity Data Model primitive types. + /// + /// entity type + /// query to apply sorting on. + /// the member expression to apply + /// the sort order to apply + /// Query with sorting applied as IOrderedQueryable of type T + public static IOrderedQueryable ThenByMember( + this IQueryable query, + Expression> expression, + bool isSortAsc) + { + return ((IOrderedQueryable)query).ThenByMember(expression, isSortAsc); + } + + /// + /// Supports sorting of a given member as an expression when type is not known. It solves problem with LINQ to Entities unable to + /// cast different types as 'System.DateTime', 'System.DateTime?' to type 'System.Object'. + /// LINQ to Entities only supports casting Entity Data Model primitive types. + /// + /// entity type + /// query to apply sorting on. + /// the member expression to apply + /// the sort order to apply + /// Query with sorting applied as IOrderedQueryable of type T + private static IOrderedQueryable ThenByMember( + this IOrderedQueryable query, + Expression> expression, + bool isSortAsc) + { + if (expression.Body is not UnaryExpression body) return isSortAsc ? query.ThenBy(expression) : query.ThenByDescending(expression); + if (body.Operand is MemberExpression memberExpression) + { + return + (IOrderedQueryable) + query.Provider.CreateQuery( + Expression.Call( + typeof(Queryable), + isSortAsc ? "ThenBy" : "ThenByDescending", + new[] { typeof(T), memberExpression.Type }, + query.Expression, + Expression.Lambda(memberExpression, expression.Parameters))); } + + return isSortAsc ? query.ThenBy(expression) : query.ThenByDescending(expression); } } diff --git a/src/Gridify/GridifyFilteringException.cs b/src/Gridify/GridifyFilteringException.cs index 7d276791..4cd24c37 100644 --- a/src/Gridify/GridifyFilteringException.cs +++ b/src/Gridify/GridifyFilteringException.cs @@ -1,11 +1,10 @@ using System; -namespace Gridify +namespace Gridify; + +public class GridifyFilteringException : Exception { - public class GridifyFilteringException : Exception + public GridifyFilteringException(string message) : base(message) { - public GridifyFilteringException(string message) : base(message) - { - } } } \ No newline at end of file diff --git a/src/Gridify/GridifyMapper.cs b/src/Gridify/GridifyMapper.cs index ebc21974..2dbd6bc0 100644 --- a/src/Gridify/GridifyMapper.cs +++ b/src/Gridify/GridifyMapper.cs @@ -4,178 +4,177 @@ using System.Linq; using System.Linq.Expressions; -namespace Gridify +namespace Gridify; + +public class GridifyMapper : IGridifyMapper { - public class GridifyMapper : IGridifyMapper + public GridifyMapperConfiguration Configuration { get; } + private readonly List> _mappings; + + public GridifyMapper(bool autoGenerateMappings = false) { - public GridifyMapperConfiguration Configuration { get; } - private readonly List> _mappings; + Configuration = new GridifyMapperConfiguration(); + _mappings = new List>(); - public GridifyMapper(bool autoGenerateMappings = false) - { - Configuration = new GridifyMapperConfiguration(); - _mappings = new List>(); + if (autoGenerateMappings) + GenerateMappings(); + } - if (autoGenerateMappings) - GenerateMappings(); - } + public GridifyMapper(GridifyMapperConfiguration configuration, bool autoGenerateMappings = false) + { + Configuration = configuration; + _mappings = new List>(); - public GridifyMapper(GridifyMapperConfiguration configuration, bool autoGenerateMappings = false) - { - Configuration = configuration; - _mappings = new List>(); + if (autoGenerateMappings) + GenerateMappings(); + } - if (autoGenerateMappings) - GenerateMappings(); - } + public GridifyMapper(Action configuration, bool autoGenerateMappings = false) + { + Configuration = new GridifyMapperConfiguration(); + configuration.Invoke(Configuration); + _mappings = new List>(); - public GridifyMapper(Action configuration, bool autoGenerateMappings = false) - { - Configuration = new GridifyMapperConfiguration(); - configuration.Invoke(Configuration); - _mappings = new List>(); + if (autoGenerateMappings) + GenerateMappings(); + } - if (autoGenerateMappings) - GenerateMappings(); - } + public IGridifyMapper AddMap(string from, Func? convertor = null!, bool overrideIfExists = true) + { + if (!overrideIfExists && HasMap(from)) + throw new GridifyMapperException($"Duplicate Key. the '{from}' key already exists"); - public IGridifyMapper AddMap(string from, Func? convertor = null!, bool overrideIfExists = true) + Expression> to; + try { - if (!overrideIfExists && HasMap(from)) - throw new GridifyMapperException($"Duplicate Key. the '{from}' key already exists"); - - Expression> to; - try - { - to = CreateExpression(from); - } - catch (Exception) - { - throw new GridifyMapperException($"Property '{from}' not found."); - } - - RemoveMap(from); - _mappings.Add(new GMap(from, to!, convertor)); - return this; + to = CreateExpression(from); } - - public IGridifyMapper GenerateMappings() + catch (Exception) { - foreach (var item in typeof(T).GetProperties()) - { - // skip classes - if (item.PropertyType.IsClass && item.PropertyType != typeof(string)) - continue; - - var name = char.ToLowerInvariant(item.Name[0]) + item.Name.Substring(1); // camel-case name - _mappings.Add(new GMap(name, CreateExpression(item.Name)!)); - } - - return this; + throw new GridifyMapperException($"Property '{from}' not found."); } - public IGridifyMapper AddMap(string from, Expression> to, Func? convertor = null!, - bool overrideIfExists = true) + RemoveMap(from); + _mappings.Add(new GMap(from, to!, convertor)); + return this; + } + + public IGridifyMapper GenerateMappings() + { + foreach (var item in typeof(T).GetProperties()) { - if (!overrideIfExists && HasMap(from)) - throw new GridifyMapperException($"Duplicate Key. the '{from}' key already exists"); + // skip classes + if (item.PropertyType.IsClass && item.PropertyType != typeof(string)) + continue; - RemoveMap(from); - _mappings.Add(new GMap(from, to, convertor)); - return this; + var name = char.ToLowerInvariant(item.Name[0]) + item.Name.Substring(1); // camel-case name + _mappings.Add(new GMap(name, CreateExpression(item.Name)!)); } - public IGridifyMapper AddMap(string from, Expression> to, Func? convertor = null!, - bool overrideIfExists = true) - { - if (!overrideIfExists && HasMap(from)) - throw new GridifyMapperException($"Duplicate Key. the '{from}' key already exists"); + return this; + } - RemoveMap(from); - _mappings.Add(new GMap(from, to, convertor)); - return this; - } + public IGridifyMapper AddMap(string from, Expression> to, Func? convertor = null!, + bool overrideIfExists = true) + { + if (!overrideIfExists && HasMap(from)) + throw new GridifyMapperException($"Duplicate Key. the '{from}' key already exists"); - public IGridifyMapper AddMap(IGMap gMap, bool overrideIfExists = true) - { - if (!overrideIfExists && HasMap(gMap.From)) - throw new GridifyMapperException($"Duplicate Key. the '{gMap.From}' key already exists"); + RemoveMap(from); + _mappings.Add(new GMap(from, to, convertor)); + return this; + } - RemoveMap(gMap.From); - _mappings.Add(gMap); - return this; - } + public IGridifyMapper AddMap(string from, Expression> to, Func? convertor = null!, + bool overrideIfExists = true) + { + if (!overrideIfExists && HasMap(from)) + throw new GridifyMapperException($"Duplicate Key. the '{from}' key already exists"); - public IGridifyMapper RemoveMap(string from) - { - var map = GetGMap(from); - if (map != null) - _mappings.Remove(map); - return this; - } + RemoveMap(from); + _mappings.Add(new GMap(from, to, convertor)); + return this; + } - public IGridifyMapper RemoveMap(IGMap gMap) - { - _mappings.Remove(gMap); - return this; - } + public IGridifyMapper AddMap(IGMap gMap, bool overrideIfExists = true) + { + if (!overrideIfExists && HasMap(gMap.From)) + throw new GridifyMapperException($"Duplicate Key. the '{gMap.From}' key already exists"); - public bool HasMap(string from) - { - return Configuration.CaseSensitive - ? _mappings.Any(q => q.From == from) - : _mappings.Any(q => from.Equals(q.From, StringComparison.InvariantCultureIgnoreCase)); - } + RemoveMap(gMap.From); + _mappings.Add(gMap); + return this; + } - public IGMap? GetGMap(string from) - { - return Configuration.CaseSensitive - ? _mappings.FirstOrDefault(q => from.Equals(q.From)) - : _mappings.FirstOrDefault(q => from.Equals(q.From, StringComparison.InvariantCultureIgnoreCase)); - } + public IGridifyMapper RemoveMap(string from) + { + var map = GetGMap(from); + if (map != null) + _mappings.Remove(map); + return this; + } - public LambdaExpression GetLambdaExpression(string key) - { - var expression = Configuration.CaseSensitive - ? _mappings.FirstOrDefault(q => key.Equals(q.From))?.To - : _mappings.FirstOrDefault(q => key.Equals(q.From, StringComparison.InvariantCultureIgnoreCase))?.To; - if (expression == null) - throw new GridifyMapperException($"Mapping Key `{key}` not found."); - return expression!; - } + public IGridifyMapper RemoveMap(IGMap gMap) + { + _mappings.Remove(gMap); + return this; + } - public Expression> GetExpression(string key) - { - var expression = Configuration.CaseSensitive - ? _mappings.FirstOrDefault(q => key.Equals(q.From))?.To - : _mappings.FirstOrDefault(q => key.Equals(q.From, StringComparison.InvariantCultureIgnoreCase))?.To; - if (expression == null) - throw new GridifyMapperException($"Mapping Key `{key}` not found."); - return expression as Expression> ?? throw new GridifyMapperException($"Expression fir the `{key}` not found."); - } + public bool HasMap(string from) + { + return Configuration.CaseSensitive + ? _mappings.Any(q => q.From == from) + : _mappings.Any(q => from.Equals(q.From, StringComparison.InvariantCultureIgnoreCase)); + } - public IEnumerable> GetCurrentMaps() - { - return _mappings; - } + public IGMap? GetGMap(string from) + { + return Configuration.CaseSensitive + ? _mappings.FirstOrDefault(q => from.Equals(q.From)) + : _mappings.FirstOrDefault(q => from.Equals(q.From, StringComparison.InvariantCultureIgnoreCase)); + } - /// - /// Converts current mappings to a comma seperated list of map names. - /// eg, field1,field2,field3 - /// - /// a comma seperated string - public override string ToString() => string.Join(",", _mappings.Select(q => q.From)); + public LambdaExpression GetLambdaExpression(string key) + { + var expression = Configuration.CaseSensitive + ? _mappings.FirstOrDefault(q => key.Equals(q.From))?.To + : _mappings.FirstOrDefault(q => key.Equals(q.From, StringComparison.InvariantCultureIgnoreCase))?.To; + if (expression == null) + throw new GridifyMapperException($"Mapping Key `{key}` not found."); + return expression!; + } - internal static Expression> CreateExpression(string from) - { - // x => - var parameter = Expression.Parameter(typeof(T)); - // x.Name - var mapProperty = Expression.Property(parameter, from); - // (object)x.Name - var convertedExpression = Expression.Convert(mapProperty, typeof(object)); - // x => (object)x.Name - return Expression.Lambda>(convertedExpression, parameter); - } + public Expression> GetExpression(string key) + { + var expression = Configuration.CaseSensitive + ? _mappings.FirstOrDefault(q => key.Equals(q.From))?.To + : _mappings.FirstOrDefault(q => key.Equals(q.From, StringComparison.InvariantCultureIgnoreCase))?.To; + if (expression == null) + throw new GridifyMapperException($"Mapping Key `{key}` not found."); + return expression as Expression> ?? throw new GridifyMapperException($"Expression fir the `{key}` not found."); + } + + public IEnumerable> GetCurrentMaps() + { + return _mappings; + } + + /// + /// Converts current mappings to a comma seperated list of map names. + /// eg, field1,field2,field3 + /// + /// a comma seperated string + public override string ToString() => string.Join(",", _mappings.Select(q => q.From)); + + internal static Expression> CreateExpression(string from) + { + // x => + var parameter = Expression.Parameter(typeof(T)); + // x.Name + var mapProperty = Expression.Property(parameter, from); + // (object)x.Name + var convertedExpression = Expression.Convert(mapProperty, typeof(object)); + // x => (object)x.Name + return Expression.Lambda>(convertedExpression, parameter); } -} +} \ No newline at end of file diff --git a/src/Gridify/GridifyMapperConfiguration.cs b/src/Gridify/GridifyMapperConfiguration.cs index e5acb279..2257e939 100644 --- a/src/Gridify/GridifyMapperConfiguration.cs +++ b/src/Gridify/GridifyMapperConfiguration.cs @@ -1,18 +1,17 @@ -namespace Gridify +namespace Gridify; + +public record GridifyMapperConfiguration { - public record GridifyMapperConfiguration - { - public bool CaseSensitive { get; set; } - - /// - /// This option enables the 'null' keyword in filtering operations - /// - public bool AllowNullSearch { get; set; } = true; - /// - /// If true, in filtering and ordering operations, - /// gridify doesn't return any exceptions when a mapping - /// is not defined for the fields - /// - public bool IgnoreNotMappedFields { get; set; } - } -} \ No newline at end of file + public bool CaseSensitive { get; set; } + + /// + /// This option enables the 'null' keyword in filtering operations + /// + public bool AllowNullSearch { get; set; } = true; + /// + /// If true, in filtering and ordering operations, + /// gridify doesn't return any exceptions when a mapping + /// is not defined for the fields + /// + public bool IgnoreNotMappedFields { get; set; } +} diff --git a/src/Gridify/GridifyMapperException.cs b/src/Gridify/GridifyMapperException.cs index 9b68d0e6..3fc590d3 100644 --- a/src/Gridify/GridifyMapperException.cs +++ b/src/Gridify/GridifyMapperException.cs @@ -1,11 +1,10 @@ using System; -namespace Gridify +namespace Gridify; + +public class GridifyMapperException : Exception { - public class GridifyMapperException : Exception - { - public GridifyMapperException(string message) : base(message) - { - } - } + public GridifyMapperException(string message) : base(message) + { + } } \ No newline at end of file diff --git a/src/Gridify/GridifyOrderingException.cs b/src/Gridify/GridifyOrderingException.cs index ea46af20..752c29cf 100644 --- a/src/Gridify/GridifyOrderingException.cs +++ b/src/Gridify/GridifyOrderingException.cs @@ -1,12 +1,11 @@ using System; -namespace Gridify +namespace Gridify; + +public class GridifyOrderingException : Exception { - public class GridifyOrderingException : Exception + public GridifyOrderingException(string message) : base(message) { - public GridifyOrderingException(string message) : base(message) - { - } } } \ No newline at end of file diff --git a/src/Gridify/GridifyQuery.cs b/src/Gridify/GridifyQuery.cs index 92450320..2145271d 100644 --- a/src/Gridify/GridifyQuery.cs +++ b/src/Gridify/GridifyQuery.cs @@ -1,23 +1,20 @@ -namespace Gridify +namespace Gridify; + +public class GridifyQuery : IGridifyQuery { - public class GridifyQuery : IGridifyQuery + public GridifyQuery() { - public GridifyQuery() - { - } - public GridifyQuery(int page, int pageSize, string filter, string? orderBy = null) - { - Page = page; - PageSize = pageSize; - OrderBy = orderBy; - Filter = filter; - } - - public int Page { get; set; } - public int PageSize { get; set; } - public string? OrderBy { get; set; } - public string? Filter { get; set; } + } + public GridifyQuery(int page, int pageSize, string filter, string? orderBy = null) + { + Page = page; + PageSize = pageSize; + OrderBy = orderBy; + Filter = filter; } - + public int Page { get; set; } + public int PageSize { get; set; } + public string? OrderBy { get; set; } + public string? Filter { get; set; } } \ No newline at end of file diff --git a/src/Gridify/GridifyQueryException.cs b/src/Gridify/GridifyQueryException.cs index 45ccb77f..e452067e 100644 --- a/src/Gridify/GridifyQueryException.cs +++ b/src/Gridify/GridifyQueryException.cs @@ -1,11 +1,10 @@ using System; -namespace Gridify +namespace Gridify; + +public class GridifyQueryException : Exception { - public class GridifyQueryException : Exception - { - public GridifyQueryException(string message) : base(message) - { - } - } + public GridifyQueryException(string message) : base(message) + { + } } \ No newline at end of file diff --git a/src/Gridify/IGMap.cs b/src/Gridify/IGMap.cs index 5264550a..90c4d169 100644 --- a/src/Gridify/IGMap.cs +++ b/src/Gridify/IGMap.cs @@ -1,12 +1,11 @@ using System; using System.Linq.Expressions; -namespace Gridify +namespace Gridify; + +public interface IGMap { - public interface IGMap - { - string From { get; set; } - LambdaExpression To { get; set; } - Func? Convertor { get; set; } - } -} + string From { get; set; } + LambdaExpression To { get; set; } + Func? Convertor { get; set; } +} \ No newline at end of file diff --git a/src/Gridify/IGridifyFiltering.cs b/src/Gridify/IGridifyFiltering.cs index 22d12b1a..5e452623 100644 --- a/src/Gridify/IGridifyFiltering.cs +++ b/src/Gridify/IGridifyFiltering.cs @@ -1,7 +1,6 @@ -namespace Gridify +namespace Gridify; + +public interface IGridifyFiltering { - public interface IGridifyFiltering - { - string? Filter { get; set; } - } -} \ No newline at end of file + string? Filter { get; set; } +} diff --git a/src/Gridify/IGridifyMapper.cs b/src/Gridify/IGridifyMapper.cs index 8635f1ec..967d4d70 100644 --- a/src/Gridify/IGridifyMapper.cs +++ b/src/Gridify/IGridifyMapper.cs @@ -2,25 +2,24 @@ using System.Collections.Generic; using System.Linq.Expressions; -namespace Gridify +namespace Gridify; + +public interface IGridifyMapper { - public interface IGridifyMapper - { - IGridifyMapper AddMap(string from, Expression> to, Func? convertor = null, bool overrideIfExists = true); + IGridifyMapper AddMap(string from, Expression> to, Func? convertor = null, bool overrideIfExists = true); - IGridifyMapper AddMap(string from, Expression> to, Func? convertor = null!, - bool overrideIfExists = true); + IGridifyMapper AddMap(string from, Expression> to, Func? convertor = null!, + bool overrideIfExists = true); - IGridifyMapper AddMap(IGMap gMap, bool overrideIfExists = true); - IGridifyMapper AddMap(string from, Func? convertor = null!,bool overrideIfExists = true); - IGridifyMapper GenerateMappings(); - IGridifyMapper RemoveMap(string propertyName); - IGridifyMapper RemoveMap(IGMap gMap); - LambdaExpression GetLambdaExpression(string from); - Expression> GetExpression(string key); - IGMap? GetGMap(string from); - bool HasMap(string key); - public GridifyMapperConfiguration Configuration { get; } - IEnumerable> GetCurrentMaps(); - } -} + IGridifyMapper AddMap(IGMap gMap, bool overrideIfExists = true); + IGridifyMapper AddMap(string from, Func? convertor = null!,bool overrideIfExists = true); + IGridifyMapper GenerateMappings(); + IGridifyMapper RemoveMap(string propertyName); + IGridifyMapper RemoveMap(IGMap gMap); + LambdaExpression GetLambdaExpression(string from); + Expression> GetExpression(string key); + IGMap? GetGMap(string from); + bool HasMap(string key); + public GridifyMapperConfiguration Configuration { get; } + IEnumerable> GetCurrentMaps(); +} \ No newline at end of file diff --git a/src/Gridify/IGridifyOrdering.cs b/src/Gridify/IGridifyOrdering.cs index d28c4b30..204d4545 100644 --- a/src/Gridify/IGridifyOrdering.cs +++ b/src/Gridify/IGridifyOrdering.cs @@ -1,7 +1,6 @@ -namespace Gridify +namespace Gridify; + +public interface IGridifyOrdering { - public interface IGridifyOrdering - { - string? OrderBy { get; set; } - } + string? OrderBy { get; set; } } \ No newline at end of file diff --git a/src/Gridify/IGridifyPagination.cs b/src/Gridify/IGridifyPagination.cs index b37613f4..a3f9f589 100644 --- a/src/Gridify/IGridifyPagination.cs +++ b/src/Gridify/IGridifyPagination.cs @@ -1,8 +1,7 @@ -namespace Gridify +namespace Gridify; + +public interface IGridifyPagination { - public interface IGridifyPagination - { - int Page { get; set; } - int PageSize { get; set; } - } + int Page { get; set; } + int PageSize { get; set; } } \ No newline at end of file diff --git a/src/Gridify/IGridifyQuery.cs b/src/Gridify/IGridifyQuery.cs index cf844726..8a0e22b5 100644 --- a/src/Gridify/IGridifyQuery.cs +++ b/src/Gridify/IGridifyQuery.cs @@ -1,6 +1,5 @@ -namespace Gridify +namespace Gridify; + +public interface IGridifyQuery : IGridifyPagination, IGridifyFiltering, IGridifyOrdering { - public interface IGridifyQuery : IGridifyPagination, IGridifyFiltering, IGridifyOrdering - { - } } \ No newline at end of file diff --git a/src/Gridify/IQueryBuilder.cs b/src/Gridify/IQueryBuilder.cs index 467925aa..94fdf308 100644 --- a/src/Gridify/IQueryBuilder.cs +++ b/src/Gridify/IQueryBuilder.cs @@ -3,222 +3,229 @@ using System.Linq; using System.Linq.Expressions; -namespace Gridify +namespace Gridify; + +public interface IQueryBuilder { - public interface IQueryBuilder - { - /// - /// Using this method you can add a custom gridify mapper that will be used to map - /// your provided string condition to a lambda expression. - /// also when you use this method for the second time, it will replace the previous one. - /// - /// - /// returns IQueryBuilder - IQueryBuilder UseCustomMapper(IGridifyMapper mapper); - - /// - /// Using this method the default gridify mapper has no predefined mappings and - /// you need to manually add your maps to the mapper using AddMap method. - /// mapper will be used to convert your provided string conditions to a lambda expression. - /// also when you use this method, previous mapper will be replaced, - /// so make sure to use this before AddMap method. - /// - /// optional mapper configuration - /// returns IQueryBuilder - IQueryBuilder UseEmptyMapper(GridifyMapperConfiguration mapperConfiguration); - - /// - IQueryBuilder UseEmptyMapper(Action mapperConfiguration); - - /// - /// Using this method you can add gridify supported string base filtering statements - /// Each added condition can be use to evaluate a context, also all conditions will be - /// ANDed together for filtering. - /// - /// (Name=John,Age>10) - /// string based filtering - /// returns IQueryBuilder - IQueryBuilder AddCondition(string condition); - - /// - /// Using this method you can use GridifyQuery to add only filtering part - /// - /// Accepts IGridifyFiltering so we can pass GridifyQuery object - /// returns IQueryBuilder - IQueryBuilder AddCondition(IGridifyFiltering condition); - - /// - /// Using this method you can use GridifyQuery object to add filtering and sorting and configure paging - /// - /// Accept IGridifyQuery so we can pass GridifyQuery object - /// returns IQueryBuilder - IQueryBuilder AddQuery(IGridifyQuery gridifyQuery); - - IQueryBuilder AddOrderBy(string orderBy); - IQueryBuilder ConfigurePaging(int page, int pageSize); - IQueryBuilder ConfigureDefaultMapper(GridifyMapperConfiguration mapperConfiguration); - IQueryBuilder ConfigureDefaultMapper(Action mapperConfiguration); - IQueryBuilder AddMap(IGMap map, bool overwrite = true); - IQueryBuilder AddMap(string from, Expression> to, Func? convertor = null, bool overwrite = true); - IQueryBuilder RemoveMap(IGMap map); - Expression> BuildFilteringExpression(); - - /// - /// Creates an evaluator delegate that can be use to evaluate an queryable context - /// - /// A delegate as type , bool>]]> - Func, bool> BuildEvaluator(); - - /// - /// Creates an compiled evaluator delegate that can be use to evaluate an enumerable collection - /// - /// A delegate as type , bool>]]> - Func, bool> BuildCompiledEvaluator(); - - /// - /// Directly Evaluate a queryable context to check if all conditions are valid or not - /// - /// - /// - bool Evaluate(IQueryable query); - - /// - /// Directly Evaluate a collection to check if all conditions are valid or not - /// - /// - /// - bool Evaluate(IEnumerable collection); - - /// - /// Returns a delegate that can be used to apply the filtering, ordering and paging to a queryable. - /// - /// - /// - /// var func = builder.Build(); - /// var query = func(queryableContext); - /// - /// - /// A delegate as type , IQueryable>]]> - Func, IQueryable> Build(); - - /// - /// Returns a delegate that can be used to apply the filtering, ordering and paging to a collection. - /// also, Internally it compiles the expressions to increase performance. - /// - /// - /// - /// var func = builder.BuildCompiled(); - /// var result = func(enumerableCollection); - /// - /// - /// A delegate as type , IEnumerable>]]> - Func, IEnumerable> BuildCompiled(); - - /// - /// Directly applies the filtering, ordering and paging to a queryable. - /// - /// - /// - /// var query = builder.Build(queryableContext); - /// - /// - /// ]]> - IQueryable Build(IQueryable context); - - /// - /// Directly applies the filtering, ordering and paging to a enumerable collection. - /// - /// - /// - /// var result = builder.Build(enumerableCollection); - /// - /// - /// ]]> - IEnumerable Build(IEnumerable collection); - - /// - /// Directly applies the filtering, ordering and paging to a enumerable collection. - /// also returns the total count of the collection. - /// - /// - /// - /// var pagingResult = builder.BuildWithPaging(enumerableCollection); - /// // or - /// var (count, result) = builder.BuildWithPaging(enumerableCollection); - /// - /// - /// ]]> - Paging BuildWithPaging(IEnumerable collection); - - /// - /// Directly applies the filtering, ordering and paging to a queryable. - /// also load the data and returns the total count of the records. - /// - /// - /// - /// var pagingResult = builder.BuildWithPaging(queryableContext); - /// // or - /// var (count, result) = builder.BuildWithPaging(queryableContext); - /// - /// - /// ]]> - Paging BuildWithPaging(IQueryable collection); - - /// - /// Directly applies the filtering, ordering and paging to a queryable. - /// also returns the total count of the records. - /// - /// - /// - /// var queryablePaging = builder.BuildWithQueryablePaging(queryableContext); - /// // or - /// var (count, query) = builder.BuildWithQueryablePaging(queryableContext); - /// - /// - /// ]]> - QueryablePaging BuildWithQueryablePaging(IQueryable collection); - - /// - /// Returns a delegate that can be used to apply the filtering, ordering and paging to a queryable. - /// also returns the total count of the records. - /// - /// - /// - /// var func = builder.BuildWithQueryablePaging(); - /// var query = func(queryableContext); - /// - /// - /// A delegate as type ,QueryablePaging>]]> - Func,QueryablePaging> BuildWithQueryablePaging(); - - /// - /// Returns a delegate that can be used to apply the filtering, ordering and paging to a queryable. - /// also load the data and returns the total count of the records. - /// - /// - /// - /// var func = builder.BuildWithPaging(); - /// var pagingQuery = func(queryableContext); - /// // or - /// var (count, query) = func(queryableContext); - /// - /// - /// ,Paging> ]]> - Func,Paging> BuildWithPaging(); - - /// - /// Returns a delegate that can be used to apply the filtering, ordering and paging to a enumerable collection. - /// also load the data and returns the total count of the records. - /// - /// - /// - /// var func = builder.BuildWithPagingAsEnumerable(); - /// var pagingResult = func(enumerableCollection); - /// // or - /// var (count, result) = func(enumerableCollection); - /// - /// - /// ,Paging> ]]> - Func,Paging> BuildWithPagingCompiled(); - } + /// + /// Using this method you can add a custom gridify mapper that will be used to map + /// your provided string condition to a lambda expression. + /// also when you use this method for the second time, it will replace the previous one. + /// + /// + /// returns IQueryBuilder + IQueryBuilder UseCustomMapper(IGridifyMapper mapper); + + /// + /// Using this method the default gridify mapper has no predefined mappings and + /// you need to manually add your maps to the mapper using AddMap method. + /// mapper will be used to convert your provided string conditions to a lambda expression. + /// also when you use this method, previous mapper will be replaced, + /// so make sure to use this before AddMap method. + /// + /// optional mapper configuration + /// returns IQueryBuilder + IQueryBuilder UseEmptyMapper(GridifyMapperConfiguration mapperConfiguration); + + /// + IQueryBuilder UseEmptyMapper(Action mapperConfiguration); + + /// + /// Using this method you can add gridify supported string base filtering statements + /// Each added condition can be use to evaluate a context, also all conditions will be + /// ANDed together for filtering. + /// + /// (Name=John,Age>10) + /// string based filtering + /// returns IQueryBuilder + IQueryBuilder AddCondition(string condition); + + /// + /// Using this method you can use GridifyQuery to add only filtering part + /// + /// Accepts IGridifyFiltering so we can pass GridifyQuery object + /// returns IQueryBuilder + IQueryBuilder AddCondition(IGridifyFiltering condition); + + /// + /// Using this method you can use GridifyQuery object to add filtering and sorting and configure paging + /// + /// Accept IGridifyQuery so we can pass GridifyQuery object + /// returns IQueryBuilder + IQueryBuilder AddQuery(IGridifyQuery gridifyQuery); + + IQueryBuilder AddOrderBy(string orderBy); + IQueryBuilder ConfigurePaging(int page, int pageSize); + IQueryBuilder ConfigureDefaultMapper(GridifyMapperConfiguration mapperConfiguration); + IQueryBuilder ConfigureDefaultMapper(Action mapperConfiguration); + IQueryBuilder AddMap(IGMap map, bool overwrite = true); + IQueryBuilder AddMap(string from, Expression> to, Func? convertor = null, bool overwrite = true); + IQueryBuilder RemoveMap(IGMap map); + + /// + /// Validate conditions, orderings, mappings + /// make sure to use ir after your configurations and before the Build methods + /// + /// boolean true/false + bool IsValid(); + + Expression> BuildFilteringExpression(); + + /// + /// Creates an evaluator delegate that can be use to evaluate an queryable context + /// + /// A delegate as type , bool>]]> + Func, bool> BuildEvaluator(); + + /// + /// Creates an compiled evaluator delegate that can be use to evaluate an enumerable collection + /// + /// A delegate as type , bool>]]> + Func, bool> BuildCompiledEvaluator(); + + /// + /// Directly Evaluate a queryable context to check if all conditions are valid or not + /// + /// + /// + bool Evaluate(IQueryable query); + + /// + /// Directly Evaluate a collection to check if all conditions are valid or not + /// + /// + /// + bool Evaluate(IEnumerable collection); + + /// + /// Returns a delegate that can be used to apply the filtering, ordering and paging to a queryable. + /// + /// + /// + /// var func = builder.Build(); + /// var query = func(queryableContext); + /// + /// + /// A delegate as type , IQueryable>]]> + Func, IQueryable> Build(); + + /// + /// Returns a delegate that can be used to apply the filtering, ordering and paging to a collection. + /// also, Internally it compiles the expressions to increase performance. + /// + /// + /// + /// var func = builder.BuildCompiled(); + /// var result = func(enumerableCollection); + /// + /// + /// A delegate as type , IEnumerable>]]> + Func, IEnumerable> BuildCompiled(); + + /// + /// Directly applies the filtering, ordering and paging to a queryable. + /// + /// + /// + /// var query = builder.Build(queryableContext); + /// + /// + /// ]]> + IQueryable Build(IQueryable context); + + /// + /// Directly applies the filtering, ordering and paging to a enumerable collection. + /// + /// + /// + /// var result = builder.Build(enumerableCollection); + /// + /// + /// ]]> + IEnumerable Build(IEnumerable collection); + + /// + /// Directly applies the filtering, ordering and paging to a enumerable collection. + /// also returns the total count of the collection. + /// + /// + /// + /// var pagingResult = builder.BuildWithPaging(enumerableCollection); + /// // or + /// var (count, result) = builder.BuildWithPaging(enumerableCollection); + /// + /// + /// ]]> + Paging BuildWithPaging(IEnumerable collection); + + /// + /// Directly applies the filtering, ordering and paging to a queryable. + /// also load the data and returns the total count of the records. + /// + /// + /// + /// var pagingResult = builder.BuildWithPaging(queryableContext); + /// // or + /// var (count, result) = builder.BuildWithPaging(queryableContext); + /// + /// + /// ]]> + Paging BuildWithPaging(IQueryable collection); + + /// + /// Directly applies the filtering, ordering and paging to a queryable. + /// also returns the total count of the records. + /// + /// + /// + /// var queryablePaging = builder.BuildWithQueryablePaging(queryableContext); + /// // or + /// var (count, query) = builder.BuildWithQueryablePaging(queryableContext); + /// + /// + /// ]]> + QueryablePaging BuildWithQueryablePaging(IQueryable collection); + + /// + /// Returns a delegate that can be used to apply the filtering, ordering and paging to a queryable. + /// also returns the total count of the records. + /// + /// + /// + /// var func = builder.BuildWithQueryablePaging(); + /// var query = func(queryableContext); + /// + /// + /// A delegate as type ,QueryablePaging>]]> + Func,QueryablePaging> BuildWithQueryablePaging(); + + /// + /// Returns a delegate that can be used to apply the filtering, ordering and paging to a queryable. + /// also load the data and returns the total count of the records. + /// + /// + /// + /// var func = builder.BuildWithPaging(); + /// var pagingQuery = func(queryableContext); + /// // or + /// var (count, query) = func(queryableContext); + /// + /// + /// ,Paging> ]]> + Func,Paging> BuildWithPaging(); + + /// + /// Returns a delegate that can be used to apply the filtering, ordering and paging to a enumerable collection. + /// also load the data and returns the total count of the records. + /// + /// + /// + /// var func = builder.BuildWithPagingAsEnumerable(); + /// var pagingResult = func(enumerableCollection); + /// // or + /// var (count, result) = func(enumerableCollection); + /// + /// + /// ,Paging> ]]> + Func,Paging> BuildWithPagingCompiled(); } diff --git a/src/Gridify/Paging.cs b/src/Gridify/Paging.cs index 758907bd..3f307318 100644 --- a/src/Gridify/Paging.cs +++ b/src/Gridify/Paging.cs @@ -1,27 +1,26 @@ using System.Collections.Generic; using System.Linq; -namespace Gridify +namespace Gridify; + +public class Paging { - public class Paging - { - public Paging() - { - Data = Enumerable.Empty(); - } - public void Deconstruct(out int count, out IEnumerable data) - { - count = Count; - data = Data; - } + public Paging() + { + Data = Enumerable.Empty(); + } + public void Deconstruct(out int count, out IEnumerable data) + { + count = Count; + data = Data; + } - public Paging(int count,IEnumerable data) - { - Count = count; - Data = data; - } - public int Count { get; set; } - public IEnumerable Data { get; set; } + public Paging(int count,IEnumerable data) + { + Count = count; + Data = data; } -} + public int Count { get; set; } + public IEnumerable Data { get; set; } +} \ No newline at end of file diff --git a/src/Gridify/PredicateBuilder.cs b/src/Gridify/PredicateBuilder.cs index 98df60e0..a4f20b22 100644 --- a/src/Gridify/PredicateBuilder.cs +++ b/src/Gridify/PredicateBuilder.cs @@ -2,60 +2,59 @@ using System.Linq.Expressions; using Gridify.Syntax; -namespace Gridify +namespace Gridify; + +public static partial class PredicateBuilder { - public static partial class PredicateBuilder + public static Expression> Or (this Expression> expr1, Expression> expr2) { - public static Expression> Or (this Expression> expr1, Expression> expr2) - { - var parameter = Expression.Parameter(typeof (T)); + var parameter = Expression.Parameter(typeof (T)); - var leftVisitor = new ReplaceExpressionVisitor(expr1.Parameters[0], parameter); - var left = leftVisitor.Visit(expr1.Body); + var leftVisitor = new ReplaceExpressionVisitor(expr1.Parameters[0], parameter); + var left = leftVisitor.Visit(expr1.Body); - var rightVisitor = new ReplaceExpressionVisitor(expr2.Parameters[0], parameter); - var right = rightVisitor.Visit(expr2.Body); + var rightVisitor = new ReplaceExpressionVisitor(expr2.Parameters[0], parameter); + var right = rightVisitor.Visit(expr2.Body); - return Expression.Lambda>(Expression.OrElse(left!, right!), parameter); - } + return Expression.Lambda>(Expression.OrElse(left!, right!), parameter); + } - public static Expression> And (this Expression> expr1, Expression> expr2) - { - var parameter = Expression.Parameter(typeof (T)); + public static Expression> And (this Expression> expr1, Expression> expr2) + { + var parameter = Expression.Parameter(typeof (T)); - var leftVisitor = new ReplaceExpressionVisitor(expr1.Parameters[0], parameter); - var left = leftVisitor.Visit(expr1.Body); + var leftVisitor = new ReplaceExpressionVisitor(expr1.Parameters[0], parameter); + var left = leftVisitor.Visit(expr1.Body); - var rightVisitor = new ReplaceExpressionVisitor(expr2.Parameters[0], parameter); - var right = rightVisitor.Visit(expr2.Body); + var rightVisitor = new ReplaceExpressionVisitor(expr2.Parameters[0], parameter); + var right = rightVisitor.Visit(expr2.Body); - return Expression.Lambda>(Expression.AndAlso(left!, right!), parameter); - } + return Expression.Lambda>(Expression.AndAlso(left!, right!), parameter); + } - public static LambdaExpression And (this LambdaExpression expr1, LambdaExpression expr2) - { - var parameter = Expression.Parameter(expr1.Parameters[0].Type); + public static LambdaExpression And (this LambdaExpression expr1, LambdaExpression expr2) + { + var parameter = Expression.Parameter(expr1.Parameters[0].Type); - var leftVisitor = new ReplaceExpressionVisitor(expr1.Parameters[0], parameter); - var left = leftVisitor.Visit(expr1.Body); + var leftVisitor = new ReplaceExpressionVisitor(expr1.Parameters[0], parameter); + var left = leftVisitor.Visit(expr1.Body); - var rightVisitor = new ReplaceExpressionVisitor(expr2.Parameters[0], parameter); - var right = rightVisitor.Visit(expr2.Body); + var rightVisitor = new ReplaceExpressionVisitor(expr2.Parameters[0], parameter); + var right = rightVisitor.Visit(expr2.Body); - return Expression.Lambda(Expression.AndAlso(left!, right!), parameter); - } + return Expression.Lambda(Expression.AndAlso(left!, right!), parameter); + } - public static LambdaExpression Or (this LambdaExpression expr1, LambdaExpression expr2) - { - var parameter = Expression.Parameter(expr1.Parameters[0].Type); + public static LambdaExpression Or (this LambdaExpression expr1, LambdaExpression expr2) + { + var parameter = Expression.Parameter(expr1.Parameters[0].Type); - var leftVisitor = new ReplaceExpressionVisitor(expr1.Parameters[0], parameter); - var left = leftVisitor.Visit(expr1.Body); + var leftVisitor = new ReplaceExpressionVisitor(expr1.Parameters[0], parameter); + var left = leftVisitor.Visit(expr1.Body); - var rightVisitor = new ReplaceExpressionVisitor(expr2.Parameters[0], parameter); - var right = rightVisitor.Visit(expr2.Body); + var rightVisitor = new ReplaceExpressionVisitor(expr2.Parameters[0], parameter); + var right = rightVisitor.Visit(expr2.Body); - return Expression.Lambda(Expression.OrElse(left!, right!), parameter); - } + return Expression.Lambda(Expression.OrElse(left!, right!), parameter); } -} +} \ No newline at end of file diff --git a/src/Gridify/QueryBuilder.cs b/src/Gridify/QueryBuilder.cs index 6c212139..3b93a89a 100644 --- a/src/Gridify/QueryBuilder.cs +++ b/src/Gridify/QueryBuilder.cs @@ -4,304 +4,332 @@ using System.Linq.Expressions; using Gridify.Syntax; -namespace Gridify +namespace Gridify; + +public class QueryBuilder : IQueryBuilder { - public class QueryBuilder : IQueryBuilder - { - private readonly List _conditions = new(); - private IGridifyMapper? _mapper; - private string _orderBy = string.Empty; - private (int page, int pageSize)? _paging; + private readonly List _conditionList = new(); + private IGridifyMapper? _mapper; + private string _orderBy = string.Empty; + private (int page, int pageSize)? _paging; - /// - public IQueryBuilder UseCustomMapper(IGridifyMapper mapper) - { - _mapper = mapper; - return this; - } + /// + public IQueryBuilder UseCustomMapper(IGridifyMapper mapper) + { + _mapper = mapper; + return this; + } - /// - public IQueryBuilder UseEmptyMapper(GridifyMapperConfiguration? mapperConfiguration = null) - { - _mapper = mapperConfiguration != null ? new GridifyMapper(mapperConfiguration) : new GridifyMapper(); - return this; - } + /// + public IQueryBuilder UseEmptyMapper(GridifyMapperConfiguration? mapperConfiguration = null) + { + _mapper = mapperConfiguration != null ? new GridifyMapper(mapperConfiguration) : new GridifyMapper(); + return this; + } - /// - public IQueryBuilder UseEmptyMapper(Action mapperConfiguration) - { - var mapperConfigurationInstance = new GridifyMapperConfiguration(); - mapperConfiguration(mapperConfigurationInstance); - return UseEmptyMapper(mapperConfigurationInstance); - } + /// + public IQueryBuilder UseEmptyMapper(Action mapperConfiguration) + { + var mapperConfigurationInstance = new GridifyMapperConfiguration(); + mapperConfiguration(mapperConfigurationInstance); + return UseEmptyMapper(mapperConfigurationInstance); + } - /// - public IQueryBuilder AddCondition(string condition) - { - _conditions.Add(ConvertConditionToExpression(condition)); - return this; - } + /// + public IQueryBuilder AddCondition(string condition) + { + _conditionList.Add(condition); + return this; + } - /// - public IQueryBuilder AddCondition(IGridifyFiltering condition) - { - if (condition.Filter != null) - _conditions.Add(ConvertConditionToExpression(condition.Filter)); - return this; - } + /// + public IQueryBuilder AddCondition(IGridifyFiltering condition) + { + if (condition.Filter != null) + _conditionList.Add(condition.Filter); + return this; + } - /// - public IQueryBuilder AddQuery(IGridifyQuery gridifyQuery) - { - if (gridifyQuery.Filter != null) - AddCondition(gridifyQuery.Filter); + /// + public IQueryBuilder AddQuery(IGridifyQuery gridifyQuery) + { + if (gridifyQuery.Filter != null) + AddCondition(gridifyQuery.Filter); - if (!string.IsNullOrEmpty(gridifyQuery.OrderBy)) - AddOrderBy(gridifyQuery.OrderBy!); + if (!string.IsNullOrEmpty(gridifyQuery.OrderBy)) + AddOrderBy(gridifyQuery.OrderBy!); - if (gridifyQuery.PageSize == 0) gridifyQuery.PageSize = GridifyExtensions.DefaultPageSize; - ConfigurePaging(gridifyQuery.Page, gridifyQuery.PageSize); + if (gridifyQuery.PageSize == 0) gridifyQuery.PageSize = GridifyExtensions.DefaultPageSize; + ConfigurePaging(gridifyQuery.Page, gridifyQuery.PageSize); - return this; - } + return this; + } - /// - public IQueryBuilder AddOrderBy(string orderBy) - { - _orderBy = string.IsNullOrEmpty(_orderBy) ? orderBy : $"{_orderBy}, {orderBy}"; - return this; - } + /// + public IQueryBuilder AddOrderBy(string orderBy) + { + _orderBy = string.IsNullOrEmpty(_orderBy) ? orderBy : $"{_orderBy}, {orderBy}"; + return this; + } - /// - public IQueryBuilder ConfigurePaging(int page, int pageSize) - { - _paging = (page, pageSize); - return this; - } + /// + public IQueryBuilder ConfigurePaging(int page, int pageSize) + { + _paging = (page, pageSize); + return this; + } - /// - public IQueryBuilder ConfigureDefaultMapper(GridifyMapperConfiguration mapperConfiguration) + /// + public IQueryBuilder ConfigureDefaultMapper(GridifyMapperConfiguration mapperConfiguration) + { + if (_mapper != null && _mapper.GetCurrentMaps().Any()) { - if (_mapper != null && _mapper.GetCurrentMaps().Any()) - { - var tempMapper = new GridifyMapper(mapperConfiguration, true); - _mapper.GetCurrentMaps().ToList().ForEach(map => tempMapper.AddMap(map)); - _mapper = tempMapper; - return this; - } - - _mapper = new GridifyMapper(mapperConfiguration, true); + var tempMapper = new GridifyMapper(mapperConfiguration, true); + _mapper.GetCurrentMaps().ToList().ForEach(map => tempMapper.AddMap(map)); + _mapper = tempMapper; return this; } - /// - public IQueryBuilder ConfigureDefaultMapper(Action mapperConfiguration) - { - var mapperConfigurationInstance = new GridifyMapperConfiguration(); - mapperConfiguration(mapperConfigurationInstance); - return ConfigureDefaultMapper(mapperConfigurationInstance); - } - - /// - public IQueryBuilder AddMap(IGMap map, bool overwrite = true) - { - _mapper ??= new GridifyMapper(true); - _mapper.AddMap(map, overwrite); - return this; - } + _mapper = new GridifyMapper(mapperConfiguration, true); + return this; + } - /// - public IQueryBuilder AddMap(string from, Expression> to, Func? convertor = null, bool overwrite = true) - { - _mapper ??= new GridifyMapper(true); - _mapper.AddMap(from, to, convertor, overwrite); - return this; - } + /// + public IQueryBuilder ConfigureDefaultMapper(Action mapperConfiguration) + { + var mapperConfigurationInstance = new GridifyMapperConfiguration(); + mapperConfiguration(mapperConfigurationInstance); + return ConfigureDefaultMapper(mapperConfigurationInstance); + } - /// - public IQueryBuilder RemoveMap(IGMap map) - { - _mapper ??= new GridifyMapper(true); - _mapper.RemoveMap(map); - return this; - } + /// + public IQueryBuilder AddMap(IGMap map, bool overwrite = true) + { + _mapper ??= new GridifyMapper(true); + _mapper.AddMap(map, overwrite); + return this; + } - /// - public Expression> BuildFilteringExpression() - { - if (_conditions.Count == 0) - return _ => true; + /// + public IQueryBuilder AddMap(string from, Expression> to, Func? convertor = null, bool overwrite = true) + { + _mapper ??= new GridifyMapper(true); + _mapper.AddMap(from, to, convertor, overwrite); + return this; + } - return (_conditions.Aggregate(null, (LambdaExpression? x, LambdaExpression y) - => x is null ? y : x.And(y)) as Expression>)!; - } + /// + public IQueryBuilder RemoveMap(IGMap map) + { + _mapper ??= new GridifyMapper(true); + _mapper.RemoveMap(map); + return this; + } - /// - public Func, bool> BuildEvaluator() + /// + public bool IsValid() + { + var isValid = true; + _mapper ??= new GridifyMapper(true); + try { - return collection => + if (_conditionList.Count > 0) { - return _conditions.Count == 0 || - _conditions.Aggregate(true, (current, expression) => - current & collection.Any((expression as Expression>)!)); - }; - } + var gqList = _conditionList.Select(q => new GridifyQuery() { Filter = q }).Cast(); + isValid = isValid && gqList.All(q => q.IsValid(_mapper)); + } - /// - public Func, bool> BuildCompiledEvaluator() - { - var compiledCond = _conditions.Select(q => q.Compile() as Func).ToList(); - var length = _conditions.Count; - return collection => + if (!string.IsNullOrWhiteSpace(_orderBy)) { - return length == 0 || - compiledCond.Aggregate(true, (current, expression) - => current && collection.Any(expression!)); - }; + IGridifyOrdering gq = new GridifyQuery() { OrderBy = _orderBy }; + isValid = isValid && gq.IsValid(_mapper); + } } - - /// - public bool Evaluate(IQueryable query) + catch (Exception) { - return BuildEvaluator()(query); + return false; } + return isValid; + } - /// - public bool Evaluate(IEnumerable collection) - { - return BuildCompiledEvaluator()(collection); - } + /// + public Expression> BuildFilteringExpression() + { + if (_conditionList.Count == 0) + return _ => true; - /// - public IQueryable Build(IQueryable context) + var _conditions = _conditionList.Select(ConvertConditionToExpression).ToList(); + return (_conditions.Aggregate(null, (LambdaExpression? x, LambdaExpression y) + => x is null ? y : x.And(y)) as Expression>)!; + } + + /// + public Func, bool> BuildEvaluator() + { + var _conditions = _conditionList.Select(ConvertConditionToExpression).ToList(); + return collection => { - var query = context; + return _conditions.Count == 0 || + _conditions.Aggregate(true, (current, expression) => + current & collection.Any(expression)); + }; + } - if (_conditions.Count > 0) - query = query.Where(BuildFilteringExpression()); + /// + public Func, bool> BuildCompiledEvaluator() + { + var _conditions = _conditionList.Select(ConvertConditionToExpression).ToList(); + var compiledCond = _conditions.Select(q => q.Compile()).ToList(); + var length = _conditions.Count; + return collection => + { + return length == 0 || + compiledCond.Aggregate(true, (current, expression) + => current && collection.Any(expression!)); + }; + } - if (!string.IsNullOrEmpty(_orderBy)) - query = query.ApplyOrdering(_orderBy); + /// + public bool Evaluate(IQueryable query) + { + return BuildEvaluator()(query); + } - if (_paging.HasValue) - query = query.Skip(_paging.Value.page * _paging.Value.pageSize).Take(_paging.Value.pageSize); + /// + public bool Evaluate(IEnumerable collection) + { + return BuildCompiledEvaluator()(collection); + } - return query; - } + /// + public IQueryable Build(IQueryable context) + { + var query = context; - /// - public Func, IQueryable> Build() - { - return Build; - } + if (_conditionList.Count > 0) + query = query.Where(BuildFilteringExpression()); - /// - public Func, IEnumerable> BuildCompiled() - { - var compiled = BuildFilteringExpression().Compile(); - return collection => - { - if (_conditions.Count > 0) - collection = collection.Where(compiled); + if (!string.IsNullOrEmpty(_orderBy)) + query = query.ApplyOrdering(_orderBy); - if (!string.IsNullOrEmpty(_orderBy)) // TODO: this also should be compiled - collection = collection.AsQueryable().ApplyOrdering(_orderBy); + if (_paging.HasValue) + query = query.Skip(_paging.Value.page * _paging.Value.pageSize).Take(_paging.Value.pageSize); - if (_paging.HasValue) - collection = collection.Skip(_paging.Value.page * _paging.Value.pageSize).Take(_paging.Value.pageSize); + return query; + } - return collection; - }; - } + /// + public Func, IQueryable> Build() + { + return Build; + } - /// - public IEnumerable Build(IEnumerable collection) + /// + public Func, IEnumerable> BuildCompiled() + { + var compiled = BuildFilteringExpression().Compile(); + return collection => { - if (_conditions.Count > 0) - collection = collection.Where(BuildFilteringExpression().Compile()); + if (_conditionList.Count > 0) + collection = collection.Where(compiled); - if (!string.IsNullOrEmpty(_orderBy)) + if (!string.IsNullOrEmpty(_orderBy)) // TODO: this also should be compiled collection = collection.AsQueryable().ApplyOrdering(_orderBy); if (_paging.HasValue) collection = collection.Skip(_paging.Value.page * _paging.Value.pageSize).Take(_paging.Value.pageSize); return collection; - } + }; + } - /// - public Paging BuildWithPaging(IEnumerable collection) - { - var query = collection.AsQueryable(); - return BuildWithPaging(query); - } + /// + public IEnumerable Build(IEnumerable collection) + { + if (_conditionList.Count > 0) + collection = collection.Where(BuildFilteringExpression().Compile()); - /// - public Paging BuildWithPaging(IQueryable collection) - { - var (count, query) = BuildWithQueryablePaging(collection); - return new Paging(count, query); - } + if (!string.IsNullOrEmpty(_orderBy)) + collection = collection.AsQueryable().ApplyOrdering(_orderBy); - /// - public Func, QueryablePaging> BuildWithQueryablePaging() - { - return BuildWithQueryablePaging; - } + if (_paging.HasValue) + collection = collection.Skip(_paging.Value.page * _paging.Value.pageSize).Take(_paging.Value.pageSize); - /// - public Func, Paging> BuildWithPaging() - { - return BuildWithPaging; - } + return collection; + } + + /// + public Paging BuildWithPaging(IEnumerable collection) + { + var query = collection.AsQueryable(); + return BuildWithPaging(query); + } + + /// + public Paging BuildWithPaging(IQueryable collection) + { + var (count, query) = BuildWithQueryablePaging(collection); + return new Paging(count, query); + } + + /// + public Func, QueryablePaging> BuildWithQueryablePaging() + { + return BuildWithQueryablePaging; + } - public Func, Paging> BuildWithPagingCompiled() + /// + public Func, Paging> BuildWithPaging() + { + return BuildWithPaging; + } + + public Func, Paging> BuildWithPagingCompiled() + { + var compiled = BuildFilteringExpression().Compile(); + return collection => { - var compiled = BuildFilteringExpression().Compile(); - return collection => - { - if (_conditions.Count > 0) - collection = collection.Where(compiled); + if (_conditionList.Count > 0) + collection = collection.Where(compiled); - if (!string.IsNullOrEmpty(_orderBy)) // TODO: this also should be compiled - collection = collection.AsQueryable().ApplyOrdering(_orderBy); + if (!string.IsNullOrEmpty(_orderBy)) // TODO: this also should be compiled + collection = collection.AsQueryable().ApplyOrdering(_orderBy); - var result = collection.ToList(); - var count = result.Count(); + var result = collection.ToList(); + var count = result.Count(); - return _paging.HasValue - ? new Paging(count, result.Skip(_paging.Value.page * _paging.Value.pageSize).Take(_paging.Value.pageSize)) - : new Paging(count, result); - }; - } + return _paging.HasValue + ? new Paging(count, result.Skip(_paging.Value.page * _paging.Value.pageSize).Take(_paging.Value.pageSize)) + : new Paging(count, result); + }; + } - /// - public QueryablePaging BuildWithQueryablePaging(IQueryable collection) - { - var query = collection; - if (_conditions.Count > 0) - query = query.Where(BuildFilteringExpression()); + /// + public QueryablePaging BuildWithQueryablePaging(IQueryable collection) + { + var query = collection; + if (_conditionList.Count > 0) + query = query.Where(BuildFilteringExpression()); - if (!string.IsNullOrEmpty(_orderBy)) - query = query.ApplyOrdering(_orderBy); + if (!string.IsNullOrEmpty(_orderBy)) + query = query.ApplyOrdering(_orderBy); - var count = query.Count(); + var count = query.Count(); - if (_paging.HasValue) - query = query.Skip(_paging.Value.page * _paging.Value.pageSize).Take(_paging.Value.pageSize); + if (_paging.HasValue) + query = query.Skip(_paging.Value.page * _paging.Value.pageSize).Take(_paging.Value.pageSize); - return new QueryablePaging(count, query); - } + return new QueryablePaging(count, query); + } - private Expression> ConvertConditionToExpression(string condition) - { - var syntaxTree = SyntaxTree.Parse(condition); + private Expression> ConvertConditionToExpression(string condition) + { + var syntaxTree = SyntaxTree.Parse(condition); - if (syntaxTree.Diagnostics.Any()) - throw new GridifyFilteringException(syntaxTree.Diagnostics.Last()!); + if (syntaxTree.Diagnostics.Any()) + throw new GridifyFilteringException(syntaxTree.Diagnostics.Last()!); - return syntaxTree.CreateQuery(_mapper); - } + return syntaxTree.CreateQuery(_mapper); } } diff --git a/src/Gridify/QueryablePaging.cs b/src/Gridify/QueryablePaging.cs index 96c41997..e98b6bf4 100644 --- a/src/Gridify/QueryablePaging.cs +++ b/src/Gridify/QueryablePaging.cs @@ -1,21 +1,20 @@ using System.Linq; -namespace Gridify +namespace Gridify; + +public class QueryablePaging { - public class QueryablePaging - { - public QueryablePaging(int count, IQueryable query) - { - Count = count; - Query = query; - } - public void Deconstruct(out int count, out IQueryable query) - { - count = Count; - query = Query; - } - public int Count { get; } - public IQueryable Query { get; } + public QueryablePaging(int count, IQueryable query) + { + Count = count; + Query = query; + } + public void Deconstruct(out int count, out IQueryable query) + { + count = Count; + query = Query; } -} + public int Count { get; } + public IQueryable Query { get; } +} \ No newline at end of file diff --git a/src/Gridify/Syntax/BinaryExpressionSyntax.cs b/src/Gridify/Syntax/BinaryExpressionSyntax.cs index 9cc2596a..29cad2c4 100644 --- a/src/Gridify/Syntax/BinaryExpressionSyntax.cs +++ b/src/Gridify/Syntax/BinaryExpressionSyntax.cs @@ -1,27 +1,26 @@ using System.Collections.Generic; -namespace Gridify.Syntax +namespace Gridify.Syntax; + +internal sealed class BinaryExpressionSyntax : ExpressionSyntax { - internal sealed class BinaryExpressionSyntax : ExpressionSyntax - { - public ExpressionSyntax Left { get; } - public SyntaxToken OperatorToken { get; } - public ExpressionSyntax Right { get; } + public ExpressionSyntax Left { get; } + public SyntaxToken OperatorToken { get; } + public ExpressionSyntax Right { get; } - public BinaryExpressionSyntax(ExpressionSyntax left, SyntaxToken operatorToken, ExpressionSyntax right) - { - Left = left; - OperatorToken = operatorToken; - Right = right; - } + public BinaryExpressionSyntax(ExpressionSyntax left, SyntaxToken operatorToken, ExpressionSyntax right) + { + Left = left; + OperatorToken = operatorToken; + Right = right; + } - public override SyntaxKind Kind => SyntaxKind.BinaryExpression; + public override SyntaxKind Kind => SyntaxKind.BinaryExpression; - public override IEnumerable GetChildren() - { - yield return Left; - yield return OperatorToken; - yield return Right; - } + public override IEnumerable GetChildren() + { + yield return Left; + yield return OperatorToken; + yield return Right; } } \ No newline at end of file diff --git a/src/Gridify/Syntax/ExpressionSyntax.cs b/src/Gridify/Syntax/ExpressionSyntax.cs index e49586fe..a569aeb5 100644 --- a/src/Gridify/Syntax/ExpressionSyntax.cs +++ b/src/Gridify/Syntax/ExpressionSyntax.cs @@ -1,6 +1,5 @@ -namespace Gridify.Syntax +namespace Gridify.Syntax; + +public abstract class ExpressionSyntax : SyntaxNode { - public abstract class ExpressionSyntax : SyntaxNode - { - } } \ No newline at end of file diff --git a/src/Gridify/Syntax/FieldExpressionSyntax.cs b/src/Gridify/Syntax/FieldExpressionSyntax.cs index 03ad6ad5..222c0dc0 100644 --- a/src/Gridify/Syntax/FieldExpressionSyntax.cs +++ b/src/Gridify/Syntax/FieldExpressionSyntax.cs @@ -1,30 +1,29 @@ using System.Collections.Generic; -namespace Gridify.Syntax +namespace Gridify.Syntax; + +internal sealed class FieldExpressionSyntax : ExpressionSyntax { - internal sealed class FieldExpressionSyntax : ExpressionSyntax + internal FieldExpressionSyntax(SyntaxToken fieldToken) { - internal FieldExpressionSyntax(SyntaxToken fieldToken) - { - FieldToken = fieldToken; - } - - public FieldExpressionSyntax(SyntaxToken fieldToken, int index) - { - IsCollection = true; - Index = index; - FieldToken = fieldToken; - } + FieldToken = fieldToken; + } - public override SyntaxKind Kind => SyntaxKind.FieldExpression; + public FieldExpressionSyntax(SyntaxToken fieldToken, int index) + { + IsCollection = true; + Index = index; + FieldToken = fieldToken; + } - public override IEnumerable GetChildren() - { - yield return FieldToken; - } + public override SyntaxKind Kind => SyntaxKind.FieldExpression; - public bool IsCollection { get; } - public int Index { get; } - public SyntaxToken FieldToken { get; } + public override IEnumerable GetChildren() + { + yield return FieldToken; } + + public bool IsCollection { get; } + public int Index { get; } + public SyntaxToken FieldToken { get; } } \ No newline at end of file diff --git a/src/Gridify/Syntax/GridifyTypeBuilder.cs b/src/Gridify/Syntax/GridifyTypeBuilder.cs index 298478c7..437dd0f5 100644 --- a/src/Gridify/Syntax/GridifyTypeBuilder.cs +++ b/src/Gridify/Syntax/GridifyTypeBuilder.cs @@ -2,96 +2,95 @@ using System.Reflection; using System.Reflection.Emit; -namespace Gridify.Syntax +namespace Gridify.Syntax; + +internal static class GridifyTypeBuilder { - internal static class GridifyTypeBuilder + private class FieldDescriptor { - private class FieldDescriptor + public FieldDescriptor(string fieldName, Type fieldType) { - public FieldDescriptor(string fieldName, Type fieldType) - { - FieldName = fieldName; - FieldType = fieldType; - } - - public string FieldName { get; } - public Type FieldType { get; } + FieldName = fieldName; + FieldType = fieldType; } - public static (object Instance, Type type) CreateNewObject(Type type, string fieldName, object? value) - { - var myTypeInfo = CompileResultTypeInfo(type, fieldName); - var myType = myTypeInfo!.AsType(); - var myObject = Activator.CreateInstance(myType); - myType.GetProperty(fieldName)!.SetValue(myObject, value); - return (myObject, myType); - } + public string FieldName { get; } + public Type FieldType { get; } + } - public static TypeInfo? CompileResultTypeInfo(Type type, string name) - { - TypeBuilder tb = GetTypeBuilder(); - ConstructorBuilder constructor = - tb.DefineDefaultConstructor(MethodAttributes.Public | MethodAttributes.SpecialName | MethodAttributes.RTSpecialName); + public static (object Instance, Type type) CreateNewObject(Type type, string fieldName, object? value) + { + var myTypeInfo = CompileResultTypeInfo(type, fieldName); + var myType = myTypeInfo!.AsType(); + var myObject = Activator.CreateInstance(myType); + myType.GetProperty(fieldName)!.SetValue(myObject, value); + return (myObject, myType); + } - var field = new FieldDescriptor(name, type); - CreateProperty(tb, field.FieldName, field.FieldType); - var objectTypeInfo = tb.CreateTypeInfo(); + public static TypeInfo? CompileResultTypeInfo(Type type, string name) + { + TypeBuilder tb = GetTypeBuilder(); + ConstructorBuilder constructor = + tb.DefineDefaultConstructor(MethodAttributes.Public | MethodAttributes.SpecialName | MethodAttributes.RTSpecialName); - return objectTypeInfo; - } + var field = new FieldDescriptor(name, type); + CreateProperty(tb, field.FieldName, field.FieldType); + var objectTypeInfo = tb.CreateTypeInfo(); - private static TypeBuilder GetTypeBuilder() - { - const string? typeSignature = "GridifyDisplayClass"; - var an = new AssemblyName(typeSignature); - var assemblyBuilder = AssemblyBuilder.DefineDynamicAssembly(new AssemblyName(Guid.NewGuid().ToString()), AssemblyBuilderAccess.Run); - ModuleBuilder moduleBuilder = assemblyBuilder.DefineDynamicModule("RuntimeModule"); - TypeBuilder tb = moduleBuilder.DefineType(typeSignature, - TypeAttributes.Public | - TypeAttributes.Class | - TypeAttributes.AutoClass | - TypeAttributes.AnsiClass | - TypeAttributes.BeforeFieldInit | - TypeAttributes.AutoLayout, - null); - return tb; - } + return objectTypeInfo; + } - private static void CreateProperty(TypeBuilder tb, string propertyName, Type propertyType) - { - FieldBuilder fieldBuilder = tb.DefineField("_" + propertyName, propertyType, FieldAttributes.Private); - - PropertyBuilder propertyBuilder = tb.DefineProperty(propertyName, PropertyAttributes.HasDefault, propertyType, null); - MethodBuilder getPropMthdBldr = tb.DefineMethod("get_" + propertyName, - MethodAttributes.Public | MethodAttributes.SpecialName | MethodAttributes.HideBySig, propertyType, Type.EmptyTypes); - ILGenerator getIl = getPropMthdBldr.GetILGenerator(); - - getIl.Emit(OpCodes.Ldarg_0); - getIl.Emit(OpCodes.Ldfld, fieldBuilder); - getIl.Emit(OpCodes.Ret); - - MethodBuilder setPropMthdBldr = - tb.DefineMethod("set_" + propertyName, - MethodAttributes.Public | - MethodAttributes.SpecialName | - MethodAttributes.HideBySig, - null, new[] { propertyType }); - - ILGenerator setIl = setPropMthdBldr.GetILGenerator(); - var modifyProperty = setIl.DefineLabel(); - var exitSet = setIl.DefineLabel(); - - setIl.MarkLabel(modifyProperty); - setIl.Emit(OpCodes.Ldarg_0); - setIl.Emit(OpCodes.Ldarg_1); - setIl.Emit(OpCodes.Stfld, fieldBuilder); - - setIl.Emit(OpCodes.Nop); - setIl.MarkLabel(exitSet); - setIl.Emit(OpCodes.Ret); - - propertyBuilder.SetGetMethod(getPropMthdBldr); - propertyBuilder.SetSetMethod(setPropMthdBldr); - } + private static TypeBuilder GetTypeBuilder() + { + const string? typeSignature = "GridifyDisplayClass"; + var an = new AssemblyName(typeSignature); + var assemblyBuilder = AssemblyBuilder.DefineDynamicAssembly(new AssemblyName(Guid.NewGuid().ToString()), AssemblyBuilderAccess.Run); + ModuleBuilder moduleBuilder = assemblyBuilder.DefineDynamicModule("RuntimeModule"); + TypeBuilder tb = moduleBuilder.DefineType(typeSignature, + TypeAttributes.Public | + TypeAttributes.Class | + TypeAttributes.AutoClass | + TypeAttributes.AnsiClass | + TypeAttributes.BeforeFieldInit | + TypeAttributes.AutoLayout, + null); + return tb; + } + + private static void CreateProperty(TypeBuilder tb, string propertyName, Type propertyType) + { + FieldBuilder fieldBuilder = tb.DefineField("_" + propertyName, propertyType, FieldAttributes.Private); + + PropertyBuilder propertyBuilder = tb.DefineProperty(propertyName, PropertyAttributes.HasDefault, propertyType, null); + MethodBuilder getPropMthdBldr = tb.DefineMethod("get_" + propertyName, + MethodAttributes.Public | MethodAttributes.SpecialName | MethodAttributes.HideBySig, propertyType, Type.EmptyTypes); + ILGenerator getIl = getPropMthdBldr.GetILGenerator(); + + getIl.Emit(OpCodes.Ldarg_0); + getIl.Emit(OpCodes.Ldfld, fieldBuilder); + getIl.Emit(OpCodes.Ret); + + MethodBuilder setPropMthdBldr = + tb.DefineMethod("set_" + propertyName, + MethodAttributes.Public | + MethodAttributes.SpecialName | + MethodAttributes.HideBySig, + null, new[] { propertyType }); + + ILGenerator setIl = setPropMthdBldr.GetILGenerator(); + var modifyProperty = setIl.DefineLabel(); + var exitSet = setIl.DefineLabel(); + + setIl.MarkLabel(modifyProperty); + setIl.Emit(OpCodes.Ldarg_0); + setIl.Emit(OpCodes.Ldarg_1); + setIl.Emit(OpCodes.Stfld, fieldBuilder); + + setIl.Emit(OpCodes.Nop); + setIl.MarkLabel(exitSet); + setIl.Emit(OpCodes.Ret); + + propertyBuilder.SetGetMethod(getPropMthdBldr); + propertyBuilder.SetSetMethod(setPropMthdBldr); } } \ No newline at end of file diff --git a/src/Gridify/Syntax/Lexer.cs b/src/Gridify/Syntax/Lexer.cs index 356ffc8a..02cc3e06 100644 --- a/src/Gridify/Syntax/Lexer.cs +++ b/src/Gridify/Syntax/Lexer.cs @@ -2,186 +2,185 @@ using System.Linq; using System.Text; -namespace Gridify.Syntax +namespace Gridify.Syntax; + +internal class Lexer { - internal class Lexer + private readonly List _diagnostics = new(); + private readonly string _text; + private int _position; + private bool _waitingForValue; + + public Lexer(string text) { - private readonly List _diagnostics = new(); - private readonly string _text; - private int _position; - private bool _waitingForValue; + _text = text; + } - public Lexer(string text) - { - _text = text; - } + public IEnumerable Diagnostics => _diagnostics; + + private char Current => _position >= _text.Length ? '\0' : _text[_position]; - public IEnumerable Diagnostics => _diagnostics; + private void Next() + { + _position++; + } - private char Current => _position >= _text.Length ? '\0' : _text[_position]; + private char Peek(int offset) + { + return _position + offset >= _text.Length ? '\0' : _text[_position + offset]; + } - private void Next() - { - _position++; - } + public SyntaxToken NextToken() + { + if (_position >= _text.Length) return new SyntaxToken(SyntaxKind.End, _position, "\0"); - private char Peek(int offset) + var peek = Peek(1); + switch (Current) { - return _position + offset >= _text.Length ? '\0' : _text[_position + offset]; + case '(': + return new SyntaxToken(SyntaxKind.OpenParenthesisToken, _position++, "("); + case ')': + return new SyntaxToken(SyntaxKind.CloseParenthesis, _position++, ")"); + case ',': + return new SyntaxToken(SyntaxKind.And, _position++, ","); + case '|': + return new SyntaxToken(SyntaxKind.Or, _position++, "|"); + case '^': + { + _waitingForValue = true; + return new SyntaxToken(SyntaxKind.StartsWith, _position++, "^"); + } + case '$': + { + _waitingForValue = true; + return new SyntaxToken(SyntaxKind.EndsWith, _position++, "$"); + } + case '!' when peek == '^': + { + _waitingForValue = true; + return new SyntaxToken(SyntaxKind.NotStartsWith, _position += 2, "!^"); + } + case '!' when peek == '$': + { + _waitingForValue = true; + return new SyntaxToken(SyntaxKind.NotEndsWith, _position += 2, "!$"); + } + case '=' when peek == '*': + { + _waitingForValue = true; + return new SyntaxToken(SyntaxKind.Like, _position += 2, "=*"); + } + case '=': + { + _waitingForValue = true; + return new SyntaxToken(SyntaxKind.Equal, _position ++ , "="); + } + case '!' when peek == '=': + { + _waitingForValue = true; + return new SyntaxToken(SyntaxKind.NotEqual, _position += 2, "!="); + } + case '!' when peek == '*': + { + _waitingForValue = true; + return new SyntaxToken(SyntaxKind.NotLike, _position += 2, "!*"); + } + case '/' when peek == 'i': + return new SyntaxToken(SyntaxKind.CaseInsensitive, _position += 2, "/i"); + case '<': + { + _waitingForValue = true; + return peek == '=' ? new SyntaxToken(SyntaxKind.LessOrEqualThan, _position += 2, "<=") : + new SyntaxToken(SyntaxKind.LessThan, _position++, "<"); + } + case '>': + { + _waitingForValue = true; + return peek == '=' ? new SyntaxToken(SyntaxKind.GreaterOrEqualThan, _position += 2, ">=") : + new SyntaxToken(SyntaxKind.GreaterThan, _position++, ">"); + } } - public SyntaxToken NextToken() + if (Current == '[') { - if (_position >= _text.Length) return new SyntaxToken(SyntaxKind.End, _position, "\0"); + Next(); + var start = _position; + while (char.IsDigit(Current)) + Next(); + + var length = _position - start; + var text = _text.Substring(start, length); - var peek = Peek(1); - switch (Current) + if (Current == ']') { - case '(': - return new SyntaxToken(SyntaxKind.OpenParenthesisToken, _position++, "("); - case ')': - return new SyntaxToken(SyntaxKind.CloseParenthesis, _position++, ")"); - case ',': - return new SyntaxToken(SyntaxKind.And, _position++, ","); - case '|': - return new SyntaxToken(SyntaxKind.Or, _position++, "|"); - case '^': - { - _waitingForValue = true; - return new SyntaxToken(SyntaxKind.StartsWith, _position++, "^"); - } - case '$': - { - _waitingForValue = true; - return new SyntaxToken(SyntaxKind.EndsWith, _position++, "$"); - } - case '!' when peek == '^': - { - _waitingForValue = true; - return new SyntaxToken(SyntaxKind.NotStartsWith, _position += 2, "!^"); - } - case '!' when peek == '$': - { - _waitingForValue = true; - return new SyntaxToken(SyntaxKind.NotEndsWith, _position += 2, "!$"); - } - case '=' when peek == '*': - { - _waitingForValue = true; - return new SyntaxToken(SyntaxKind.Like, _position += 2, "=*"); - } - case '=': - { - _waitingForValue = true; - return new SyntaxToken(SyntaxKind.Equal, _position ++ , "="); - } - case '!' when peek == '=': - { - _waitingForValue = true; - return new SyntaxToken(SyntaxKind.NotEqual, _position += 2, "!="); - } - case '!' when peek == '*': - { - _waitingForValue = true; - return new SyntaxToken(SyntaxKind.NotLike, _position += 2, "!*"); - } - case '/' when peek == 'i': - return new SyntaxToken(SyntaxKind.CaseInsensitive, _position += 2, "/i"); - case '<': - { - _waitingForValue = true; - return peek == '=' ? new SyntaxToken(SyntaxKind.LessOrEqualThan, _position += 2, "<=") : - new SyntaxToken(SyntaxKind.LessThan, _position++, "<"); - } - case '>': - { - _waitingForValue = true; - return peek == '=' ? new SyntaxToken(SyntaxKind.GreaterOrEqualThan, _position += 2, ">=") : - new SyntaxToken(SyntaxKind.GreaterThan, _position++, ">"); - } + _position++; + return new SyntaxToken(SyntaxKind.FieldIndexToken, start, text); } - if (Current == '[') - { + _diagnostics.Add($"bad character input: '{peek.ToString()}' at {_position++.ToString()}. expected ']' "); + return new SyntaxToken(SyntaxKind.BadToken, _position, Current.ToString()); + + } + + if (char.IsLetter(Current) && !_waitingForValue) + { + var start = _position; + + while (char.IsLetterOrDigit(Current) || Current is '_') Next(); - var start = _position; - while (char.IsDigit(Current)) - Next(); - - var length = _position - start; - var text = _text.Substring(start, length); - - if (Current == ']') - { - _position++; - return new SyntaxToken(SyntaxKind.FieldIndexToken, start, text); - } - - _diagnostics.Add($"bad character input: '{peek.ToString()}' at {_position++.ToString()}. expected ']' "); - return new SyntaxToken(SyntaxKind.BadToken, _position, Current.ToString()); - } + var length = _position - start; + var text = _text.Substring(start, length); - if (char.IsLetter(Current) && !_waitingForValue) - { - var start = _position; + return new SyntaxToken(SyntaxKind.FieldToken, start, text); + } - while (char.IsLetterOrDigit(Current) || Current is '_') - Next(); - var length = _position - start; - var text = _text.Substring(start, length); - - return new SyntaxToken(SyntaxKind.FieldToken, start, text); - } - + if (char.IsWhiteSpace(Current)) + { + var start = _position; - if (char.IsWhiteSpace(Current)) - { - var start = _position; + // skipper + while (char.IsWhiteSpace(Current)) + Next(); + + var length = _position - start; + var text = _text.Substring(start, length); - // skipper - while (char.IsWhiteSpace(Current)) - Next(); + return new SyntaxToken(SyntaxKind.WhiteSpace, start, text); + } - var length = _position - start; - var text = _text.Substring(start, length); + if (_waitingForValue) + { + var start = _position; - return new SyntaxToken(SyntaxKind.WhiteSpace, start, text); + var exitCharacters = new[] {'(', ')', ',', '|'}; + var lastChar = '\0'; + while ((!exitCharacters.Contains(Current) || exitCharacters.Contains(Current) && lastChar == '\\') && + _position < _text.Length && + (!(Current == '/' && Peek(1) == 'i') || (Current == '/' && Peek(1) == 'i') && lastChar == '\\')) // exit on case-insensitive operator + { + lastChar = Current; + Next(); } - if (_waitingForValue) + var text = new StringBuilder(); + for (var i = start; i < _position; i++) { - var start = _position; - - var exitCharacters = new[] {'(', ')', ',', '|'}; - var lastChar = '\0'; - while ((!exitCharacters.Contains(Current) || exitCharacters.Contains(Current) && lastChar == '\\') && - _position < _text.Length && - (!(Current == '/' && Peek(1) == 'i') || (Current == '/' && Peek(1) == 'i') && lastChar == '\\')) // exit on case-insensitive operator - { - lastChar = Current; - Next(); - } - - var text = new StringBuilder(); - for (var i = start; i < _position; i++) - { - var current = _text[i]; - - if (current != '\\') // ignore escape character - text.Append(current); - else if (current == '\\' && i > 0) // escape escape character - if (_text[i - 1] == '\\') - text.Append(current); - } + var current = _text[i]; - _waitingForValue = false; - return new SyntaxToken(SyntaxKind.ValueToken, start, text.ToString()); + if (current != '\\') // ignore escape character + text.Append(current); + else if (current == '\\' && i > 0) // escape escape character + if (_text[i - 1] == '\\') + text.Append(current); } - _diagnostics.Add($"bad character input: '{Current.ToString()}' at {_position.ToString()}"); - return new SyntaxToken(SyntaxKind.BadToken, _position++, string.Empty); + _waitingForValue = false; + return new SyntaxToken(SyntaxKind.ValueToken, start, text.ToString()); } + + _diagnostics.Add($"bad character input: '{Current.ToString()}' at {_position.ToString()}"); + return new SyntaxToken(SyntaxKind.BadToken, _position++, string.Empty); } -} \ No newline at end of file +} diff --git a/src/Gridify/Syntax/ParenthesizedExpressionSyntax.cs b/src/Gridify/Syntax/ParenthesizedExpressionSyntax.cs index 8f2a2f93..b69fab9f 100644 --- a/src/Gridify/Syntax/ParenthesizedExpressionSyntax.cs +++ b/src/Gridify/Syntax/ParenthesizedExpressionSyntax.cs @@ -1,25 +1,24 @@ using System.Collections.Generic; -namespace Gridify.Syntax +namespace Gridify.Syntax; + +internal sealed class ParenthesizedExpressionSyntax : ExpressionSyntax { - internal sealed class ParenthesizedExpressionSyntax : ExpressionSyntax - { - public SyntaxToken OpenParenthesisToken { get; } - public ExpressionSyntax Expression { get; } - public SyntaxToken CloseParenthesisToken { get; } - public override SyntaxKind Kind => SyntaxKind.ParenthesizedExpression; + public SyntaxToken OpenParenthesisToken { get; } + public ExpressionSyntax Expression { get; } + public SyntaxToken CloseParenthesisToken { get; } + public override SyntaxKind Kind => SyntaxKind.ParenthesizedExpression; - public ParenthesizedExpressionSyntax(SyntaxToken openParenthesisToken,ExpressionSyntax expression,SyntaxToken closeParenthesisToken) - { - OpenParenthesisToken = openParenthesisToken; - Expression = expression; - CloseParenthesisToken = closeParenthesisToken; - } - public override IEnumerable GetChildren() - { - yield return OpenParenthesisToken; - yield return Expression; - yield return CloseParenthesisToken; - } + public ParenthesizedExpressionSyntax(SyntaxToken openParenthesisToken,ExpressionSyntax expression,SyntaxToken closeParenthesisToken) + { + OpenParenthesisToken = openParenthesisToken; + Expression = expression; + CloseParenthesisToken = closeParenthesisToken; + } + public override IEnumerable GetChildren() + { + yield return OpenParenthesisToken; + yield return Expression; + yield return CloseParenthesisToken; } } \ No newline at end of file diff --git a/src/Gridify/Syntax/Parser.cs b/src/Gridify/Syntax/Parser.cs index d2d10978..ba4874ec 100644 --- a/src/Gridify/Syntax/Parser.cs +++ b/src/Gridify/Syntax/Parser.cs @@ -1,151 +1,147 @@ -using System; -using System.Collections.Generic; +using System.Collections.Generic; using System.Linq; -using System.Text.RegularExpressions; -namespace Gridify.Syntax +namespace Gridify.Syntax; + +internal class Parser { - internal class Parser - { - private readonly List _diagnostics = new(); - private readonly SyntaxToken[] _tokens; - private int _position; + private readonly List _diagnostics = new(); + private readonly SyntaxToken[] _tokens; + private int _position; - public Parser(string text) + public Parser(string text) + { + var tokens = new List(); + var lexer = new Lexer(text); + SyntaxToken token; + do { - var tokens = new List(); - var lexer = new Lexer(text); - SyntaxToken token; - do - { - token = lexer.NextToken(); - if (token.Kind != SyntaxKind.BadToken && token.Kind != SyntaxKind.WhiteSpace) tokens.Add(token); - } while (token.Kind != SyntaxKind.End); - - _tokens = tokens.ToArray(); - _diagnostics.AddRange(lexer.Diagnostics); - } + token = lexer.NextToken(); + if (token.Kind != SyntaxKind.BadToken && token.Kind != SyntaxKind.WhiteSpace) tokens.Add(token); + } while (token.Kind != SyntaxKind.End); - private SyntaxToken Current => Peek(0); + _tokens = tokens.ToArray(); + _diagnostics.AddRange(lexer.Diagnostics); + } - private SyntaxToken Peek(int offset) - { - var index = _position + offset; - return index >= _tokens.Length ? _tokens[_tokens.Length - 1] : _tokens[index]; - } + private SyntaxToken Current => Peek(0); - public SyntaxTree Parse() + private SyntaxToken Peek(int offset) + { + var index = _position + offset; + return index >= _tokens.Length ? _tokens[_tokens.Length - 1] : _tokens[index]; + } + + public SyntaxTree Parse() + { + var expression = ParseTerm(); + var end = Match(SyntaxKind.End); + return new SyntaxTree(_diagnostics, expression, end); + } + + private ExpressionSyntax ParseTerm() + { + var left = ParseFactor(); + var binaryKinds = new[] { - var expression = ParseTerm(); - var end = Match(SyntaxKind.End); - return new SyntaxTree(_diagnostics, expression, end); - } + SyntaxKind.And, + SyntaxKind.Or + }; - private ExpressionSyntax ParseTerm() + while (binaryKinds.Contains(Current.Kind)) { - var left = ParseFactor(); - var binaryKinds = new[] - { - SyntaxKind.And, - SyntaxKind.Or - }; - - while (binaryKinds.Contains(Current.Kind)) - { - var operatorToken = NextToken(); - var right = ParseFactor(); - left = new BinaryExpressionSyntax(left, operatorToken, right); - } - - return left; + var operatorToken = NextToken(); + var right = ParseFactor(); + left = new BinaryExpressionSyntax(left, operatorToken, right); } - private ExpressionSyntax ParseFactor() + return left; + } + + private ExpressionSyntax ParseFactor() + { + var left = ParsePrimaryExpression(); + var binaryKinds = new[] + { + SyntaxKind.Equal, + SyntaxKind.Like, + SyntaxKind.NotEqual, + SyntaxKind.NotLike, + SyntaxKind.GreaterThan, + SyntaxKind.LessThan, + SyntaxKind.GreaterOrEqualThan, + SyntaxKind.LessOrEqualThan, + SyntaxKind.StartsWith, + SyntaxKind.EndsWith, + SyntaxKind.NotStartsWith, + SyntaxKind.NotEndsWith + }; + + while (binaryKinds.Contains(Current.Kind)) { - var left = ParsePrimaryExpression(); - var binaryKinds = new[] - { - SyntaxKind.Equal, - SyntaxKind.Like, - SyntaxKind.NotEqual, - SyntaxKind.NotLike, - SyntaxKind.GreaterThan, - SyntaxKind.LessThan, - SyntaxKind.GreaterOrEqualThan, - SyntaxKind.LessOrEqualThan, - SyntaxKind.StartsWith, - SyntaxKind.EndsWith, - SyntaxKind.NotStartsWith, - SyntaxKind.NotEndsWith - }; - - while (binaryKinds.Contains(Current.Kind)) - { - var operatorToken = NextToken(); - var right = ParseValueExpression(); - left = new BinaryExpressionSyntax(left, operatorToken, right); - } - - return left; + var operatorToken = NextToken(); + var right = ParseValueExpression(); + left = new BinaryExpressionSyntax(left, operatorToken, right); } - private ExpressionSyntax ParseValueExpression() - { - // field= - if (Current.Kind != SyntaxKind.ValueToken) - return new ValueExpressionSyntax(new SyntaxToken(), false, true); + return left; + } - var valueToken = Match(SyntaxKind.ValueToken); - var isCaseInsensitive = IsMatch(SyntaxKind.CaseInsensitive, out _); - return new ValueExpressionSyntax(valueToken, isCaseInsensitive, false); - } + private ExpressionSyntax ParseValueExpression() + { + // field= + if (Current.Kind != SyntaxKind.ValueToken) + return new ValueExpressionSyntax(new SyntaxToken(), false, true); - private SyntaxToken NextToken() - { - var current = Current; - _position++; - return current; - } + var valueToken = Match(SyntaxKind.ValueToken); + var isCaseInsensitive = IsMatch(SyntaxKind.CaseInsensitive, out _); + return new ValueExpressionSyntax(valueToken, isCaseInsensitive, false); + } - private bool IsMatch(SyntaxKind kind, out SyntaxToken token) + private SyntaxToken NextToken() + { + var current = Current; + _position++; + return current; + } + + private bool IsMatch(SyntaxKind kind, out SyntaxToken token) + { + if (Current.Kind != kind) { - if (Current.Kind != kind) - { - token = Current; - return false; - } - - token = NextToken(); - return true; + token = Current; + return false; } - private SyntaxToken Match(SyntaxKind kind) - { - if (Current.Kind == kind) - return NextToken(); + token = NextToken(); + return true; + } - _diagnostics.Add($"Unexpected token <{Current.Kind}>, expected <{kind}>"); - return new SyntaxToken(kind, 0, string.Empty); - } + private SyntaxToken Match(SyntaxKind kind) + { + if (Current.Kind == kind) + return NextToken(); - private ExpressionSyntax ParsePrimaryExpression() - { - if (Current.Kind != SyntaxKind.OpenParenthesisToken) return ParseFieldExpression(); + _diagnostics.Add($"Unexpected token <{Current.Kind}>, expected <{kind}>"); + return new SyntaxToken(kind, 0, string.Empty); + } - var left = NextToken(); - var expression = ParseTerm(); - var right = Match(SyntaxKind.CloseParenthesis); - return new ParenthesizedExpressionSyntax(left, expression, right); - } + private ExpressionSyntax ParsePrimaryExpression() + { + if (Current.Kind != SyntaxKind.OpenParenthesisToken) return ParseFieldExpression(); - private ExpressionSyntax ParseFieldExpression() - { - var fieldToken = Match(SyntaxKind.FieldToken); + var left = NextToken(); + var expression = ParseTerm(); + var right = Match(SyntaxKind.CloseParenthesis); + return new ParenthesizedExpressionSyntax(left, expression, right); + } - if (IsMatch(SyntaxKind.FieldIndexToken, out SyntaxToken fieldSyntaxToken)) - return new FieldExpressionSyntax(fieldToken, int.Parse(fieldSyntaxToken.Text)); + private ExpressionSyntax ParseFieldExpression() + { + var fieldToken = Match(SyntaxKind.FieldToken); - return new FieldExpressionSyntax(fieldToken); - } + return IsMatch(SyntaxKind.FieldIndexToken, out var fieldSyntaxToken) + ? new FieldExpressionSyntax(fieldToken, int.Parse(fieldSyntaxToken.Text)) + : new FieldExpressionSyntax(fieldToken); } -} \ No newline at end of file +} diff --git a/src/Gridify/Syntax/ReplaceExpressionVisitor.cs b/src/Gridify/Syntax/ReplaceExpressionVisitor.cs index a8f7864a..51de46c7 100644 --- a/src/Gridify/Syntax/ReplaceExpressionVisitor.cs +++ b/src/Gridify/Syntax/ReplaceExpressionVisitor.cs @@ -1,21 +1,20 @@ using System.Linq.Expressions; -namespace Gridify.Syntax +namespace Gridify.Syntax; + +internal class ReplaceExpressionVisitor : ExpressionVisitor { - internal class ReplaceExpressionVisitor : ExpressionVisitor - { - private readonly Expression _oldValue; - private readonly Expression _newValue; + private readonly Expression _oldValue; + private readonly Expression _newValue; - public ReplaceExpressionVisitor(Expression oldValue, Expression newValue) - { - _oldValue = oldValue; - _newValue = newValue; - } + public ReplaceExpressionVisitor(Expression oldValue, Expression newValue) + { + _oldValue = oldValue; + _newValue = newValue; + } - public override Expression Visit(Expression node) - { - return node == _oldValue ? _newValue : base.Visit(node)!; - } + public override Expression Visit(Expression node) + { + return node == _oldValue ? _newValue : base.Visit(node)!; } -} +} \ No newline at end of file diff --git a/src/Gridify/Syntax/SyntaxKind.cs b/src/Gridify/Syntax/SyntaxKind.cs index fac1a7a7..0a2c680d 100644 --- a/src/Gridify/Syntax/SyntaxKind.cs +++ b/src/Gridify/Syntax/SyntaxKind.cs @@ -1,37 +1,36 @@ -namespace Gridify.Syntax +namespace Gridify.Syntax; + +public enum SyntaxKind { - public enum SyntaxKind - { - // specials - End, - BadToken, - WhiteSpace, + // specials + End, + BadToken, + WhiteSpace, - FieldToken, - OpenParenthesisToken, - CloseParenthesis, - And, - Or, - Equal, - Like, - NotEqual, - NotLike, - LessThan, - GreaterThan, - LessOrEqualThan, - GreaterOrEqualThan, - StartsWith, - EndsWith, + FieldToken, + OpenParenthesisToken, + CloseParenthesis, + And, + Or, + Equal, + Like, + NotEqual, + NotLike, + LessThan, + GreaterThan, + LessOrEqualThan, + GreaterOrEqualThan, + StartsWith, + EndsWith, - // expressions - FieldExpression, - BinaryExpression, - ValueExpression, - ValueToken, - ParenthesizedExpression, - NotStartsWith, - NotEndsWith, - CaseInsensitive, - FieldIndexToken - } + // expressions + FieldExpression, + BinaryExpression, + ValueExpression, + ValueToken, + ParenthesizedExpression, + NotStartsWith, + NotEndsWith, + CaseInsensitive, + FieldIndexToken } \ No newline at end of file diff --git a/src/Gridify/Syntax/SyntaxNode.cs b/src/Gridify/Syntax/SyntaxNode.cs index cb8b9ad8..4089043f 100644 --- a/src/Gridify/Syntax/SyntaxNode.cs +++ b/src/Gridify/Syntax/SyntaxNode.cs @@ -1,10 +1,9 @@ using System.Collections.Generic; -namespace Gridify.Syntax +namespace Gridify.Syntax; + +public abstract class SyntaxNode { - public abstract class SyntaxNode - { - public abstract SyntaxKind Kind { get; } - public abstract IEnumerable GetChildren(); - } + public abstract SyntaxKind Kind { get; } + public abstract IEnumerable GetChildren(); } \ No newline at end of file diff --git a/src/Gridify/Syntax/SyntaxToken.cs b/src/Gridify/Syntax/SyntaxToken.cs index 2be392a7..1a0a948d 100644 --- a/src/Gridify/Syntax/SyntaxToken.cs +++ b/src/Gridify/Syntax/SyntaxToken.cs @@ -1,28 +1,27 @@ using System.Collections.Generic; using System.Linq; -namespace Gridify.Syntax +namespace Gridify.Syntax; + +public class SyntaxToken : SyntaxNode { - public class SyntaxToken : SyntaxNode - { - public override SyntaxKind Kind { get; } - public string Text { get; } + public override SyntaxKind Kind { get; } + public string Text { get; } - public override IEnumerable GetChildren() - { - return Enumerable.Empty(); - } + public override IEnumerable GetChildren() + { + return Enumerable.Empty(); + } - // we don't need position yet ( we can use the second argument later when we had a analyzer or debugger ) - public SyntaxToken(SyntaxKind kind, int _ , string text) - { - Kind = kind; - Text = text; - } + // we don't need position yet ( we can use the second argument later when we had a analyzer or debugger ) + public SyntaxToken(SyntaxKind kind, int _ , string text) + { + Kind = kind; + Text = text; + } - public SyntaxToken() - { - Text = string.Empty; - } + public SyntaxToken() + { + Text = string.Empty; } } \ No newline at end of file diff --git a/src/Gridify/Syntax/SyntaxTree.cs b/src/Gridify/Syntax/SyntaxTree.cs index 2b588be0..84bf03ad 100644 --- a/src/Gridify/Syntax/SyntaxTree.cs +++ b/src/Gridify/Syntax/SyntaxTree.cs @@ -3,23 +3,22 @@ using System.Runtime.CompilerServices; [assembly: InternalsVisibleTo("Gridify.Tests")] -namespace Gridify.Syntax +namespace Gridify.Syntax; + +public sealed class SyntaxTree { - public sealed class SyntaxTree - { - public IReadOnlyList Diagnostics { get; } - public ExpressionSyntax Root { get; } + public IReadOnlyList Diagnostics { get; } + public ExpressionSyntax Root { get; } - public SyntaxTree(IEnumerable diagnostics, ExpressionSyntax root,SyntaxToken endToken) - { - Diagnostics = diagnostics.ToArray(); - Root = root; - } + public SyntaxTree(IEnumerable diagnostics, ExpressionSyntax root,SyntaxToken endToken) + { + Diagnostics = diagnostics.ToArray(); + Root = root; + } - public static SyntaxTree Parse(string text) - { - var parser = new Parser(text); - return parser.Parse(); - } + public static SyntaxTree Parse(string text) + { + var parser = new Parser(text); + return parser.Parse(); } } \ No newline at end of file diff --git a/src/Gridify/Syntax/SyntaxTreeToQueryConvertor.cs b/src/Gridify/Syntax/SyntaxTreeToQueryConvertor.cs index 8237df2a..cbdec558 100644 --- a/src/Gridify/Syntax/SyntaxTreeToQueryConvertor.cs +++ b/src/Gridify/Syntax/SyntaxTreeToQueryConvertor.cs @@ -5,484 +5,483 @@ using System.Linq.Expressions; using System.Reflection; -namespace Gridify.Syntax +namespace Gridify.Syntax; + +internal static class ExpressionToQueryConvertor { - internal static class ExpressionToQueryConvertor + private static (Expression> Expression, bool IsNested)? ConvertBinaryExpressionSyntaxToQuery( + BinaryExpressionSyntax binarySyntax, IGridifyMapper mapper) { - private static (Expression> Expression, bool IsNested)? ConvertBinaryExpressionSyntaxToQuery( - BinaryExpressionSyntax binarySyntax, IGridifyMapper mapper) - { - var fieldExpression = binarySyntax.Left as FieldExpressionSyntax; + var fieldExpression = binarySyntax.Left as FieldExpressionSyntax; - var left = fieldExpression?.FieldToken.Text.Trim(); - var right = (binarySyntax.Right as ValueExpressionSyntax); - var op = binarySyntax.OperatorToken; + var left = fieldExpression?.FieldToken.Text.Trim(); + var right = (binarySyntax.Right as ValueExpressionSyntax); + var op = binarySyntax.OperatorToken; - if (left == null || right == null) return null; + if (left == null || right == null) return null; - var gMap = mapper.GetGMap(left); + var gMap = mapper.GetGMap(left); - if (gMap == null) throw new GridifyMapperException($"Mapping '{left}' not found"); + if (gMap == null) throw new GridifyMapperException($"Mapping '{left}' not found"); - if (fieldExpression!.IsCollection) - gMap.To = UpdateExpressionIndex(gMap.To, fieldExpression.Index); + if (fieldExpression!.IsCollection) + gMap.To = UpdateExpressionIndex(gMap.To, fieldExpression.Index); - var isNested = ((GMap)gMap).IsNestedCollection(); - if (isNested) - { - var result = GenerateNestedExpression(mapper, gMap, right, op); - if (result == null) return null; - return (result, isNested); - } - else - { - if (GenerateExpression(gMap.To.Body, gMap.To.Parameters[0], right, - op, mapper.Configuration.AllowNullSearch, gMap.Convertor) is not Expression> result) return null; - return (result, false); - } + var isNested = ((GMap)gMap).IsNestedCollection(); + if (isNested) + { + var result = GenerateNestedExpression(mapper, gMap, right, op); + if (result == null) return null; + return (result, isNested); } - - private static LambdaExpression UpdateExpressionIndex(LambdaExpression exp, int index) + else { - var body = new ReplaceExpressionVisitor(exp.Parameters[1], Expression.Constant(index, typeof(int))).Visit(exp.Body); - return Expression.Lambda(body, exp.Parameters); + if (GenerateExpression(gMap.To.Body, gMap.To.Parameters[0], right, + op, mapper.Configuration.AllowNullSearch, gMap.Convertor) is not Expression> result) return null; + return (result, false); } + } - private static Expression>? GenerateNestedExpression( - IGridifyMapper mapper, - IGMap gMap, - ValueExpressionSyntax value, - SyntaxNode op) - { - var body = gMap.To.Body; + private static LambdaExpression UpdateExpressionIndex(LambdaExpression exp, int index) + { + var body = new ReplaceExpressionVisitor(exp.Parameters[1], Expression.Constant(index, typeof(int))).Visit(exp.Body); + return Expression.Lambda(body, exp.Parameters); + } - if (body is MethodCallExpression selectExp && selectExp.Method.Name == "Select") - { - var targetExp = selectExp.Arguments.Single(a => a.NodeType == ExpressionType.Lambda) as LambdaExpression; - var conditionExp = GenerateExpression(targetExp!.Body, targetExp.Parameters[0], value, op, mapper.Configuration.AllowNullSearch, - gMap.Convertor); + private static Expression>? GenerateNestedExpression( + IGridifyMapper mapper, + IGMap gMap, + ValueExpressionSyntax value, + SyntaxNode op) + { + var body = gMap.To.Body; - if (conditionExp == null) return null; + if (body is MethodCallExpression selectExp && selectExp.Method.Name == "Select") + { + var targetExp = selectExp.Arguments.Single(a => a.NodeType == ExpressionType.Lambda) as LambdaExpression; + var conditionExp = GenerateExpression(targetExp!.Body, targetExp.Parameters[0], value, op, mapper.Configuration.AllowNullSearch, + gMap.Convertor); - return ParseMethodCallExpression(selectExp, conditionExp) as Expression>; - } + if (conditionExp == null) return null; - // this should never happening - throw new GridifyFilteringException($"The 'Select' method on '{gMap.From}' not found"); + return ParseMethodCallExpression(selectExp, conditionExp) as Expression>; } - private static LambdaExpression ParseMethodCallExpression(MethodCallExpression exp, LambdaExpression predicate) + // this should never happening + throw new GridifyFilteringException($"The 'Select' method on '{gMap.From}' not found"); + } + + private static LambdaExpression ParseMethodCallExpression(MethodCallExpression exp, LambdaExpression predicate) + { + switch (exp.Arguments.First()) { - switch (exp.Arguments.First()) + case MemberExpression member: + return GetAnyExpression(member, predicate); + case MethodCallExpression subExp when subExp.Method.Name == "SelectMany" && + subExp.Arguments.Last() is LambdaExpression { Body: MemberExpression lambdaMember }: { - case MemberExpression member: - return GetAnyExpression(member, predicate); - case MethodCallExpression subExp when subExp.Method.Name == "SelectMany" && - subExp.Arguments.Last() is LambdaExpression { Body: MemberExpression lambdaMember }: - { - var newPredicate = GetAnyExpression(lambdaMember, predicate); - return ParseMethodCallExpression(subExp, newPredicate); - } - case MethodCallExpression subExp when subExp.Method.Name == "Select" && subExp.Arguments.Last() is LambdaExpression - { - Body: MemberExpression lambdaMember - } lambda: - { - var newExp = new ReplaceExpressionVisitor(predicate.Parameters[0], lambdaMember).Visit(predicate.Body); - var newPredicate = GetExpressionWithNullCheck(lambdaMember, lambda.Parameters[0], newExp!); - return ParseMethodCallExpression(subExp, newPredicate); - } - default: - throw new InvalidOperationException(); + var newPredicate = GetAnyExpression(lambdaMember, predicate); + return ParseMethodCallExpression(subExp, newPredicate); } + case MethodCallExpression subExp when subExp.Method.Name == "Select" && subExp.Arguments.Last() is LambdaExpression + { + Body: MemberExpression lambdaMember + } lambda: + { + var newExp = new ReplaceExpressionVisitor(predicate.Parameters[0], lambdaMember).Visit(predicate.Body); + var newPredicate = GetExpressionWithNullCheck(lambdaMember, lambda.Parameters[0], newExp!); + return ParseMethodCallExpression(subExp, newPredicate); + } + default: + throw new InvalidOperationException(); } + } - private static ParameterExpression GetParameterExpression(MemberExpression member) - { - return Expression.Parameter(member.Expression.Type, member.Expression.ToString()); - } + private static ParameterExpression GetParameterExpression(MemberExpression member) + { + return Expression.Parameter(member.Expression.Type, member.Expression.ToString()); + } - private static LambdaExpression GetAnyExpression(MemberExpression member, Expression predicate) - { - var param = GetParameterExpression(member); - var prop = Expression.PropertyOrField(param!, member.Member.Name); + private static LambdaExpression GetAnyExpression(MemberExpression member, Expression predicate) + { + var param = GetParameterExpression(member); + var prop = Expression.PropertyOrField(param!, member.Member.Name); - var tp = prop.Type.IsGenericType - ? prop.Type.GenericTypeArguments.First() // list - : prop.Type.GetElementType(); // array + var tp = prop.Type.IsGenericType + ? prop.Type.GenericTypeArguments.First() // list + : prop.Type.GetElementType(); // array - if (tp == null) throw new GridifyFilteringException($"Can not detect the '{member.Member.Name}' property type."); + if (tp == null) throw new GridifyFilteringException($"Can not detect the '{member.Member.Name}' property type."); - var anyMethod = GetAnyMethod(tp); - var anyExp = Expression.Call(anyMethod, prop, predicate); + var anyMethod = GetAnyMethod(tp); + var anyExp = Expression.Call(anyMethod, prop, predicate); - return GetExpressionWithNullCheck(prop, param, anyExp); - } + return GetExpressionWithNullCheck(prop, param, anyExp); + } - private static LambdaExpression GetExpressionWithNullCheck(MemberExpression prop, ParameterExpression param, Expression right) - { - var nullChecker = Expression.NotEqual(prop, Expression.Constant(null)); - var exp = Expression.AndAlso(nullChecker, right); - return Expression.Lambda(exp, param); - } + private static LambdaExpression GetExpressionWithNullCheck(MemberExpression prop, ParameterExpression param, Expression right) + { + var nullChecker = Expression.NotEqual(prop, Expression.Constant(null)); + var exp = Expression.AndAlso(nullChecker, right); + return Expression.Lambda(exp, param); + } - private static LambdaExpression? GenerateExpression( - Expression body, - ParameterExpression parameter, - ValueExpressionSyntax valueExpression, - SyntaxNode op, - bool allowNullSearch, - Func? convertor) - { - // Remove the boxing for value types - if (body.NodeType == ExpressionType.Convert) body = ((UnaryExpression)body).Operand; + private static LambdaExpression? GenerateExpression( + Expression body, + ParameterExpression parameter, + ValueExpressionSyntax valueExpression, + SyntaxNode op, + bool allowNullSearch, + Func? convertor) + { + // Remove the boxing for value types + if (body.NodeType == ExpressionType.Convert) body = ((UnaryExpression)body).Operand; - object? value = valueExpression.ValueToken.Text; + object? value = valueExpression.ValueToken.Text; - // execute user custom Convertor - if (convertor != null) - value = convertor.Invoke(valueExpression.ValueToken.Text) ?? null; + // execute user custom Convertor + if (convertor != null) + value = convertor.Invoke(valueExpression.ValueToken.Text) ?? null; - // handle the `null` keyword in value - if (allowNullSearch && op.Kind is SyntaxKind.Equal or SyntaxKind.NotEqual && value?.ToString() == "null") - value = null; + // handle the `null` keyword in value + if (allowNullSearch && op.Kind is SyntaxKind.Equal or SyntaxKind.NotEqual && value?.ToString() == "null") + value = null; - // type fixer - if (value is not null && body.Type != value.GetType()) + // type fixer + if (value is not null && body.Type != value.GetType()) + { + try { - try - { - // handle broken guids, github issue #2 - if (body.Type == typeof(Guid) && !Guid.TryParse(value!.ToString(), out _)) value = Guid.NewGuid().ToString(); + // handle broken guids, github issue #2 + if (body.Type == typeof(Guid) && !Guid.TryParse(value!.ToString(), out _)) value = Guid.NewGuid().ToString(); - var converter = TypeDescriptor.GetConverter(body.Type); - value = converter.ConvertFromString(value!.ToString())!; - } - catch (FormatException) - { - // return no records in case of any exception in formatting - return Expression.Lambda(Expression.Constant(false), parameter); // q => false - } + var converter = TypeDescriptor.GetConverter(body.Type); + value = converter.ConvertFromString(value!.ToString())!; } - - // handle case-Insensitive search - if (value is not null && valueExpression.IsCaseInsensitive - && op.Kind is not SyntaxKind.GreaterThan - && op.Kind is not SyntaxKind.LessThan - && op.Kind is not SyntaxKind.GreaterOrEqualThan - && op.Kind is not SyntaxKind.LessOrEqualThan) + catch (FormatException) { - value = value.ToString().ToLower(); - body = Expression.Call(body, GetToLowerMethod()); + // return no records in case of any exception in formatting + return Expression.Lambda(Expression.Constant(false), parameter); // q => false } + } - Expression be; + // handle case-Insensitive search + if (value is not null && valueExpression.IsCaseInsensitive + && op.Kind is not SyntaxKind.GreaterThan + && op.Kind is not SyntaxKind.LessThan + && op.Kind is not SyntaxKind.GreaterOrEqualThan + && op.Kind is not SyntaxKind.LessOrEqualThan) + { + value = value.ToString().ToLower(); + body = Expression.Call(body, GetToLowerMethod()); + } - // use string.Compare instead of operators if value and field are both strings - var areBothStrings = body.Type == typeof(string) && value?.GetType() == typeof(string); + Expression be; - switch (op.Kind) - { - case SyntaxKind.Equal when !valueExpression.IsNullOrDefault: - be = Expression.Equal(body, GetValueExpression(body.Type, value)); - break; - case SyntaxKind.Equal when valueExpression.IsNullOrDefault: - if (body.Type == typeof(string)) - be = Expression.Call(null, GetIsNullOrEmptyMethod(), body); - else - { - var canBeNull = !body.Type.IsValueType || (Nullable.GetUnderlyingType(body.Type) != null); - be = canBeNull - ? Expression.OrElse(Expression.Equal(body, Expression.Constant(null)), Expression.Equal(body, Expression.Default(body.Type))) - : Expression.Equal(body, Expression.Default(body.Type)); - } + // use string.Compare instead of operators if value and field are both strings + var areBothStrings = body.Type == typeof(string) && value?.GetType() == typeof(string); - break; - case SyntaxKind.NotEqual when !valueExpression.IsNullOrDefault: - be = Expression.NotEqual(body, GetValueExpression(body.Type, value)); - break; - case SyntaxKind.NotEqual when valueExpression.IsNullOrDefault: - if (body.Type == typeof(string)) - be = Expression.Not(Expression.Call(null, GetIsNullOrEmptyMethod(), body)); - else - { - var canBeNull = !body.Type.IsValueType || (Nullable.GetUnderlyingType(body.Type) != null); - be = canBeNull - ? Expression.AndAlso(Expression.NotEqual(body, Expression.Constant(null)), - Expression.NotEqual(body, Expression.Default(body.Type))) - : Expression.NotEqual(body, Expression.Default(body.Type)); - } + switch (op.Kind) + { + case SyntaxKind.Equal when !valueExpression.IsNullOrDefault: + be = Expression.Equal(body, GetValueExpression(body.Type, value)); + break; + case SyntaxKind.Equal when valueExpression.IsNullOrDefault: + if (body.Type == typeof(string)) + be = Expression.Call(null, GetIsNullOrEmptyMethod(), body); + else + { + var canBeNull = !body.Type.IsValueType || (Nullable.GetUnderlyingType(body.Type) != null); + be = canBeNull + ? Expression.OrElse(Expression.Equal(body, Expression.Constant(null)), Expression.Equal(body, Expression.Default(body.Type))) + : Expression.Equal(body, Expression.Default(body.Type)); + } - break; - case SyntaxKind.GreaterThan when areBothStrings == false: - be = Expression.GreaterThan(body, GetValueExpression(body.Type, value)); - break; - case SyntaxKind.LessThan when areBothStrings == false: - be = Expression.LessThan(body, GetValueExpression(body.Type, value)); - break; - case SyntaxKind.GreaterOrEqualThan when areBothStrings == false: - be = Expression.GreaterThanOrEqual(body, GetValueExpression(body.Type, value)); - break; - case SyntaxKind.LessOrEqualThan when areBothStrings == false: - be = Expression.LessThanOrEqual(body, GetValueExpression(body.Type, value)); - break; - case SyntaxKind.GreaterThan when areBothStrings: - be = GetGreaterThanExpression(body, valueExpression, value); - break; - case SyntaxKind.LessThan when areBothStrings: - be = GetLessThanExpression(body, valueExpression, value); - break; - case SyntaxKind.GreaterOrEqualThan when areBothStrings: - be = GetGreaterThanOrEqualExpression(body, valueExpression, value); - break; - case SyntaxKind.LessOrEqualThan when areBothStrings: - be = GetLessThanOrEqualExpression(body, valueExpression, value); - break; - case SyntaxKind.Like: - be = Expression.Call(body, GetContainsMethod(), GetValueExpression(body.Type, value)); - break; - case SyntaxKind.NotLike: - be = Expression.Not(Expression.Call(body, GetContainsMethod(), GetValueExpression(body.Type, value))); - break; - case SyntaxKind.StartsWith: - if (body.Type != typeof(string)) - { - body = Expression.Call(body, GetToStringMethod()); - be = Expression.Call(body, GetStartWithMethod(), GetValueExpression(body.Type, value?.ToString())); - } - else - be = Expression.Call(body, GetStartWithMethod(), GetValueExpression(body.Type, value)); + break; + case SyntaxKind.NotEqual when !valueExpression.IsNullOrDefault: + be = Expression.NotEqual(body, GetValueExpression(body.Type, value)); + break; + case SyntaxKind.NotEqual when valueExpression.IsNullOrDefault: + if (body.Type == typeof(string)) + be = Expression.Not(Expression.Call(null, GetIsNullOrEmptyMethod(), body)); + else + { + var canBeNull = !body.Type.IsValueType || (Nullable.GetUnderlyingType(body.Type) != null); + be = canBeNull + ? Expression.AndAlso(Expression.NotEqual(body, Expression.Constant(null)), + Expression.NotEqual(body, Expression.Default(body.Type))) + : Expression.NotEqual(body, Expression.Default(body.Type)); + } - break; - case SyntaxKind.EndsWith: - if (body.Type != typeof(string)) - { - body = Expression.Call(body, GetToStringMethod()); - be = Expression.Call(body, GetEndsWithMethod(), GetValueExpression(body.Type, value?.ToString())); - } - else - be = Expression.Call(body, GetEndsWithMethod(), GetValueExpression(body.Type, value)); + break; + case SyntaxKind.GreaterThan when areBothStrings == false: + be = Expression.GreaterThan(body, GetValueExpression(body.Type, value)); + break; + case SyntaxKind.LessThan when areBothStrings == false: + be = Expression.LessThan(body, GetValueExpression(body.Type, value)); + break; + case SyntaxKind.GreaterOrEqualThan when areBothStrings == false: + be = Expression.GreaterThanOrEqual(body, GetValueExpression(body.Type, value)); + break; + case SyntaxKind.LessOrEqualThan when areBothStrings == false: + be = Expression.LessThanOrEqual(body, GetValueExpression(body.Type, value)); + break; + case SyntaxKind.GreaterThan when areBothStrings: + be = GetGreaterThanExpression(body, valueExpression, value); + break; + case SyntaxKind.LessThan when areBothStrings: + be = GetLessThanExpression(body, valueExpression, value); + break; + case SyntaxKind.GreaterOrEqualThan when areBothStrings: + be = GetGreaterThanOrEqualExpression(body, valueExpression, value); + break; + case SyntaxKind.LessOrEqualThan when areBothStrings: + be = GetLessThanOrEqualExpression(body, valueExpression, value); + break; + case SyntaxKind.Like: + be = Expression.Call(body, GetContainsMethod(), GetValueExpression(body.Type, value)); + break; + case SyntaxKind.NotLike: + be = Expression.Not(Expression.Call(body, GetContainsMethod(), GetValueExpression(body.Type, value))); + break; + case SyntaxKind.StartsWith: + if (body.Type != typeof(string)) + { + body = Expression.Call(body, GetToStringMethod()); + be = Expression.Call(body, GetStartWithMethod(), GetValueExpression(body.Type, value?.ToString())); + } + else + be = Expression.Call(body, GetStartWithMethod(), GetValueExpression(body.Type, value)); - break; - case SyntaxKind.NotStartsWith: - if (body.Type != typeof(string)) - { - body = Expression.Call(body, GetToStringMethod()); - be = Expression.Not(Expression.Call(body, GetStartWithMethod(), GetValueExpression(body.Type, value?.ToString()))); - } - else - be = Expression.Not(Expression.Call(body, GetStartWithMethod(), GetValueExpression(body.Type, value))); + break; + case SyntaxKind.EndsWith: + if (body.Type != typeof(string)) + { + body = Expression.Call(body, GetToStringMethod()); + be = Expression.Call(body, GetEndsWithMethod(), GetValueExpression(body.Type, value?.ToString())); + } + else + be = Expression.Call(body, GetEndsWithMethod(), GetValueExpression(body.Type, value)); - break; - case SyntaxKind.NotEndsWith: - if (body.Type != typeof(string)) - { - body = Expression.Call(body, GetToStringMethod()); - be = Expression.Not(Expression.Call(body, GetEndsWithMethod(), GetValueExpression(body.Type, value?.ToString()))); - } - else - be = Expression.Not(Expression.Call(body, GetEndsWithMethod(), GetValueExpression(body.Type, value))); + break; + case SyntaxKind.NotStartsWith: + if (body.Type != typeof(string)) + { + body = Expression.Call(body, GetToStringMethod()); + be = Expression.Not(Expression.Call(body, GetStartWithMethod(), GetValueExpression(body.Type, value?.ToString()))); + } + else + be = Expression.Not(Expression.Call(body, GetStartWithMethod(), GetValueExpression(body.Type, value))); - break; - default: - return null; - } + break; + case SyntaxKind.NotEndsWith: + if (body.Type != typeof(string)) + { + body = Expression.Call(body, GetToStringMethod()); + be = Expression.Not(Expression.Call(body, GetEndsWithMethod(), GetValueExpression(body.Type, value?.ToString()))); + } + else + be = Expression.Not(Expression.Call(body, GetEndsWithMethod(), GetValueExpression(body.Type, value))); - return Expression.Lambda(be, parameter); + break; + default: + return null; } - private static Expression GetValueExpression(Type type, object? value) - { - if (!GridifyExtensions.EntityFrameworkCompatibilityLayer) - return Expression.Constant(value, type); + return Expression.Lambda(be, parameter); + } - // active parameterized query for EF - const string fieldName = "Value"; - var (instance, type1) = GridifyTypeBuilder.CreateNewObject(type, fieldName, value); - return Expression.PropertyOrField(Expression.Constant(instance, type1), fieldName); - } + private static Expression GetValueExpression(Type type, object? value) + { + if (!GridifyExtensions.EntityFrameworkCompatibilityLayer) + return Expression.Constant(value, type); - private static BinaryExpression GetLessThanOrEqualExpression(Expression body, ValueExpressionSyntax valueExpression, object? value) - { - if (GridifyExtensions.EntityFrameworkCompatibilityLayer) - return Expression.LessThanOrEqual(Expression.Call(null, GetCompareMethod(), body, GetValueExpression(typeof(string), value)), - Expression.Constant(0)); + // active parameterized query for EF + const string fieldName = "Value"; + var (instance, type1) = GridifyTypeBuilder.CreateNewObject(type, fieldName, value); + return Expression.PropertyOrField(Expression.Constant(instance, type1), fieldName); + } - return Expression.LessThanOrEqual(Expression.Call(null, GetCompareMethodWithStringComparison(), body, - GetValueExpression(typeof(string), value), - GetStringComparisonCaseExpression(valueExpression.IsCaseInsensitive)), Expression.Constant(0)); - } + private static BinaryExpression GetLessThanOrEqualExpression(Expression body, ValueExpressionSyntax valueExpression, object? value) + { + if (GridifyExtensions.EntityFrameworkCompatibilityLayer) + return Expression.LessThanOrEqual(Expression.Call(null, GetCompareMethod(), body, GetValueExpression(typeof(string), value)), + Expression.Constant(0)); - private static BinaryExpression GetGreaterThanOrEqualExpression(Expression body, ValueExpressionSyntax valueExpression, object? value) - { - if (GridifyExtensions.EntityFrameworkCompatibilityLayer) - return Expression.GreaterThanOrEqual(Expression.Call(null, GetCompareMethod(), body, GetValueExpression(typeof(string), value)), - Expression.Constant(0)); + return Expression.LessThanOrEqual(Expression.Call(null, GetCompareMethodWithStringComparison(), body, + GetValueExpression(typeof(string), value), + GetStringComparisonCaseExpression(valueExpression.IsCaseInsensitive)), Expression.Constant(0)); + } - return Expression.GreaterThanOrEqual(Expression.Call(null, GetCompareMethodWithStringComparison(), body, - GetValueExpression(typeof(string), value), - GetStringComparisonCaseExpression(valueExpression.IsCaseInsensitive)), Expression.Constant(0)); - } + private static BinaryExpression GetGreaterThanOrEqualExpression(Expression body, ValueExpressionSyntax valueExpression, object? value) + { + if (GridifyExtensions.EntityFrameworkCompatibilityLayer) + return Expression.GreaterThanOrEqual(Expression.Call(null, GetCompareMethod(), body, GetValueExpression(typeof(string), value)), + Expression.Constant(0)); - private static BinaryExpression GetLessThanExpression(Expression body, ValueExpressionSyntax valueExpression, object? value) - { - if (GridifyExtensions.EntityFrameworkCompatibilityLayer) - return Expression.LessThan(Expression.Call(null, GetCompareMethod(), body, GetValueExpression(typeof(string), value)), - Expression.Constant(0)); + return Expression.GreaterThanOrEqual(Expression.Call(null, GetCompareMethodWithStringComparison(), body, + GetValueExpression(typeof(string), value), + GetStringComparisonCaseExpression(valueExpression.IsCaseInsensitive)), Expression.Constant(0)); + } - return Expression.LessThan(Expression.Call(null, GetCompareMethodWithStringComparison(), body, - GetValueExpression(typeof(string), value), - GetStringComparisonCaseExpression(valueExpression.IsCaseInsensitive)), Expression.Constant(0)); - } + private static BinaryExpression GetLessThanExpression(Expression body, ValueExpressionSyntax valueExpression, object? value) + { + if (GridifyExtensions.EntityFrameworkCompatibilityLayer) + return Expression.LessThan(Expression.Call(null, GetCompareMethod(), body, GetValueExpression(typeof(string), value)), + Expression.Constant(0)); - private static BinaryExpression GetGreaterThanExpression(Expression body, ValueExpressionSyntax valueExpression, object? value) - { - if (GridifyExtensions.EntityFrameworkCompatibilityLayer) - return Expression.GreaterThan(Expression.Call(null, GetCompareMethod(), body, GetValueExpression(typeof(string), value)), - Expression.Constant(0)); + return Expression.LessThan(Expression.Call(null, GetCompareMethodWithStringComparison(), body, + GetValueExpression(typeof(string), value), + GetStringComparisonCaseExpression(valueExpression.IsCaseInsensitive)), Expression.Constant(0)); + } - return Expression.GreaterThan(Expression.Call(null, GetCompareMethodWithStringComparison(), body, - GetValueExpression(typeof(string), value), - GetStringComparisonCaseExpression(valueExpression.IsCaseInsensitive)), Expression.Constant(0)); - } + private static BinaryExpression GetGreaterThanExpression(Expression body, ValueExpressionSyntax valueExpression, object? value) + { + if (GridifyExtensions.EntityFrameworkCompatibilityLayer) + return Expression.GreaterThan(Expression.Call(null, GetCompareMethod(), body, GetValueExpression(typeof(string), value)), + Expression.Constant(0)); - private static ConstantExpression GetStringComparisonCaseExpression(bool isCaseInsensitive) - { - return isCaseInsensitive - ? Expression.Constant(StringComparison.OrdinalIgnoreCase) - : Expression.Constant(StringComparison.Ordinal); - } + return Expression.GreaterThan(Expression.Call(null, GetCompareMethodWithStringComparison(), body, + GetValueExpression(typeof(string), value), + GetStringComparisonCaseExpression(valueExpression.IsCaseInsensitive)), Expression.Constant(0)); + } - private static MethodInfo GetAnyMethod(Type @type) => - typeof(Enumerable).GetMethods().Single(m => m.Name == "Any" && m.GetParameters().Length == 2).MakeGenericMethod(@type); + private static ConstantExpression GetStringComparisonCaseExpression(bool isCaseInsensitive) + { + return isCaseInsensitive + ? Expression.Constant(StringComparison.OrdinalIgnoreCase) + : Expression.Constant(StringComparison.Ordinal); + } - private static MethodInfo GetEndsWithMethod() => typeof(string).GetMethod("EndsWith", new[] { typeof(string) })!; + private static MethodInfo GetAnyMethod(Type @type) => + typeof(Enumerable).GetMethods().Single(m => m.Name == "Any" && m.GetParameters().Length == 2).MakeGenericMethod(@type); - private static MethodInfo GetStartWithMethod() => typeof(string).GetMethod("StartsWith", new[] { typeof(string) })!; + private static MethodInfo GetEndsWithMethod() => typeof(string).GetMethod("EndsWith", new[] { typeof(string) })!; - private static MethodInfo GetContainsMethod() => typeof(string).GetMethod("Contains", new[] { typeof(string) })!; - private static MethodInfo GetIsNullOrEmptyMethod() => typeof(string).GetMethod("IsNullOrEmpty", new[] { typeof(string) })!; - private static MethodInfo GetToLowerMethod() => typeof(string).GetMethod("ToLower", new Type[] { })!; + private static MethodInfo GetStartWithMethod() => typeof(string).GetMethod("StartsWith", new[] { typeof(string) })!; - private static MethodInfo GetCompareMethodWithStringComparison() => - typeof(string).GetMethod("Compare", new[] { typeof(string), typeof(string), typeof(StringComparison) })!; + private static MethodInfo GetContainsMethod() => typeof(string).GetMethod("Contains", new[] { typeof(string) })!; + private static MethodInfo GetIsNullOrEmptyMethod() => typeof(string).GetMethod("IsNullOrEmpty", new[] { typeof(string) })!; + private static MethodInfo GetToLowerMethod() => typeof(string).GetMethod("ToLower", new Type[] { })!; - private static MethodInfo GetCompareMethod() => - typeof(string).GetMethod("Compare", new[] { typeof(string), typeof(string) })!; + private static MethodInfo GetCompareMethodWithStringComparison() => + typeof(string).GetMethod("Compare", new[] { typeof(string), typeof(string), typeof(StringComparison) })!; - private static MethodInfo GetToStringMethod() => typeof(object).GetMethod("ToString")!; + private static MethodInfo GetCompareMethod() => + typeof(string).GetMethod("Compare", new[] { typeof(string), typeof(string) })!; + private static MethodInfo GetToStringMethod() => typeof(object).GetMethod("ToString")!; - internal static (Expression> Expression, bool IsNested) - GenerateQuery(ExpressionSyntax expression, IGridifyMapper mapper, bool isParenthesisOpen = false) - { - while (true) - switch (expression.Kind) + + internal static (Expression> Expression, bool IsNested) + GenerateQuery(ExpressionSyntax expression, IGridifyMapper mapper, bool isParenthesisOpen = false) + { + while (true) + switch (expression.Kind) + { + case SyntaxKind.BinaryExpression: { - case SyntaxKind.BinaryExpression: - { - var bExp = expression as BinaryExpressionSyntax; + var bExp = expression as BinaryExpressionSyntax; - if (bExp!.Left is FieldExpressionSyntax && bExp.Right is ValueExpressionSyntax) + if (bExp!.Left is FieldExpressionSyntax && bExp.Right is ValueExpressionSyntax) + { + try { - try - { - return ConvertBinaryExpressionSyntaxToQuery(bExp, mapper) ?? throw new GridifyFilteringException("Invalid expression"); - } - catch (GridifyMapperException) - { - if (mapper.Configuration.IgnoreNotMappedFields) - return (_ => true, false); - - throw; - } + return ConvertBinaryExpressionSyntaxToQuery(bExp, mapper) ?? throw new GridifyFilteringException("Invalid expression"); } - - (Expression> exp, bool isNested) leftQuery; - (Expression> exp, bool isNested) rightQuery; - - if (bExp.Left is ParenthesizedExpressionSyntax lpExp) + catch (GridifyMapperException) { - leftQuery = GenerateQuery(lpExp.Expression, mapper, true); - } - else - leftQuery = GenerateQuery(bExp.Left, mapper); + if (mapper.Configuration.IgnoreNotMappedFields) + return (_ => true, false); + throw; + } + } - if (bExp.Right is ParenthesizedExpressionSyntax rpExp) - rightQuery = GenerateQuery(rpExp.Expression, mapper, true); - else - rightQuery = GenerateQuery(bExp.Right, mapper); - - // check for nested collections - if (isParenthesisOpen && - CheckIfCanMerge(leftQuery, rightQuery, bExp.OperatorToken.Kind) is Expression> mergedResult) - return (mergedResult, true); + (Expression> exp, bool isNested) leftQuery; + (Expression> exp, bool isNested) rightQuery; - var result = bExp.OperatorToken.Kind switch - { - SyntaxKind.And => leftQuery.exp.And(rightQuery.exp), - SyntaxKind.Or => leftQuery.exp.Or(rightQuery.exp), - _ => throw new GridifyFilteringException($"Invalid expression Operator '{bExp.OperatorToken.Kind}'") - }; - return (result, false); - } - case SyntaxKind.ParenthesizedExpression: // first entrypoint only + if (bExp.Left is ParenthesizedExpressionSyntax lpExp) { - var pExp = expression as ParenthesizedExpressionSyntax; - return GenerateQuery(pExp!.Expression, mapper, true); + leftQuery = GenerateQuery(lpExp.Expression, mapper, true); } - default: - throw new GridifyFilteringException($"Invalid expression format '{expression.Kind}'."); - } - } - - private static LambdaExpression? CheckIfCanMerge((Expression> exp, bool isNested) leftQuery, - (Expression> exp, bool isNested) rightQuery, SyntaxKind op) - { - if (leftQuery.isNested && rightQuery.isNested) - { - var leftExp = ParseNestedExpression(leftQuery.exp.Body); - var rightExp = ParseNestedExpression(rightQuery.exp.Body); + else + leftQuery = GenerateQuery(bExp.Left, mapper); - if (leftExp.Arguments.First() is MemberExpression leftMember && - rightExp.Arguments.First() is MemberExpression rightMember && - leftMember.Type == rightMember.Type) - { - // we can merge - var leftLambda = leftExp.Arguments.Last() as LambdaExpression; - var rightLambda = rightExp.Arguments.Last() as LambdaExpression; - if (leftLambda is null || rightLambda is null) - return null; + if (bExp.Right is ParenthesizedExpressionSyntax rpExp) + rightQuery = GenerateQuery(rpExp.Expression, mapper, true); + else + rightQuery = GenerateQuery(bExp.Right, mapper); - var visitedRight = new ReplaceExpressionVisitor(rightLambda.Parameters[0], leftLambda.Parameters[0]) - .Visit(rightLambda.Body); + // check for nested collections + if (isParenthesisOpen && + CheckIfCanMerge(leftQuery, rightQuery, bExp.OperatorToken.Kind) is Expression> mergedResult) + return (mergedResult, true); - var mergedExpression = op switch + var result = bExp.OperatorToken.Kind switch { - SyntaxKind.And => Expression.AndAlso(leftLambda.Body, visitedRight), - SyntaxKind.Or => Expression.OrElse(leftLambda.Body, visitedRight), - _ => throw new InvalidOperationException() + SyntaxKind.And => leftQuery.exp.And(rightQuery.exp), + SyntaxKind.Or => leftQuery.exp.Or(rightQuery.exp), + _ => throw new GridifyFilteringException($"Invalid expression Operator '{bExp.OperatorToken.Kind}'") }; - - var mergedLambda = Expression.Lambda(mergedExpression, leftLambda.Parameters); - var newLambda = GetAnyExpression(leftMember, mergedLambda) as Expression>; - return newLambda; + return (result, false); } + case SyntaxKind.ParenthesizedExpression: // first entrypoint only + { + var pExp = expression as ParenthesizedExpressionSyntax; + return GenerateQuery(pExp!.Expression, mapper, true); + } + default: + throw new GridifyFilteringException($"Invalid expression format '{expression.Kind}'."); } + } - return null; - } - - private static MethodCallExpression ParseNestedExpression(Expression exp) + private static LambdaExpression? CheckIfCanMerge((Expression> exp, bool isNested) leftQuery, + (Expression> exp, bool isNested) rightQuery, SyntaxKind op) + { + if (leftQuery.isNested && rightQuery.isNested) { - return exp switch + var leftExp = ParseNestedExpression(leftQuery.exp.Body); + var rightExp = ParseNestedExpression(rightQuery.exp.Body); + + if (leftExp.Arguments.First() is MemberExpression leftMember && + rightExp.Arguments.First() is MemberExpression rightMember && + leftMember.Type == rightMember.Type) { - BinaryExpression { Right: MethodCallExpression cExp } => cExp, - MethodCallExpression mcExp => mcExp, - _ => throw new InvalidExpressionException() - }; + // we can merge + var leftLambda = leftExp.Arguments.Last() as LambdaExpression; + var rightLambda = rightExp.Arguments.Last() as LambdaExpression; + + if (leftLambda is null || rightLambda is null) + return null; + + var visitedRight = new ReplaceExpressionVisitor(rightLambda.Parameters[0], leftLambda.Parameters[0]) + .Visit(rightLambda.Body); + + var mergedExpression = op switch + { + SyntaxKind.And => Expression.AndAlso(leftLambda.Body, visitedRight), + SyntaxKind.Or => Expression.OrElse(leftLambda.Body, visitedRight), + _ => throw new InvalidOperationException() + }; + + var mergedLambda = Expression.Lambda(mergedExpression, leftLambda.Parameters); + var newLambda = GetAnyExpression(leftMember, mergedLambda) as Expression>; + return newLambda; + } } + + return null; + } + + private static MethodCallExpression ParseNestedExpression(Expression exp) + { + return exp switch + { + BinaryExpression { Right: MethodCallExpression cExp } => cExp, + MethodCallExpression mcExp => mcExp, + _ => throw new InvalidExpressionException() + }; } -} +} \ No newline at end of file diff --git a/src/Gridify/Syntax/ValueExpressionSyntax.cs b/src/Gridify/Syntax/ValueExpressionSyntax.cs index 263571f4..0145cbe9 100644 --- a/src/Gridify/Syntax/ValueExpressionSyntax.cs +++ b/src/Gridify/Syntax/ValueExpressionSyntax.cs @@ -1,25 +1,24 @@ using System.Collections.Generic; -namespace Gridify.Syntax +namespace Gridify.Syntax; + +internal sealed class ValueExpressionSyntax : ExpressionSyntax { - internal sealed class ValueExpressionSyntax : ExpressionSyntax + public ValueExpressionSyntax(SyntaxToken valueToken,bool isCaseInsensitive, bool isNullOrDefault) { - public ValueExpressionSyntax(SyntaxToken valueToken,bool isCaseInsensitive, bool isNullOrDefault) - { - ValueToken = valueToken; - IsCaseInsensitive = isCaseInsensitive; - IsNullOrDefault = isNullOrDefault; - } - - public override SyntaxKind Kind => SyntaxKind.ValueExpression; + ValueToken = valueToken; + IsCaseInsensitive = isCaseInsensitive; + IsNullOrDefault = isNullOrDefault; + } - public override IEnumerable GetChildren() - { - yield return ValueToken; - } + public override SyntaxKind Kind => SyntaxKind.ValueExpression; - public SyntaxToken ValueToken { get; } - public bool IsCaseInsensitive { get; } - public bool IsNullOrDefault { get; } + public override IEnumerable GetChildren() + { + yield return ValueToken; } + + public SyntaxToken ValueToken { get; } + public bool IsCaseInsensitive { get; } + public bool IsNullOrDefault { get; } } \ No newline at end of file diff --git a/test/EntityFrameworkIntegrationTests/DatabaseFixture.cs b/test/EntityFrameworkIntegrationTests/DatabaseFixture.cs index 9bfba6d6..98ea72db 100644 --- a/test/EntityFrameworkIntegrationTests/DatabaseFixture.cs +++ b/test/EntityFrameworkIntegrationTests/DatabaseFixture.cs @@ -2,36 +2,35 @@ using System.Linq; using Xunit; -namespace EntityFrameworkIntegrationTests.cs +namespace EntityFrameworkIntegrationTests.cs; + +public class DatabaseFixture : IDisposable { - public class DatabaseFixture : IDisposable + public DatabaseFixture() { - public DatabaseFixture() - { - _dbContext = new MyDbContext(); - AddTestUsers(); - // ... initialize data in the test database ... - } + _dbContext = new MyDbContext(); + AddTestUsers(); + // ... initialize data in the test database ... + } - public void Dispose() - { - _dbContext.Dispose(); - } + public void Dispose() + { + _dbContext.Dispose(); + } - public MyDbContext _dbContext { get; private set; } - private void AddTestUsers() - { - Assert.Equal(0,_dbContext.Users.Count()); - _dbContext.Users.AddRange( - new User { Id = 1, Name = "ahmad" }, - new User { Id = 2, Name = "ali" }, - new User { Id = 3, Name = "vahid" }, - new User { Id = 4, Name = "hamid" }, - new User { Id = 5, Name = "Hamed" }, - new User { Id = 6, Name = "sara" }, - new User { Id = 7, Name = "Ali" }); + public MyDbContext _dbContext { get; private set; } + private void AddTestUsers() + { + Assert.Equal(0,_dbContext.Users.Count()); + _dbContext.Users.AddRange( + new User { Id = 1, Name = "ahmad" }, + new User { Id = 2, Name = "ali" }, + new User { Id = 3, Name = "vahid" }, + new User { Id = 4, Name = "hamid" }, + new User { Id = 5, Name = "Hamed" }, + new User { Id = 6, Name = "sara" }, + new User { Id = 7, Name = "Ali" }); - _dbContext.SaveChanges(); - } + _dbContext.SaveChanges(); } -} +} \ No newline at end of file diff --git a/test/EntityFrameworkIntegrationTests/GridifyEntityFrameworkTests.cs b/test/EntityFrameworkIntegrationTests/GridifyEntityFrameworkTests.cs index 1327572b..9bf977f3 100644 --- a/test/EntityFrameworkIntegrationTests/GridifyEntityFrameworkTests.cs +++ b/test/EntityFrameworkIntegrationTests/GridifyEntityFrameworkTests.cs @@ -2,83 +2,82 @@ using Gridify; using Xunit; -namespace EntityFrameworkIntegrationTests.cs +namespace EntityFrameworkIntegrationTests.cs; + +public class GridifyEntityFrameworkTests : IClassFixture { - public class GridifyEntityFrameworkTests : IClassFixture + private readonly DatabaseFixture fixture; + private MyDbContext _ctx => fixture._dbContext; + + public GridifyEntityFrameworkTests(DatabaseFixture fixture) + { + this.fixture = fixture; + } + [Fact] + public void EntityFrameworkServiceProviderCachingShouldNotThrowException() { - private readonly DatabaseFixture fixture; - private MyDbContext _ctx => fixture._dbContext; - - public GridifyEntityFrameworkTests(DatabaseFixture fixture) - { - this.fixture = fixture; - } - [Fact] - public void EntityFrameworkServiceProviderCachingShouldNotThrowException() - { - // System.ArgumentException: An item with the same key has already been added. Key: Param_0 - - // arrange - var gq = new GridifyQuery { Filter = "name=n1|name=n2" }; - - _ctx.Users.Gridify(gq); - _ctx.Users.Gridify(gq); - - //act - var exception = Record.Exception(() => _ctx.Users.GridifyQueryable(gq)); - - // assert - Assert.Null(exception); - } - - [Fact] - public void GridifyQueryableDateTimeShouldNotThrowException() - { - // arrange - var gq = new GridifyQuery { OrderBy = "CreateDate" }; - - // act - var exception = Record.Exception(() => _ctx.Users.GridifyQueryable(gq)); - - // assert - Assert.Null(exception); - } - - // issue #27 ef core feedback - // here is working without using the `EnableEntityFrameworkCompatibilityLayer` - // because EF In-Memory provider can support the StringComparison parameter - // but it doesn't work in other sql providers - // https://github.com/alirezanet/Gridify/issues/27#issuecomment-929221457 - [Fact] - public void ApplyFiltering_GreaterThanBetweenTwoStringsInEF() - { - GridifyGlobalConfiguration.EnableEntityFrameworkCompatibilityLayer(); - - var actual = _ctx.Users.ApplyFiltering("name > h").ToList(); - var expected = _ctx.Users.Where(q => string.Compare(q.Name, "h") > 0).ToList(); - - Assert.Equal(expected.Count, actual.Count); - Assert.Equal(expected, actual); - Assert.True(actual.Any()); - } - - [Fact] - public void Builder_BuildEvaluator_Should_Correctly_Evaluate_All_Conditions() - { - var builder = new QueryBuilder() - .AddCondition("name=*a") - .AddCondition("id>3"); - - var evaluator = builder.BuildEvaluator(); - var actual = evaluator(_ctx.Users); - - Assert.True(actual); - Assert.True(builder.Evaluate(_ctx.Users)); - - builder.AddCondition("name=fakeName"); - Assert.False(builder.Evaluate(_ctx.Users)); - - } + // System.ArgumentException: An item with the same key has already been added. Key: Param_0 + + // arrange + var gq = new GridifyQuery { Filter = "name=n1|name=n2" }; + + _ctx.Users.Gridify(gq); + _ctx.Users.Gridify(gq); + + //act + var exception = Record.Exception(() => _ctx.Users.GridifyQueryable(gq)); + // assert + Assert.Null(exception); } -} + + [Fact] + public void GridifyQueryableDateTimeShouldNotThrowException() + { + // arrange + var gq = new GridifyQuery { OrderBy = "CreateDate" }; + + // act + var exception = Record.Exception(() => _ctx.Users.GridifyQueryable(gq)); + + // assert + Assert.Null(exception); + } + + // issue #27 ef core feedback + // here is working without using the `EnableEntityFrameworkCompatibilityLayer` + // because EF In-Memory provider can support the StringComparison parameter + // but it doesn't work in other sql providers + // https://github.com/alirezanet/Gridify/issues/27#issuecomment-929221457 + [Fact] + public void ApplyFiltering_GreaterThanBetweenTwoStringsInEF() + { + GridifyGlobalConfiguration.EnableEntityFrameworkCompatibilityLayer(); + + var actual = _ctx.Users.ApplyFiltering("name > h").ToList(); + var expected = _ctx.Users.Where(q => string.Compare(q.Name, "h") > 0).ToList(); + + Assert.Equal(expected.Count, actual.Count); + Assert.Equal(expected, actual); + Assert.True(actual.Any()); + } + + [Fact] + public void Builder_BuildEvaluator_Should_Correctly_Evaluate_All_Conditions() + { + var builder = new QueryBuilder() + .AddCondition("name=*a") + .AddCondition("id>3"); + + var evaluator = builder.BuildEvaluator(); + var actual = evaluator(_ctx.Users); + + Assert.True(actual); + Assert.True(builder.Evaluate(_ctx.Users)); + + builder.AddCondition("name=fakeName"); + Assert.False(builder.Evaluate(_ctx.Users)); + + } + +} \ No newline at end of file diff --git a/test/EntityFrameworkIntegrationTests/MyDbContext.cs b/test/EntityFrameworkIntegrationTests/MyDbContext.cs index e5197315..8c2dac6a 100644 --- a/test/EntityFrameworkIntegrationTests/MyDbContext.cs +++ b/test/EntityFrameworkIntegrationTests/MyDbContext.cs @@ -1,25 +1,24 @@ using System; using Microsoft.EntityFrameworkCore; -namespace EntityFrameworkIntegrationTests.cs -{ - public class MyDbContext : DbContext - { - public DbSet Users { get; set; } +namespace EntityFrameworkIntegrationTests.cs; - protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) - { - optionsBuilder.UseInMemoryDatabase("myTestDb"); - optionsBuilder.EnableServiceProviderCaching(true); - base.OnConfiguring(optionsBuilder); - } - } +public class MyDbContext : DbContext +{ + public DbSet Users { get; set; } - public class User + protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) { - public int Id { get; set; } - public string Name { get; set; } - public DateTime? CreateDate { get; set; } - public Guid FkGuid { get; set; } + optionsBuilder.UseInMemoryDatabase("myTestDb"); + optionsBuilder.EnableServiceProviderCaching(true); + base.OnConfiguring(optionsBuilder); } +} + +public class User +{ + public int Id { get; set; } + public string Name { get; set; } + public DateTime? CreateDate { get; set; } + public Guid FkGuid { get; set; } } \ No newline at end of file diff --git a/test/EntityFrameworkSqlProviderIntegrationTests/GridifyEntityFrameworkSqlTests.cs b/test/EntityFrameworkSqlProviderIntegrationTests/GridifyEntityFrameworkSqlTests.cs index c50ec031..f6104c08 100644 --- a/test/EntityFrameworkSqlProviderIntegrationTests/GridifyEntityFrameworkSqlTests.cs +++ b/test/EntityFrameworkSqlProviderIntegrationTests/GridifyEntityFrameworkSqlTests.cs @@ -6,73 +6,72 @@ using Microsoft.EntityFrameworkCore; using Xunit; -namespace EntityFrameworkIntegrationTests.cs +namespace EntityFrameworkIntegrationTests.cs; + +public class GridifyEntityFrameworkTests { - public class GridifyEntityFrameworkTests - { - private readonly MyDbContext _dbContext; + private readonly MyDbContext _dbContext; - public GridifyEntityFrameworkTests() - { - _dbContext = new MyDbContext(); - } + public GridifyEntityFrameworkTests() + { + _dbContext = new MyDbContext(); + } - // issue #24, - // https://github.com/alirezanet/Gridify/issues/24 - [Fact] - public void ApplyFiltering_GeneratedSqlShouldNotCreateParameterizedQuery_WhenCompatibilityLayerIsDisable_SqlServerProvider() - { - var actual = _dbContext.Users.ApplyFiltering("name = vahid").ToQueryString(); - var expected = _dbContext.Users.Where(q => q.Name == "vahid").ToQueryString(); + // issue #24, + // https://github.com/alirezanet/Gridify/issues/24 + [Fact] + public void ApplyFiltering_GeneratedSqlShouldNotCreateParameterizedQuery_WhenCompatibilityLayerIsDisable_SqlServerProvider() + { + var actual = _dbContext.Users.ApplyFiltering("name = vahid").ToQueryString(); + var expected = _dbContext.Users.Where(q => q.Name == "vahid").ToQueryString(); - Assert.StartsWith("SELECT [u].[Id]", expected); - Assert.StartsWith("SELECT [u].[Id]", actual); - } + Assert.StartsWith("SELECT [u].[Id]", expected); + Assert.StartsWith("SELECT [u].[Id]", actual); + } - // issue #24, - // https://github.com/alirezanet/Gridify/issues/24 - [Fact] - public void ApplyFiltering_GeneratedSqlShouldCreateParameterizedQuery_SqlServerProvider() - { - GridifyGlobalConfiguration.EnableEntityFrameworkCompatibilityLayer(); + // issue #24, + // https://github.com/alirezanet/Gridify/issues/24 + [Fact] + public void ApplyFiltering_GeneratedSqlShouldCreateParameterizedQuery_SqlServerProvider() + { + GridifyGlobalConfiguration.EnableEntityFrameworkCompatibilityLayer(); - var name = "vahid"; - var expected = _dbContext.Users.Where(q => q.Name == name).ToQueryString(); + var name = "vahid"; + var expected = _dbContext.Users.Where(q => q.Name == name).ToQueryString(); - var actual = _dbContext.Users.ApplyFiltering("name = vahid").ToQueryString(); + var actual = _dbContext.Users.ApplyFiltering("name = vahid").ToQueryString(); - Assert.StartsWith("DECLARE @__Value", actual); - Assert.StartsWith("DECLARE @__name", expected); - } + Assert.StartsWith("DECLARE @__Value", actual); + Assert.StartsWith("DECLARE @__name", expected); + } - // issue #27 ef core sqlServer feedback, and issue #24 - // https://github.com/alirezanet/Gridify/issues/27#issuecomment-929221457 - [Fact] - public void ApplyFiltering_GreaterThanBetweenTwoStringsInEF_SqlServerProvider_EnableCompatibilityLayer() - { - GridifyGlobalConfiguration.EnableEntityFrameworkCompatibilityLayer(); - var sb = new StringBuilder(); - sb.AppendLine("DECLARE @__Value_0 nvarchar(4000) = N'h';"); - sb.AppendLine("SELECT [u].[Id], [u].[CreateDate], [u].[FkGuid], [u].[Name]"); - sb.AppendLine("FROM [Users] AS [u]"); - sb.AppendLine("WHERE [u].[Name] > @__Value_0"); + // issue #27 ef core sqlServer feedback, and issue #24 + // https://github.com/alirezanet/Gridify/issues/27#issuecomment-929221457 + [Fact] + public void ApplyFiltering_GreaterThanBetweenTwoStringsInEF_SqlServerProvider_EnableCompatibilityLayer() + { + GridifyGlobalConfiguration.EnableEntityFrameworkCompatibilityLayer(); + var sb = new StringBuilder(); + sb.AppendLine("DECLARE @__Value_0 nvarchar(4000) = N'h';"); + sb.AppendLine("SELECT [u].[Id], [u].[CreateDate], [u].[FkGuid], [u].[Name]"); + sb.AppendLine("FROM [Users] AS [u]"); + sb.AppendLine("WHERE [u].[Name] > @__Value_0"); - var actual = _dbContext.Users.ApplyFiltering("name > h").ToQueryString(); - Assert.True(string.Compare( - sb.ToString(), - actual, - CultureInfo.CurrentCulture, - CompareOptions.IgnoreCase | CompareOptions.IgnoreSymbols) == 0); - } + var actual = _dbContext.Users.ApplyFiltering("name > h").ToQueryString(); + Assert.True(string.Compare( + sb.ToString(), + actual, + CultureInfo.CurrentCulture, + CompareOptions.IgnoreCase | CompareOptions.IgnoreSymbols) == 0); + } - // issue #27 ef core sqlServer feedback - // https://github.com/alirezanet/Gridify/issues/27#issuecomment-929221457dd - [Fact] - public void ApplyFiltering_GreaterThanBetweenTwoStringsInEF_SqlServerProvider_ShouldThrowErrorWhenCompatibilityLayerIsDisabled() - { - GridifyGlobalConfiguration.DisableEntityFrameworkCompatibilityLayer(); - Assert.Throws(() => _dbContext.Users.ApplyFiltering("name > h").ToQueryString()); - } + // issue #27 ef core sqlServer feedback + // https://github.com/alirezanet/Gridify/issues/27#issuecomment-929221457dd + [Fact] + public void ApplyFiltering_GreaterThanBetweenTwoStringsInEF_SqlServerProvider_ShouldThrowErrorWhenCompatibilityLayerIsDisabled() + { + GridifyGlobalConfiguration.DisableEntityFrameworkCompatibilityLayer(); + Assert.Throws(() => _dbContext.Users.ApplyFiltering("name > h").ToQueryString()); } } \ No newline at end of file diff --git a/test/EntityFrameworkSqlProviderIntegrationTests/Interceptors.cs b/test/EntityFrameworkSqlProviderIntegrationTests/Interceptors.cs index 32dc5bb0..030c2286 100644 --- a/test/EntityFrameworkSqlProviderIntegrationTests/Interceptors.cs +++ b/test/EntityFrameworkSqlProviderIntegrationTests/Interceptors.cs @@ -6,149 +6,148 @@ using System.Threading.Tasks; using Microsoft.EntityFrameworkCore.Diagnostics; -namespace EntityFrameworkIntegrationTests.cs +namespace EntityFrameworkIntegrationTests.cs; + +public class SuppressConnectionInterceptor : DbConnectionInterceptor { - public class SuppressConnectionInterceptor : DbConnectionInterceptor + public override ValueTask ConnectionOpeningAsync(DbConnection connection, ConnectionEventData eventData, + InterceptionResult result, + CancellationToken cancellationToken = new()) { - public override ValueTask ConnectionOpeningAsync(DbConnection connection, ConnectionEventData eventData, - InterceptionResult result, - CancellationToken cancellationToken = new()) - { - result = InterceptionResult.Suppress(); - return base.ConnectionOpeningAsync(connection, eventData, result, cancellationToken); - } - - public override InterceptionResult ConnectionOpening(DbConnection connection, ConnectionEventData eventData, InterceptionResult result) - { - result = InterceptionResult.Suppress(); - return base.ConnectionOpening(connection, eventData, result); - } + result = InterceptionResult.Suppress(); + return base.ConnectionOpeningAsync(connection, eventData, result, cancellationToken); } - public class EmptyMessageDataReader : DbDataReader + public override InterceptionResult ConnectionOpening(DbConnection connection, ConnectionEventData eventData, InterceptionResult result) { + result = InterceptionResult.Suppress(); + return base.ConnectionOpening(connection, eventData, result); + } +} + +public class EmptyMessageDataReader : DbDataReader +{ - private readonly List _users = new List(); + private readonly List _users = new List(); - public EmptyMessageDataReader() - { - } + public EmptyMessageDataReader() + { + } - public override int FieldCount - => 0; + public override int FieldCount + => 0; - public override int RecordsAffected - => 0; + public override int RecordsAffected + => 0; - public override bool HasRows - => false; + public override bool HasRows + => false; - public override bool IsClosed - => true; + public override bool IsClosed + => true; - public override int Depth - => 0; + public override int Depth + => 0; - public override bool Read() - => false; + public override bool Read() + => false; - public override int GetInt32(int ordinal) - => 0; + public override int GetInt32(int ordinal) + => 0; - public override bool IsDBNull(int ordinal) - => false; + public override bool IsDBNull(int ordinal) + => false; - public override string GetString(int ordinal) - => "suppressed message"; + public override string GetString(int ordinal) + => "suppressed message"; - public override bool GetBoolean(int ordinal) - => true; + public override bool GetBoolean(int ordinal) + => true; - public override byte GetByte(int ordinal) - => 0; + public override byte GetByte(int ordinal) + => 0; - public override long GetBytes(int ordinal, long dataOffset, byte[] buffer, int bufferOffset, int length) - => 0; + public override long GetBytes(int ordinal, long dataOffset, byte[] buffer, int bufferOffset, int length) + => 0; - public override char GetChar(int ordinal) - => '\0'; + public override char GetChar(int ordinal) + => '\0'; - public override long GetChars(int ordinal, long dataOffset, char[] buffer, int bufferOffset, int length) - => 0; + public override long GetChars(int ordinal, long dataOffset, char[] buffer, int bufferOffset, int length) + => 0; - public override string GetDataTypeName(int ordinal) - => string.Empty; + public override string GetDataTypeName(int ordinal) + => string.Empty; - public override DateTime GetDateTime(int ordinal) - => DateTime.Now; + public override DateTime GetDateTime(int ordinal) + => DateTime.Now; - public override decimal GetDecimal(int ordinal) - => 0; + public override decimal GetDecimal(int ordinal) + => 0; - public override double GetDouble(int ordinal) - => 0; + public override double GetDouble(int ordinal) + => 0; - public override Type GetFieldType(int ordinal) - => typeof(User); + public override Type GetFieldType(int ordinal) + => typeof(User); - public override float GetFloat(int ordinal) - => 0; + public override float GetFloat(int ordinal) + => 0; - public override Guid GetGuid(int ordinal) - => Guid.Empty; + public override Guid GetGuid(int ordinal) + => Guid.Empty; - public override short GetInt16(int ordinal) - => 0; + public override short GetInt16(int ordinal) + => 0; - public override long GetInt64(int ordinal) - => 0; + public override long GetInt64(int ordinal) + => 0; - public override string GetName(int ordinal) - => ""; + public override string GetName(int ordinal) + => ""; - public override int GetOrdinal(string name) - => 0; + public override int GetOrdinal(string name) + => 0; - public override object GetValue(int ordinal) - => new object(); + public override object GetValue(int ordinal) + => new object(); - public override int GetValues(object[] values) - => 0; + public override int GetValues(object[] values) + => 0; - public override object this[int ordinal] - => new object(); + public override object this[int ordinal] + => new object(); - public override object this[string name] - => new object(); + public override object this[string name] + => new object(); - public override bool NextResult() - => false; + public override bool NextResult() + => false; - public override IEnumerator GetEnumerator() - => _users.GetEnumerator(); + public override IEnumerator GetEnumerator() + => _users.GetEnumerator(); +} + +public class SuppressCommandResultInterceptor : DbCommandInterceptor +{ + public override InterceptionResult ReaderExecuting( + DbCommand command, + CommandEventData eventData, + InterceptionResult result) + { + result = InterceptionResult.SuppressWithResult(new EmptyMessageDataReader()); + + return result; } - public class SuppressCommandResultInterceptor : DbCommandInterceptor + public override ValueTask> ReaderExecutingAsync( + DbCommand command, + CommandEventData eventData, + InterceptionResult result, + CancellationToken cancellationToken = default) { - public override InterceptionResult ReaderExecuting( - DbCommand command, - CommandEventData eventData, - InterceptionResult result) - { - result = InterceptionResult.SuppressWithResult(new EmptyMessageDataReader()); - - return result; - } - - public override ValueTask> ReaderExecutingAsync( - DbCommand command, - CommandEventData eventData, - InterceptionResult result, - CancellationToken cancellationToken = default) - { - result = InterceptionResult.SuppressWithResult(new EmptyMessageDataReader()); - - return new ValueTask>(result); - } + result = InterceptionResult.SuppressWithResult(new EmptyMessageDataReader()); + + return new ValueTask>(result); } } \ No newline at end of file diff --git a/test/EntityFrameworkSqlProviderIntegrationTests/MyDbContext.cs b/test/EntityFrameworkSqlProviderIntegrationTests/MyDbContext.cs index c0b0f4a4..d5cf7530 100644 --- a/test/EntityFrameworkSqlProviderIntegrationTests/MyDbContext.cs +++ b/test/EntityFrameworkSqlProviderIntegrationTests/MyDbContext.cs @@ -2,27 +2,26 @@ using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Diagnostics; -namespace EntityFrameworkIntegrationTests.cs -{ - public class MyDbContext : DbContext - { - public DbSet Users { get; set; } +namespace EntityFrameworkIntegrationTests.cs; - protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) - { - optionsBuilder.UseSqlServer("Server=myServerAddress;Database=myDataBase;User Id=myUsername;Password=myPassword;"); - optionsBuilder.AddInterceptors(new SuppressCommandResultInterceptor()); - optionsBuilder.AddInterceptors(new SuppressConnectionInterceptor()); - optionsBuilder.EnableServiceProviderCaching(); - base.OnConfiguring(optionsBuilder); - } - } +public class MyDbContext : DbContext +{ + public DbSet Users { get; set; } - public class User + protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) { - public int Id { get; set; } - public string Name { get; set; } - public DateTime? CreateDate { get; set; } - public Guid FkGuid { get; set; } + optionsBuilder.UseSqlServer("Server=myServerAddress;Database=myDataBase;User Id=myUsername;Password=myPassword;"); + optionsBuilder.AddInterceptors(new SuppressCommandResultInterceptor()); + optionsBuilder.AddInterceptors(new SuppressConnectionInterceptor()); + optionsBuilder.EnableServiceProviderCaching(); + base.OnConfiguring(optionsBuilder); } +} + +public class User +{ + public int Id { get; set; } + public string Name { get; set; } + public DateTime? CreateDate { get; set; } + public Guid FkGuid { get; set; } } \ No newline at end of file diff --git a/test/Gridify.Tests/GridifyExtensionsShould.cs b/test/Gridify.Tests/GridifyExtensionsShould.cs index af938f83..c711caf1 100644 --- a/test/Gridify.Tests/GridifyExtensionsShould.cs +++ b/test/Gridify.Tests/GridifyExtensionsShould.cs @@ -4,856 +4,891 @@ using System.Linq; using Xunit; -namespace Gridify.Tests +namespace Gridify.Tests; + +public class GridifyExtensionsShould { - public class GridifyExtensionsShould - { - private readonly List _fakeRepository; - - public GridifyExtensionsShould() - { - _fakeRepository = new List(GetSampleData()); - } - - - #region "ApplyFiltering" - - [Fact] - public void ApplyFiltering_SingleField() - { - var actual = _fakeRepository.AsQueryable() - .ApplyFiltering("name=John") - .ToList(); - var expected = _fakeRepository.Where(q => q.Name == "John").ToList(); - Assert.Equal(expected.Count, actual.Count); - Assert.Equal(expected, actual); - Assert.True(actual.Any()); - } - - - [Fact] - public void ApplyFiltering_SingleField_GridifyQuery() - { - var gq = new GridifyQuery { Filter = "name=John" }; - var actual = _fakeRepository.AsQueryable() - .ApplyFiltering(gq) - .ToList(); - var expected = _fakeRepository.Where(q => q.Name == "John").ToList(); - Assert.Equal(expected.Count, actual.Count); - Assert.Equal(expected, actual); - Assert.True(actual.Any()); - } - - [Fact] - public void ApplyFiltering_NullHandlingUsingCustomConvertor() - { - // create custom mapper - var gm = new GridifyMapper(q => q.AllowNullSearch = false).GenerateMappings(); - - // map any string to related property , also use Client convertor to handle custom scenarios - gm.AddMap("date", g => g.MyDateTime!, q => (q == "null" ? null : q)!); - - var actual = _fakeRepository.AsQueryable() - .ApplyFiltering("date=null", gm) - .ToList(); - - var expected = _fakeRepository.Where(q => q.MyDateTime == null).ToList(); - Assert.Equal(expected.Count, actual.Count); - Assert.Equal(expected, actual); - Assert.True(actual.Any()); - } - - [Fact] - public void ApplyFiltering_NullHandlingUsingMapper() - { - // create custom mapper - var gm = new GridifyMapper(q => q.AllowNullSearch = true) // default is true - .GenerateMappings(); - - var actual = _fakeRepository.AsQueryable() - .ApplyFiltering("MyDateTime=null", gm) - .ToList(); - - var expected = _fakeRepository.Where(q => q.MyDateTime == null).ToList(); - Assert.Equal(expected.Count, actual.Count); - Assert.Equal(expected, actual); - Assert.True(actual.Any()); - } - - [Fact] - public void ApplyFiltering_DisableNullHandlingUsingMapper() - { - // create custom mapper - var gm = new GridifyMapper(q => q.AllowNullSearch = false) // default is true - .GenerateMappings(); - - var actual = _fakeRepository.AsQueryable() - .ApplyFiltering("MyDateTime=null", gm) - .ToList(); - - var expected = _fakeRepository.Where(q => q.MyDateTime.ToString() == "null").ToList(); - Assert.Equal(expected, actual); - Assert.Empty(actual); - } - - [Fact] - public void ApplyFiltering_DuplicatefieldName() - { - const string gq = "name=John|name=Sara"; - var actual = _fakeRepository.AsQueryable() - .ApplyFiltering(gq) - .ToList(); - var expected = _fakeRepository.Where(q => (q.Name == "John") | (q.Name == "Sara")).ToList(); - Assert.Equal(expected.Count, actual.Count); - Assert.Equal(expected, actual); - Assert.True(actual.Any()); - } - - - [Theory] - [InlineData(@" name =\(LI\,AM\)", "(LI,AM)")] - [InlineData(@" name =jessi=ca", "jessi=ca")] - [InlineData(@" name =\\Liam", @"\Liam")] - [InlineData(@" name =LI \| AM", @"LI | AM")] - public void ApplyFiltering_EscapeSpecialCharacters(string textFilter, string rawText) - { - var gq = new GridifyQuery { Filter = textFilter }; - var actual = _fakeRepository.AsQueryable() - .ApplyFiltering(gq) - .ToList(); - var expected = _fakeRepository.Where(q => q.Name == rawText).ToList(); - Assert.Equal(expected.Count, actual.Count); - Assert.Equal(expected, actual); - Assert.True(actual.Any()); - } - - [Fact] - public void ApplyFiltering_ParenthesisQueryWithoutEscapeShouldThrowException() - { - var gq = new GridifyQuery { Filter = @"name=(LI,AM)" }; - Action act = () => _fakeRepository.AsQueryable() - .ApplyFiltering(gq); - - Assert.Throws(act); - } - - - [Fact] - public void ApplyFiltering_SingleGuidField() - { - var guidString = "e2cec5dd-208d-4bb5-a852-50008f8ba366"; - var guid = Guid.Parse(guidString); - var gq = new GridifyQuery { Filter = "myGuid=" + guidString }; - var actual = _fakeRepository.AsQueryable() - .ApplyFiltering(gq) - .ToList(); - var expected = _fakeRepository.Where(q => q.MyGuid == guid).ToList(); - Assert.Equal(expected.Count, actual.Count); - Assert.Equal(expected, actual); - Assert.True(actual.Any()); - } - - [Fact] - public void ApplyFiltering_SingleBrokenGuidField() - { - var brokenGuidString = "e2cec5dd-208d-4bb5-a852-"; - var gq = new GridifyQuery { Filter = "myGuid=" + brokenGuidString }; - - var actual = _fakeRepository.AsQueryable() - .ApplyFiltering(gq) - .ToList(); - - Assert.False(actual.Any()); - } - - - [Fact] - public void ApplyFiltering_SingleBrokenGuidField_NotEqual() - { - var brokenGuidString = "e2cec5dd-208d-4bb5-a852-"; - var gq = new GridifyQuery { Filter = "myGuid!=" + brokenGuidString }; - - var actual = _fakeRepository.AsQueryable() - .ApplyFiltering(gq) - .ToList(); - - Assert.True(actual.Any()); - } - - - [Fact] - public void ApplyFiltering_InvalidFilterExpressionShouldThrowException() - { - var gq = new GridifyQuery { Filter = "=guid,d=" }; - Assert.Throws(() => - _fakeRepository.AsQueryable().ApplyFiltering(gq).ToList()); - } - - [Fact] - public void ApplyFiltering_InvalidCharacterShouldThrowException() - { - var gq = new GridifyQuery { Filter = "@name=ali" }; - Assert.Throws(() => - _fakeRepository.AsQueryable().ApplyFiltering(gq).ToList()); - } - - [Theory] - [InlineData(null)] - [InlineData("")] - public void ApplyFiltering_NullOrEmptyFilter_ShouldSkip(string? filter) - { - var gq = new GridifyQuery(); - if (filter == null) - gq = null; - else - gq.Filter = filter; - - var actual = _fakeRepository.AsQueryable() - .ApplyFiltering(gq) - .ToList(); - - var expected = _fakeRepository.ToList(); - Assert.Equal(expected.Count, actual.Count); - Assert.Equal(expected, actual); - Assert.True(actual.Any()); - } - - [Fact] - public void ApplyFiltering_CustomConvertor() - { - const string gq = "name=liam"; - var gm = new GridifyMapper() - .GenerateMappings() - .AddMap("name", q => q.Name, q => q.ToUpper()); // using client side Custom convertor - - var actual = _fakeRepository.AsQueryable() - .ApplyFiltering(gq, gm) - .ToList(); - - var expected = _fakeRepository.Where(q => q.Name == "LIAM").ToList(); - Assert.Equal(expected.Count, actual.Count); - Assert.Equal(expected, actual); - Assert.True(actual.Any()); - } - - [Fact] - public void ApplyFiltering_StartsWith() - { - const string gq = "name^A"; - - var actual = _fakeRepository.AsQueryable() - .ApplyFiltering(gq) - .ToList(); - - var expected = _fakeRepository.Where(q => q.Name!.StartsWith("A")).ToList(); - - Assert.Equal(expected.Count, actual.Count); - Assert.Equal(expected, actual); - Assert.True(actual.Any()); - } - - [Fact] - public void ApplyEverything_EmptyGridifyQuery() - { - var gq = new GridifyQuery(); - - var actual = _fakeRepository.AsQueryable() - .ApplyFilteringOrderingPaging(gq) - .ToList(); - - var expected = _fakeRepository.Skip(0).Take(GridifyExtensions.DefaultPageSize).ToList(); - - Assert.Equal(expected.Count, actual.Count); - Assert.Equal(expected, actual); - Assert.True(actual.Any()); - } - - - [Fact] - public void ApplyFiltering_StartsWithOnNotStringsShouldNotThrowError() - { - const string gq = "Id^2"; - - var actual = _fakeRepository.AsQueryable() - .ApplyFiltering(gq) - .ToList(); - - var expected = _fakeRepository.Where(q => q.Id.ToString().StartsWith("2")).ToList(); - - Assert.Equal(expected.Count, actual.Count); - Assert.Equal(expected, actual); - Assert.True(actual.Any()); - } - - [Fact] - public void ApplyFiltering_NotStartsWith() - { - const string gq = "name!^A"; - - var actual = _fakeRepository.AsQueryable() - .ApplyFiltering(gq) - .ToList(); - - var expected = _fakeRepository.Where(q => !q.Name!.StartsWith("A")).ToList(); - - Assert.Equal(expected.Count, actual.Count); - Assert.Equal(expected, actual); - Assert.True(actual.Any()); - } - - [Fact] - public void ApplyFiltering_EndsWith() - { - const string gq = "name $ li"; - - var actual = _fakeRepository.AsQueryable() - .ApplyFiltering(gq) - .ToList(); - - var expected = _fakeRepository.Where(q => q.Name!.EndsWith("li")).ToList(); - Assert.Equal(expected.Count, actual.Count); - Assert.Equal(expected, actual); - Assert.True(actual.Any()); - } - - [Fact] - public void ApplyFiltering_NotEndsWith() - { - const string gq = "name !$ i"; - - var actual = _fakeRepository.AsQueryable() - .ApplyFiltering(gq) - .ToList(); - - var expected = _fakeRepository.Where(q => !q.Name!.EndsWith("i")).ToList(); - - Assert.Equal(expected.Count, actual.Count); - Assert.Equal(expected, actual); - Assert.True(actual.Any()); - } - - [Fact] - public void ApplyFiltering_MultipleCondition() - { - var actual = _fakeRepository.AsQueryable() - .ApplyFiltering("name=Jack|name=Rose|id>=7") - .ToList(); - var expected = _fakeRepository.Where(q => q.Name == "Jack" || q.Name == "Rose" || q.Id >= 7).ToList(); - Assert.Equal(expected.Count, actual.Count); - Assert.Equal(expected, actual); - Assert.True(actual.Any()); - } - - [Fact] - public void ApplyFiltering_ComplexWithParenthesis() - { - var actual = _fakeRepository.AsQueryable() - .ApplyFiltering("(name=*J|name=*S),(Id<5)") - .ToList(); - var expected = _fakeRepository.Where(q => (q.Name!.Contains("J") || q.Name.Contains("S")) && q.Id < 5).ToList(); - Assert.Equal(expected.Count, actual.Count); - Assert.Equal(expected, actual); - Assert.True(actual.Any()); - } - - [Fact] - public void ApplyFiltering_NestedParenthesisWithSpace() - { - // we shouldn't add spaces for values - var gq = new GridifyQuery { Filter = " ( name =*J| ( name =*S , Id <5 ) )" }; - var actual = _fakeRepository.AsQueryable() - .ApplyFiltering(gq) - .ToList(); - var expected = _fakeRepository.Where(q => q.Name!.Contains("J") || q.Name.Contains("S") && q.Id < 5).ToList(); - Assert.Equal(expected.Count, actual.Count); - Assert.Equal(expected, actual); - Assert.True(actual.Any()); - } - - [Fact] - public void ApplyFiltering_UsingChildClassProperty() - { - var gq = new GridifyQuery { Filter = "Child_Name=Bob" }; - var gm = new GridifyMapper() - .GenerateMappings() - .AddMap("Child_name", q => q.ChildClass!.Name); - - var actual = _fakeRepository.AsQueryable() - .Where(q => q.ChildClass != null) - .ApplyFiltering(gq, gm) - .ToList(); - - var expected = _fakeRepository.Where(q => q.ChildClass is { Name: "Bob" }).ToList(); - Assert.Equal(expected.Count, actual.Count); - Assert.Equal(expected, actual); - Assert.True(actual.Any()); - } - - [Fact] - public void ApplyFiltering_CaseInsensitiveSearchUsingConvertor() //issue #21 - { - var gq = new GridifyQuery { Filter = "name=BOB" }; - var gm = new GridifyMapper() - .AddMap("name", q => q.Name!.ToLower(), c => c.ToLower()); - - var actual = _fakeRepository.AsQueryable() - .ApplyFiltering(gq, gm) - .ToList(); - - var expected = _fakeRepository.Where(q => q.Name!.ToLower() == "BOB".ToLower()).ToList(); - Assert.Equal(expected.Count, actual.Count); - Assert.Equal(expected, actual); - Assert.True(actual.Any()); - } - - [Fact] - public void ApplyFiltering_CaseInsensitiveSearch() //issue #21 - { - var gq = new GridifyQuery { Filter = "name=BOB/i " }; - - var actual = _fakeRepository.AsQueryable() - .ApplyFiltering(gq) - .ToList(); - - var expected = _fakeRepository.Where(q => q.Name!.ToLower() == "BOB".ToLower()).ToList(); - Assert.Equal(expected.Count, actual.Count); - Assert.Equal(expected, actual); - Assert.True(actual.Any()); - } - - [Fact] - public void ApplyFiltering_EscapeCaseInsensitiveSearch() //issue #21 - { - var actual = _fakeRepository.AsQueryable() - .ApplyFiltering(@"name=Case\/i") - .ToList(); - - var expected = _fakeRepository.Where(q => q.Name == "Case/i").ToList(); - Assert.Equal(expected.Count, actual.Count); - Assert.Equal(expected, actual); - Assert.True(actual.Any()); - } - - [Fact] - public void ApplyFiltering_CaseInsensitiveOperatorAtTheBeginningOfValue_ShouldIgnore() - { - var actual = _fakeRepository.AsQueryable() - .ApplyFiltering(@"name=\/icase") - .ToList(); - - var expected = _fakeRepository.Where(q => q.Name == "/icase").ToList(); - Assert.Equal(expected.Count, actual.Count); - Assert.Equal(expected, actual); - Assert.False(actual.Any()); - } - - [Fact] // issue #27 - public void ApplyFiltering_GreaterThanBetweenTwoStrings() - { - var actual = _fakeRepository.AsQueryable().ApplyFiltering("name > ali").ToList(); - - var expected = _fakeRepository.Where(q => string.Compare(q.Name, "ali", StringComparison.Ordinal) > 0).ToList(); - Assert.Equal(expected.Count, actual.Count); - Assert.Equal(expected, actual); - Assert.True(actual.Any()); - } - - - [Fact] // issue #27 - public void ApplyFiltering_LessThanBetweenTwoStrings() - { - var actual = _fakeRepository.AsQueryable().ApplyFiltering("name < v").ToList(); - - var expected = _fakeRepository.Where(q => string.Compare(q.Name, "v", StringComparison.Ordinal) < 0).ToList(); - Assert.Equal(expected.Count, actual.Count); - Assert.Equal(expected, actual); - Assert.True(actual.Any()); - } - - [Fact] // issue #27 - public void ApplyFiltering_LessThanOrEqualBetweenTwoStrings() - { - var actual = _fakeRepository.AsQueryable().ApplyFiltering("name <= l").ToList(); - var expected = _fakeRepository.Where(q => string.Compare(q.Name, "l", StringComparison.Ordinal) <= 0).ToList(); - - Assert.Equal(expected.Count, actual.Count); - Assert.Equal(expected, actual); - Assert.True(actual.Any()); - } - - [Fact] // issue #27 - public void ApplyFiltering_GreaterThanOrEqualBetweenTwoStrings() - { - var actual = _fakeRepository.AsQueryable().ApplyFiltering("name >= c").ToList(); - var expected = _fakeRepository.Where(q => string.Compare(q.Name, "c", StringComparison.Ordinal) >= 0).ToList(); - - Assert.Equal(expected.Count, actual.Count); - Assert.Equal(expected, actual); - Assert.True(actual.Any()); - } - - [Fact] // issue #27 - public void ApplyFiltering_GreaterThanOrEqual_CaseInsensitive_BetweenTwoStrings() - { - var actual = _fakeRepository.AsQueryable().ApplyFiltering("name >= j/i").ToList(); - var expected = _fakeRepository.Where(q => string.Compare(q.Name, "j", StringComparison.OrdinalIgnoreCase) >= 0).ToList(); - - Assert.Equal(expected.Count, actual.Count); - Assert.Equal(expected, actual); - Assert.True(actual.Any()); - } - - [Fact] // issue #25 - public void ApplyFiltering_Equal_ProcessingNullOrDefaultValue() - { - var actual = _fakeRepository.AsQueryable().ApplyFiltering("tag=").ToList(); - var expected = _fakeRepository.Where(q => string.IsNullOrEmpty(q.Tag)).ToList(); - var expected2 = _fakeRepository.Where(q => q.Tag is null or "").ToList(); - - Assert.Equal(expected.Count, actual.Count); - Assert.Equal(expected2.Count, actual.Count); - Assert.Equal(expected, actual); - Assert.Equal(expected2, actual); - Assert.True(actual.Any()); - } - - [Fact] // issue #25 - public void ApplyFiltering_NotEqual_ProcessingNullOrDefaultValue() - { - var actual = _fakeRepository.AsQueryable().ApplyFiltering("tag!=").ToList(); - var expected = _fakeRepository.Where(q => !string.IsNullOrEmpty(q.Tag)).ToList(); - var expected2 = _fakeRepository.Where(q => q.Tag is not null && q.Tag != "").ToList(); - - Assert.Equal(expected.Count, actual.Count); - Assert.Equal(expected2.Count, actual.Count); - Assert.Equal(expected, actual); - Assert.Equal(expected2, actual); - Assert.True(actual.Any()); - } - - [Fact] // issue #25 - public void ApplyFiltering_Equal_ProcessingNullOrDefaultValueNonStringTypes() - { - var actual = _fakeRepository.AsQueryable().ApplyFiltering("myGuid=").ToList(); - var expected = _fakeRepository.Where(q => q.MyGuid == default).ToList(); - - Assert.Equal(expected.Count, actual.Count); - Assert.Equal(expected, actual); - Assert.True(actual.Any()); - } - - [Fact] // issue #25 - public void ApplyFiltering_NotEqual_ProcessingNullOrDefaultValueNonStringTypes() - { - var actual = _fakeRepository.AsQueryable().ApplyFiltering("myGuid!=").ToList(); - var expected = _fakeRepository.Where(q => q.MyGuid != default).ToList(); - - Assert.Equal(expected.Count, actual.Count); - Assert.Equal(expected, actual); - Assert.True(actual.Any()); - } - - [Fact] // issue #33 - public void ApplyFiltering_WithSpaces() - { - var actual = _fakeRepository.AsQueryable().ApplyFiltering("name =ali reza").ToList(); - var expected = _fakeRepository.Where(q => q.Name == "ali reza" ).ToList(); - - Assert.Equal(expected.Count, actual.Count); - Assert.Equal(expected, actual); - Assert.True(actual.Any()); - } - - - [Fact] // issue #34 - public void ApplyFiltering_UnmappedFields_ShouldThrowException() - { - var gm = new GridifyMapper() - .AddMap("Id", q => q.Id); - - var exp = Assert.Throws(() => _fakeRepository.AsQueryable().ApplyFiltering("name=John,id>0", gm).ToList()); - Assert.Equal("Mapping 'name' not found",exp.Message); - } - - [Fact] // issue #34 - public void ApplyFiltering_UnmappedFields_ShouldSkipWhenIgnored() - { - var gm = new GridifyMapper(configuration => configuration.IgnoreNotMappedFields = true) - .AddMap("Id", q => q.Id); - - // name=*a filter should be ignored - var actual = _fakeRepository.AsQueryable().ApplyFiltering("name=*a, id>15", gm).ToList(); - var expected = _fakeRepository.Where(q => q.Id > 15).ToList(); - - Assert.Equal(expected.Count, actual.Count); - Assert.Equal(expected, actual); - Assert.True(actual.Any()); - } - - #endregion - - #region "ApplyOrdering" - - [Fact] - public void ApplyOrdering_OrderBy_Ascending() - { - var gq = new GridifyQuery { OrderBy = "name" }; - var actual = _fakeRepository.AsQueryable() - .ApplyOrdering(gq) - .ToList(); - var expected = _fakeRepository.OrderBy(q => q.Name).ToList(); - Assert.Equal(expected, actual); - } - - [Fact] - public void ApplyOrdering_OrderBy_DateTime() - { - var gq = new GridifyQuery { OrderBy = "MyDateTime" }; - var actual = _fakeRepository.AsQueryable() - .ApplyOrdering(gq) - .ToList(); - var expected = _fakeRepository.OrderBy(q => q.MyDateTime).ToList(); - - Assert.Equal(expected, actual); - Assert.Equal(expected.First().Id, actual.First().Id); - } - - [Fact] - public void ApplyOrdering_OrderBy_Descending() - { - var gq = new GridifyQuery { OrderBy = "Name desc" }; - var actual = _fakeRepository.AsQueryable() - .ApplyOrdering(gq) - .ToList(); - var expected = _fakeRepository.OrderByDescending(q => q.Name).ToList(); - - Assert.Equal(expected, actual); - Assert.Equal(expected.First().Id, actual.First().Id); - } - - [Fact] - public void ApplyOrdering_MultipleOrderBy() - { - var gq = new GridifyQuery { OrderBy = "MyDateTime desc , id , name asc" }; - var actual = _fakeRepository.AsQueryable() - .ApplyOrdering(gq) - .ToList(); - var expected = _fakeRepository - .OrderByDescending(q => q.MyDateTime) - .ThenBy(q => q.Id) - .ThenBy(q => q.Name) - .ToList(); - - Assert.Equal(expected.First().Id, actual.First().Id); - Assert.Equal(expected.Last().Id, actual.Last().Id); - Assert.Equal(expected, actual); - } - - - [Fact] - public void ApplyOrdering_SortUsingChildClassProperty() - { - var gq = new GridifyQuery { OrderBy = "Child_Name desc" }; - var gm = new GridifyMapper() - .GenerateMappings() - .AddMap("Child_Name", q => q.ChildClass!.Name); - - var actual = _fakeRepository.AsQueryable().Where(q => q.ChildClass != null) - .ApplyOrdering(gq, gm) - .ToList(); - - var expected = _fakeRepository.Where(q => q.ChildClass != null) - .OrderByDescending(q => q.ChildClass!.Name).ToList(); - - Assert.Equal(expected, actual); - } - - [Fact] - public void ApplyOrdering_EmptyOrderBy_ShouldSkip() - { - var gq = new GridifyQuery(); - var actual = _fakeRepository.AsQueryable() - .ApplyOrdering(gq) - .ToList(); - var expected = _fakeRepository.ToList(); - Assert.Equal(expected, actual); - } - - [Fact] - public void ApplyOrdering_NullGridifyQuery_ShouldSkip() - { - GridifyQuery? gq = null; - var actual = _fakeRepository.AsQueryable() - .ApplyOrdering(gq) - .ToList(); - var expected = _fakeRepository.ToList(); - Assert.Equal(expected, actual); - } - - [Fact] // issue #34 - public void ApplyOrdering_UnmappedFields_ShouldThrowException() - { - var gm = new GridifyMapper() - .AddMap("Id", q => q.Id); - - var exp = Assert.Throws(() => _fakeRepository.AsQueryable().ApplyOrdering("name,id", gm).ToList()); - Assert.Equal("Mapping 'name' not found",exp.Message); - } - - [Fact] // issue #34 - public void ApplyOrdering_UnmappedFields_ShouldSkipWhenIgnored() - { - var gm = new GridifyMapper(configuration => configuration.IgnoreNotMappedFields = true) - .AddMap("Id", q => q.Id); - - // name orderBy should be ignored - var actual = _fakeRepository.AsQueryable().ApplyOrdering("name,id", gm).ToList(); - var expected = _fakeRepository.OrderBy(q => q.Id ).ToList(); - - Assert.Equal(expected.Count, actual.Count); - Assert.Equal(expected, actual); - Assert.True(actual.Any()); - } - - #endregion - - #region "ApplyPaging" - - [Fact] - public void ApplyPaging_UsingDefaultValues() - { - var gq = new GridifyQuery(); - var actual = _fakeRepository.AsQueryable() - .ApplyPaging(gq) - .ToList(); - - // just returning first page with default size - var expected = _fakeRepository.Take(GridifyExtensions.DefaultPageSize).ToList(); - - Assert.Equal(expected.Count, actual.Count); - Assert.Equal(expected, actual); - Assert.True(actual.Any()); - } - - [Theory] - [InlineData(1, 5)] - [InlineData(2, 5)] - [InlineData(1, 10)] - [InlineData(4, 3)] - [InlineData(5, 3)] - [InlineData(1, 15)] - [InlineData(20, 10)] - public void ApplyPaging_UsingCustomValues(int page, int pageSize) - { - var gq = new GridifyQuery { Page = page, PageSize = pageSize }; - var actual = _fakeRepository.AsQueryable() - .ApplyPaging(gq) - .ToList(); - - var skip = (page - 1) * pageSize; - var expected = _fakeRepository.Skip(skip) - .Take(pageSize).ToList(); - - Assert.Equal(expected.Count, actual.Count); - Assert.Equal(expected, actual); - } - - #endregion - - #region "Other" - - [Fact] - public void Gridify_ActionOverload() - { - var actual = _fakeRepository.AsQueryable() - .Gridify(q => - { - q.Filter = "name=John"; - q.PageSize = 13; - q.OrderBy = "name desc"; - }); - - var query = _fakeRepository.AsQueryable().Where(q => q.Name == "John").OrderByDescending(q => q.Name); - var totalItems = query.Count(); - var items = query.Skip(-2).Take(15).ToList(); - var expected = new Paging() { Data = items, Count = totalItems }; - - Assert.Equal(expected.Count, actual.Count); - Assert.Equal(expected.Data.Count(), actual.Data.Count()); - Assert.True(actual.Data.Any()); - Assert.Equal(expected.Data.First().Id, actual.Data.First().Id); - } - - [Theory] - [InlineData(0, 5, true)] - [InlineData(1, 5, false)] - [InlineData(0, 10, true)] - [InlineData(3, 3, false)] - [InlineData(4, 3, true)] - [InlineData(0, 15, false)] - [InlineData(19, 10, true)] - public void ApplyOrderingAndPaging_UsingCustomValues(int page, int pageSize, bool isSortAsc) - { - var orderByExp = "name " + (isSortAsc ? "asc" : "desc"); - var gq = new GridifyQuery { Page = page, PageSize = pageSize, OrderBy = orderByExp }; - // actual - var actual = _fakeRepository.AsQueryable() - .ApplyOrderingAndPaging(gq) - .ToList(); - - // expected - var skip = (page - 1) * pageSize; - var expectedQuery = _fakeRepository.AsQueryable(); - if (isSortAsc) - expectedQuery = expectedQuery.OrderBy(q => q.Name); - else - expectedQuery = expectedQuery.OrderByDescending(q => q.Name); - var expected = expectedQuery.Skip(skip).Take(pageSize).ToList(); - - Assert.Equal(expected.Count, actual.Count); - Assert.Equal(expected.FirstOrDefault()?.Id, actual.FirstOrDefault()?.Id); - Assert.Equal(expected.LastOrDefault()?.Id, actual.LastOrDefault()?.Id); - } - - #endregion - - #region "Data" - - public static IEnumerable GetSampleData() - { - var lst = new List(); - lst.Add(new TestClass(1, "John", null, Guid.NewGuid(), DateTime.Now)); - lst.Add(new TestClass(2, "Bob", null, Guid.NewGuid(), DateTime.UtcNow)); - lst.Add(new TestClass(3, "Jack", (TestClass)lst[0].Clone(), Guid.Empty, DateTime.Now.AddDays(2))); - lst.Add(new TestClass(4, "Rose", null, Guid.Parse("e2cec5dd-208d-4bb5-a852-50008f8ba366"))); - lst.Add(new TestClass(5, "Ali", null, tag: "123")); - lst.Add(new TestClass(6, "Hamid", (TestClass)lst[0].Clone(), Guid.Parse("de12bae1-93fa-40e4-92d1-2e60f95b468c"))); - lst.Add(new TestClass(7, "Hasan", (TestClass)lst[1].Clone())); - lst.Add(new TestClass(8, "Farhad", (TestClass)lst[2].Clone(), Guid.Empty)); - lst.Add(new TestClass(9, "Sara", null)); - lst.Add(new TestClass(10, "Jorge", null)); - lst.Add(new TestClass(11, "joe", null)); - lst.Add(new TestClass(12, "jimmy", (TestClass)lst[0].Clone())); - lst.Add(new TestClass(13, "Nazanin", null, tag: "test0")); - lst.Add(new TestClass(14, "Reza", null, tag: "test1")); - lst.Add(new TestClass(15, "Korosh", (TestClass)lst[0].Clone())); - lst.Add(new TestClass(16, "Kamran", (TestClass)lst[1].Clone())); - lst.Add(new TestClass(17, "Saeid", (TestClass)lst[2].Clone())); - lst.Add(new TestClass(18, "jessi=ca", null)); - lst.Add(new TestClass(19, "Ped=ram", null, tag: "test3")); - lst.Add(new TestClass(20, "Peyman!", null)); - lst.Add(new TestClass(21, "Fereshte", null, tag: null)); - lst.Add(new TestClass(22, "LIAM", null)); - lst.Add(new TestClass(22, @"\Liam", null, tag: null)); - lst.Add(new TestClass(23, "LI | AM", null)); - lst.Add(new TestClass(24, "(LI,AM)", null, tag: string.Empty)); - lst.Add(new TestClass(25, "Case/i", null, tag: string.Empty)); - lst.Add(new TestClass(26, "/iCase", null)); - lst.Add(new TestClass(27, "ali reza", null)); - - return lst; - } - - #endregion + private readonly List _fakeRepository; + + public GridifyExtensionsShould() + { + _fakeRepository = new List(GetSampleData()); + } + + + #region "ApplyFiltering" + + [Fact] + public void ApplyFiltering_SingleField() + { + var actual = _fakeRepository.AsQueryable() + .ApplyFiltering("name=John") + .ToList(); + var expected = _fakeRepository.Where(q => q.Name == "John").ToList(); + Assert.Equal(expected.Count, actual.Count); + Assert.Equal(expected, actual); + Assert.True(actual.Any()); + } + + + [Fact] + public void ApplyFiltering_SingleField_GridifyQuery() + { + var gq = new GridifyQuery { Filter = "name=John" }; + var actual = _fakeRepository.AsQueryable() + .ApplyFiltering(gq) + .ToList(); + var expected = _fakeRepository.Where(q => q.Name == "John").ToList(); + Assert.Equal(expected.Count, actual.Count); + Assert.Equal(expected, actual); + Assert.True(actual.Any()); + } + + [Fact] + public void ApplyFiltering_NullHandlingUsingCustomConvertor() + { + // create custom mapper + var gm = new GridifyMapper(q => q.AllowNullSearch = false).GenerateMappings(); + + // map any string to related property , also use Client convertor to handle custom scenarios + gm.AddMap("date", g => g.MyDateTime!, q => (q == "null" ? null : q)!); + + var actual = _fakeRepository.AsQueryable() + .ApplyFiltering("date=null", gm) + .ToList(); + + var expected = _fakeRepository.Where(q => q.MyDateTime == null).ToList(); + Assert.Equal(expected.Count, actual.Count); + Assert.Equal(expected, actual); + Assert.True(actual.Any()); + } + + [Fact] + public void ApplyFiltering_NullHandlingUsingMapper() + { + // create custom mapper + var gm = new GridifyMapper(q => q.AllowNullSearch = true) // default is true + .GenerateMappings(); + + var actual = _fakeRepository.AsQueryable() + .ApplyFiltering("MyDateTime=null", gm) + .ToList(); + + var expected = _fakeRepository.Where(q => q.MyDateTime == null).ToList(); + Assert.Equal(expected.Count, actual.Count); + Assert.Equal(expected, actual); + Assert.True(actual.Any()); + } + + [Fact] + public void ApplyFiltering_DisableNullHandlingUsingMapper() + { + // create custom mapper + var gm = new GridifyMapper(q => q.AllowNullSearch = false) // default is true + .GenerateMappings(); + + var actual = _fakeRepository.AsQueryable() + .ApplyFiltering("MyDateTime=null", gm) + .ToList(); + + var expected = _fakeRepository.Where(q => q.MyDateTime.ToString() == "null").ToList(); + Assert.Equal(expected, actual); + Assert.Empty(actual); + } + + [Fact] + public void ApplyFiltering_DuplicatefieldName() + { + const string gq = "name=John|name=Sara"; + var actual = _fakeRepository.AsQueryable() + .ApplyFiltering(gq) + .ToList(); + var expected = _fakeRepository.Where(q => (q.Name == "John") | (q.Name == "Sara")).ToList(); + Assert.Equal(expected.Count, actual.Count); + Assert.Equal(expected, actual); + Assert.True(actual.Any()); + } + + + [Theory] + [InlineData(@" name =\(LI\,AM\)", "(LI,AM)")] + [InlineData(@" name =jessi=ca", "jessi=ca")] + [InlineData(@" name =\\Liam", @"\Liam")] + [InlineData(@" name =LI \| AM", @"LI | AM")] + public void ApplyFiltering_EscapeSpecialCharacters(string textFilter, string rawText) + { + var gq = new GridifyQuery { Filter = textFilter }; + var actual = _fakeRepository.AsQueryable() + .ApplyFiltering(gq) + .ToList(); + var expected = _fakeRepository.Where(q => q.Name == rawText).ToList(); + Assert.Equal(expected.Count, actual.Count); + Assert.Equal(expected, actual); + Assert.True(actual.Any()); + } + + [Fact] + public void ApplyFiltering_ParenthesisQueryWithoutEscapeShouldThrowException() + { + var gq = new GridifyQuery { Filter = @"name=(LI,AM)" }; + Action act = () => _fakeRepository.AsQueryable() + .ApplyFiltering(gq); + + Assert.Throws(act); + } + + + [Fact] + public void ApplyFiltering_SingleGuidField() + { + var guidString = "e2cec5dd-208d-4bb5-a852-50008f8ba366"; + var guid = Guid.Parse(guidString); + var gq = new GridifyQuery { Filter = "myGuid=" + guidString }; + var actual = _fakeRepository.AsQueryable() + .ApplyFiltering(gq) + .ToList(); + var expected = _fakeRepository.Where(q => q.MyGuid == guid).ToList(); + Assert.Equal(expected.Count, actual.Count); + Assert.Equal(expected, actual); + Assert.True(actual.Any()); + } + + [Fact] + public void ApplyFiltering_SingleBrokenGuidField() + { + var brokenGuidString = "e2cec5dd-208d-4bb5-a852-"; + var gq = new GridifyQuery { Filter = "myGuid=" + brokenGuidString }; + + var actual = _fakeRepository.AsQueryable() + .ApplyFiltering(gq) + .ToList(); + + Assert.False(actual.Any()); + } + + + [Fact] + public void ApplyFiltering_SingleBrokenGuidField_NotEqual() + { + var brokenGuidString = "e2cec5dd-208d-4bb5-a852-"; + var gq = new GridifyQuery { Filter = "myGuid!=" + brokenGuidString }; + + var actual = _fakeRepository.AsQueryable() + .ApplyFiltering(gq) + .ToList(); + + Assert.True(actual.Any()); + } + + + [Fact] + public void ApplyFiltering_InvalidFilterExpressionShouldThrowException() + { + var gq = new GridifyQuery { Filter = "=guid,d=" }; + Assert.Throws(() => + _fakeRepository.AsQueryable().ApplyFiltering(gq).ToList()); + } + + [Fact] + public void ApplyFiltering_InvalidCharacterShouldThrowException() + { + var gq = new GridifyQuery { Filter = "@name=ali" }; + Assert.Throws(() => + _fakeRepository.AsQueryable().ApplyFiltering(gq).ToList()); + } + + [Theory] + [InlineData(null)] + [InlineData("")] + public void ApplyFiltering_NullOrEmptyFilter_ShouldSkip(string? filter) + { + var gq = new GridifyQuery(); + if (filter == null) + gq = null; + else + gq.Filter = filter; + + var actual = _fakeRepository.AsQueryable() + .ApplyFiltering(gq) + .ToList(); + + var expected = _fakeRepository.ToList(); + Assert.Equal(expected.Count, actual.Count); + Assert.Equal(expected, actual); + Assert.True(actual.Any()); + } + + [Fact] + public void ApplyFiltering_CustomConvertor() + { + const string gq = "name=liam"; + var gm = new GridifyMapper() + .GenerateMappings() + .AddMap("name", q => q.Name, q => q.ToUpper()); // using client side Custom convertor + + var actual = _fakeRepository.AsQueryable() + .ApplyFiltering(gq, gm) + .ToList(); + + var expected = _fakeRepository.Where(q => q.Name == "LIAM").ToList(); + Assert.Equal(expected.Count, actual.Count); + Assert.Equal(expected, actual); + Assert.True(actual.Any()); + } + + [Fact] + public void ApplyFiltering_StartsWith() + { + const string gq = "name^A"; + + var actual = _fakeRepository.AsQueryable() + .ApplyFiltering(gq) + .ToList(); + + var expected = _fakeRepository.Where(q => q.Name!.StartsWith("A")).ToList(); + + Assert.Equal(expected.Count, actual.Count); + Assert.Equal(expected, actual); + Assert.True(actual.Any()); + } + + [Fact] + public void ApplyEverything_EmptyGridifyQuery() + { + var gq = new GridifyQuery(); + + var actual = _fakeRepository.AsQueryable() + .ApplyFilteringOrderingPaging(gq) + .ToList(); + + var expected = _fakeRepository.Skip(0).Take(GridifyExtensions.DefaultPageSize).ToList(); + + Assert.Equal(expected.Count, actual.Count); + Assert.Equal(expected, actual); + Assert.True(actual.Any()); + } + + + [Fact] + public void ApplyFiltering_StartsWithOnNotStringsShouldNotThrowError() + { + const string gq = "Id^2"; + + var actual = _fakeRepository.AsQueryable() + .ApplyFiltering(gq) + .ToList(); + + var expected = _fakeRepository.Where(q => q.Id.ToString().StartsWith("2")).ToList(); + + Assert.Equal(expected.Count, actual.Count); + Assert.Equal(expected, actual); + Assert.True(actual.Any()); + } + + [Fact] + public void ApplyFiltering_NotStartsWith() + { + const string gq = "name!^A"; + + var actual = _fakeRepository.AsQueryable() + .ApplyFiltering(gq) + .ToList(); + + var expected = _fakeRepository.Where(q => !q.Name!.StartsWith("A")).ToList(); + + Assert.Equal(expected.Count, actual.Count); + Assert.Equal(expected, actual); + Assert.True(actual.Any()); + } + + [Fact] + public void ApplyFiltering_EndsWith() + { + const string gq = "name $ li"; + + var actual = _fakeRepository.AsQueryable() + .ApplyFiltering(gq) + .ToList(); + + var expected = _fakeRepository.Where(q => q.Name!.EndsWith("li")).ToList(); + Assert.Equal(expected.Count, actual.Count); + Assert.Equal(expected, actual); + Assert.True(actual.Any()); + } + + [Fact] + public void ApplyFiltering_NotEndsWith() + { + const string gq = "name !$ i"; + + var actual = _fakeRepository.AsQueryable() + .ApplyFiltering(gq) + .ToList(); + + var expected = _fakeRepository.Where(q => !q.Name!.EndsWith("i")).ToList(); + + Assert.Equal(expected.Count, actual.Count); + Assert.Equal(expected, actual); + Assert.True(actual.Any()); + } + + [Fact] + public void ApplyFiltering_MultipleCondition() + { + var actual = _fakeRepository.AsQueryable() + .ApplyFiltering("name=Jack|name=Rose|id>=7") + .ToList(); + var expected = _fakeRepository.Where(q => q.Name == "Jack" || q.Name == "Rose" || q.Id >= 7).ToList(); + Assert.Equal(expected.Count, actual.Count); + Assert.Equal(expected, actual); + Assert.True(actual.Any()); + } + + [Fact] + public void ApplyFiltering_ComplexWithParenthesis() + { + var actual = _fakeRepository.AsQueryable() + .ApplyFiltering("(name=*J|name=*S),(Id<5)") + .ToList(); + var expected = _fakeRepository.Where(q => (q.Name!.Contains("J") || q.Name.Contains("S")) && q.Id < 5).ToList(); + Assert.Equal(expected.Count, actual.Count); + Assert.Equal(expected, actual); + Assert.True(actual.Any()); + } + + [Fact] + public void ApplyFiltering_NestedParenthesisWithSpace() + { + // we shouldn't add spaces for values + var gq = new GridifyQuery { Filter = " ( name =*J| ( name =*S , Id <5 ) )" }; + var actual = _fakeRepository.AsQueryable() + .ApplyFiltering(gq) + .ToList(); + var expected = _fakeRepository.Where(q => q.Name!.Contains("J") || q.Name.Contains("S") && q.Id < 5).ToList(); + Assert.Equal(expected.Count, actual.Count); + Assert.Equal(expected, actual); + Assert.True(actual.Any()); + } + + [Fact] + public void ApplyFiltering_UsingChildClassProperty() + { + var gq = new GridifyQuery { Filter = "Child_Name=Bob" }; + var gm = new GridifyMapper() + .GenerateMappings() + .AddMap("Child_name", q => q.ChildClass!.Name); + + var actual = _fakeRepository.AsQueryable() + .Where(q => q.ChildClass != null) + .ApplyFiltering(gq, gm) + .ToList(); + + var expected = _fakeRepository.Where(q => q.ChildClass is { Name: "Bob" }).ToList(); + Assert.Equal(expected.Count, actual.Count); + Assert.Equal(expected, actual); + Assert.True(actual.Any()); + } + + [Fact] + public void ApplyFiltering_CaseInsensitiveSearchUsingConvertor() //issue #21 + { + var gq = new GridifyQuery { Filter = "name=BOB" }; + var gm = new GridifyMapper() + .AddMap("name", q => q.Name!.ToLower(), c => c.ToLower()); + + var actual = _fakeRepository.AsQueryable() + .ApplyFiltering(gq, gm) + .ToList(); + + var expected = _fakeRepository.Where(q => q.Name!.ToLower() == "BOB".ToLower()).ToList(); + Assert.Equal(expected.Count, actual.Count); + Assert.Equal(expected, actual); + Assert.True(actual.Any()); + } + + [Fact] + public void ApplyFiltering_CaseInsensitiveSearch() //issue #21 + { + var gq = new GridifyQuery { Filter = "name=BOB/i " }; + + var actual = _fakeRepository.AsQueryable() + .ApplyFiltering(gq) + .ToList(); + + var expected = _fakeRepository.Where(q => q.Name!.ToLower() == "BOB".ToLower()).ToList(); + Assert.Equal(expected.Count, actual.Count); + Assert.Equal(expected, actual); + Assert.True(actual.Any()); + } + + [Fact] + public void ApplyFiltering_EscapeCaseInsensitiveSearch() //issue #21 + { + var actual = _fakeRepository.AsQueryable() + .ApplyFiltering(@"name=Case\/i") + .ToList(); + + var expected = _fakeRepository.Where(q => q.Name == "Case/i").ToList(); + Assert.Equal(expected.Count, actual.Count); + Assert.Equal(expected, actual); + Assert.True(actual.Any()); } + + [Fact] + public void ApplyFiltering_CaseInsensitiveOperatorAtTheBeginningOfValue_ShouldIgnore() + { + var actual = _fakeRepository.AsQueryable() + .ApplyFiltering(@"name=\/icase") + .ToList(); + + var expected = _fakeRepository.Where(q => q.Name == "/icase").ToList(); + Assert.Equal(expected.Count, actual.Count); + Assert.Equal(expected, actual); + Assert.False(actual.Any()); + } + + [Fact] // issue #27 + public void ApplyFiltering_GreaterThanBetweenTwoStrings() + { + var actual = _fakeRepository.AsQueryable().ApplyFiltering("name > ali").ToList(); + + var expected = _fakeRepository.Where(q => string.Compare(q.Name, "ali", StringComparison.Ordinal) > 0).ToList(); + Assert.Equal(expected.Count, actual.Count); + Assert.Equal(expected, actual); + Assert.True(actual.Any()); + } + + + [Fact] // issue #27 + public void ApplyFiltering_LessThanBetweenTwoStrings() + { + var actual = _fakeRepository.AsQueryable().ApplyFiltering("name < v").ToList(); + + var expected = _fakeRepository.Where(q => string.Compare(q.Name, "v", StringComparison.Ordinal) < 0).ToList(); + Assert.Equal(expected.Count, actual.Count); + Assert.Equal(expected, actual); + Assert.True(actual.Any()); + } + + [Fact] // issue #27 + public void ApplyFiltering_LessThanOrEqualBetweenTwoStrings() + { + var actual = _fakeRepository.AsQueryable().ApplyFiltering("name <= l").ToList(); + var expected = _fakeRepository.Where(q => string.Compare(q.Name, "l", StringComparison.Ordinal) <= 0).ToList(); + + Assert.Equal(expected.Count, actual.Count); + Assert.Equal(expected, actual); + Assert.True(actual.Any()); + } + + [Fact] // issue #27 + public void ApplyFiltering_GreaterThanOrEqualBetweenTwoStrings() + { + var actual = _fakeRepository.AsQueryable().ApplyFiltering("name >= c").ToList(); + var expected = _fakeRepository.Where(q => string.Compare(q.Name, "c", StringComparison.Ordinal) >= 0).ToList(); + + Assert.Equal(expected.Count, actual.Count); + Assert.Equal(expected, actual); + Assert.True(actual.Any()); + } + + [Fact] // issue #27 + public void ApplyFiltering_GreaterThanOrEqual_CaseInsensitive_BetweenTwoStrings() + { + var actual = _fakeRepository.AsQueryable().ApplyFiltering("name >= j/i").ToList(); + var expected = _fakeRepository.Where(q => string.Compare(q.Name, "j", StringComparison.OrdinalIgnoreCase) >= 0).ToList(); + + Assert.Equal(expected.Count, actual.Count); + Assert.Equal(expected, actual); + Assert.True(actual.Any()); + } + + [Fact] // issue #25 + public void ApplyFiltering_Equal_ProcessingNullOrDefaultValue() + { + var actual = _fakeRepository.AsQueryable().ApplyFiltering("tag=").ToList(); + var expected = _fakeRepository.Where(q => string.IsNullOrEmpty(q.Tag)).ToList(); + var expected2 = _fakeRepository.Where(q => q.Tag is null or "").ToList(); + + Assert.Equal(expected.Count, actual.Count); + Assert.Equal(expected2.Count, actual.Count); + Assert.Equal(expected, actual); + Assert.Equal(expected2, actual); + Assert.True(actual.Any()); + } + + [Fact] // issue #25 + public void ApplyFiltering_NotEqual_ProcessingNullOrDefaultValue() + { + var actual = _fakeRepository.AsQueryable().ApplyFiltering("tag!=").ToList(); + var expected = _fakeRepository.Where(q => !string.IsNullOrEmpty(q.Tag)).ToList(); + var expected2 = _fakeRepository.Where(q => q.Tag is not null && q.Tag != "").ToList(); + + Assert.Equal(expected.Count, actual.Count); + Assert.Equal(expected2.Count, actual.Count); + Assert.Equal(expected, actual); + Assert.Equal(expected2, actual); + Assert.True(actual.Any()); + } + + [Fact] // issue #25 + public void ApplyFiltering_Equal_ProcessingNullOrDefaultValueNonStringTypes() + { + var actual = _fakeRepository.AsQueryable().ApplyFiltering("myGuid=").ToList(); + var expected = _fakeRepository.Where(q => q.MyGuid == default).ToList(); + + Assert.Equal(expected.Count, actual.Count); + Assert.Equal(expected, actual); + Assert.True(actual.Any()); + } + + [Fact] // issue #25 + public void ApplyFiltering_NotEqual_ProcessingNullOrDefaultValueNonStringTypes() + { + var actual = _fakeRepository.AsQueryable().ApplyFiltering("myGuid!=").ToList(); + var expected = _fakeRepository.Where(q => q.MyGuid != default).ToList(); + + Assert.Equal(expected.Count, actual.Count); + Assert.Equal(expected, actual); + Assert.True(actual.Any()); + } + + [Fact] // issue #33 + public void ApplyFiltering_WithSpaces() + { + var actual = _fakeRepository.AsQueryable().ApplyFiltering("name =ali reza").ToList(); + var expected = _fakeRepository.Where(q => q.Name == "ali reza" ).ToList(); + + Assert.Equal(expected.Count, actual.Count); + Assert.Equal(expected, actual); + Assert.True(actual.Any()); + } + + + [Fact] // issue #34 + public void ApplyFiltering_UnmappedFields_ShouldThrowException() + { + var gm = new GridifyMapper() + .AddMap("Id", q => q.Id); + + var exp = Assert.Throws(() => _fakeRepository.AsQueryable().ApplyFiltering("name=John,id>0", gm).ToList()); + Assert.Equal("Mapping 'name' not found",exp.Message); + } + + [Fact] // issue #34 + public void ApplyFiltering_UnmappedFields_ShouldSkipWhenIgnored() + { + var gm = new GridifyMapper(configuration => configuration.IgnoreNotMappedFields = true) + .AddMap("Id", q => q.Id); + + // name=*a filter should be ignored + var actual = _fakeRepository.AsQueryable().ApplyFiltering("name=*a, id>15", gm).ToList(); + var expected = _fakeRepository.Where(q => q.Id > 15).ToList(); + + Assert.Equal(expected.Count, actual.Count); + Assert.Equal(expected, actual); + Assert.True(actual.Any()); + } + + #endregion + + #region "ApplyOrdering" + + [Fact] + public void ApplyOrdering_OrderBy_Ascending() + { + var gq = new GridifyQuery { OrderBy = "name" }; + var actual = _fakeRepository.AsQueryable() + .ApplyOrdering(gq) + .ToList(); + var expected = _fakeRepository.OrderBy(q => q.Name).ToList(); + Assert.Equal(expected, actual); + } + + [Fact] + public void ApplyOrdering_OrderBy_DateTime() + { + var gq = new GridifyQuery { OrderBy = "MyDateTime" }; + var actual = _fakeRepository.AsQueryable() + .ApplyOrdering(gq) + .ToList(); + var expected = _fakeRepository.OrderBy(q => q.MyDateTime).ToList(); + + Assert.Equal(expected, actual); + Assert.Equal(expected.First().Id, actual.First().Id); + } + + [Fact] + public void ApplyOrdering_OrderBy_Descending() + { + var gq = new GridifyQuery { OrderBy = "Name desc" }; + var actual = _fakeRepository.AsQueryable() + .ApplyOrdering(gq) + .ToList(); + var expected = _fakeRepository.OrderByDescending(q => q.Name).ToList(); + + Assert.Equal(expected, actual); + Assert.Equal(expected.First().Id, actual.First().Id); + } + + [Fact] + public void ApplyOrdering_MultipleOrderBy() + { + var gq = new GridifyQuery { OrderBy = "MyDateTime desc , id , name asc" }; + var actual = _fakeRepository.AsQueryable() + .ApplyOrdering(gq) + .ToList(); + var expected = _fakeRepository + .OrderByDescending(q => q.MyDateTime) + .ThenBy(q => q.Id) + .ThenBy(q => q.Name) + .ToList(); + + Assert.Equal(expected.First().Id, actual.First().Id); + Assert.Equal(expected.Last().Id, actual.Last().Id); + Assert.Equal(expected, actual); + } + + + [Fact] + public void ApplyOrdering_SortUsingChildClassProperty() + { + var gq = new GridifyQuery { OrderBy = "Child_Name desc" }; + var gm = new GridifyMapper() + .GenerateMappings() + .AddMap("Child_Name", q => q.ChildClass!.Name); + + var actual = _fakeRepository.AsQueryable().Where(q => q.ChildClass != null) + .ApplyOrdering(gq, gm) + .ToList(); + + var expected = _fakeRepository.Where(q => q.ChildClass != null) + .OrderByDescending(q => q.ChildClass!.Name).ToList(); + + Assert.Equal(expected, actual); + } + + [Fact] + public void ApplyOrdering_EmptyOrderBy_ShouldSkip() + { + var gq = new GridifyQuery(); + var actual = _fakeRepository.AsQueryable() + .ApplyOrdering(gq) + .ToList(); + var expected = _fakeRepository.ToList(); + Assert.Equal(expected, actual); + } + + [Fact] + public void ApplyOrdering_NullGridifyQuery_ShouldSkip() + { + GridifyQuery? gq = null; + var actual = _fakeRepository.AsQueryable() + .ApplyOrdering(gq) + .ToList(); + var expected = _fakeRepository.ToList(); + Assert.Equal(expected, actual); + } + + [Fact] // issue #34 + public void ApplyOrdering_UnmappedFields_ShouldThrowException() + { + var gm = new GridifyMapper() + .AddMap("Id", q => q.Id); + + var exp = Assert.Throws(() => _fakeRepository.AsQueryable().ApplyOrdering("name,id", gm).ToList()); + Assert.Equal("Mapping 'name' not found",exp.Message); + } + + [Fact] // issue #34 + public void ApplyOrdering_UnmappedFields_ShouldSkipWhenIgnored() + { + var gm = new GridifyMapper(configuration => configuration.IgnoreNotMappedFields = true) + .AddMap("Id", q => q.Id); + + // name orderBy should be ignored + var actual = _fakeRepository.AsQueryable().ApplyOrdering("name,id", gm).ToList(); + var expected = _fakeRepository.OrderBy(q => q.Id ).ToList(); + + Assert.Equal(expected.Count, actual.Count); + Assert.Equal(expected, actual); + Assert.True(actual.Any()); + } + + #endregion + + #region "ApplyPaging" + + [Fact] + public void ApplyPaging_UsingDefaultValues() + { + var gq = new GridifyQuery(); + var actual = _fakeRepository.AsQueryable() + .ApplyPaging(gq) + .ToList(); + + // just returning first page with default size + var expected = _fakeRepository.Take(GridifyExtensions.DefaultPageSize).ToList(); + + Assert.Equal(expected.Count, actual.Count); + Assert.Equal(expected, actual); + Assert.True(actual.Any()); + } + + [Theory] + [InlineData(1, 5)] + [InlineData(2, 5)] + [InlineData(1, 10)] + [InlineData(4, 3)] + [InlineData(5, 3)] + [InlineData(1, 15)] + [InlineData(20, 10)] + public void ApplyPaging_UsingCustomValues(int page, int pageSize) + { + var gq = new GridifyQuery { Page = page, PageSize = pageSize }; + var actual = _fakeRepository.AsQueryable() + .ApplyPaging(gq) + .ToList(); + + var skip = (page - 1) * pageSize; + var expected = _fakeRepository.Skip(skip) + .Take(pageSize).ToList(); + + Assert.Equal(expected.Count, actual.Count); + Assert.Equal(expected, actual); + } + + #endregion + + #region "Other" + + [Fact] + public void Gridify_ActionOverload() + { + var actual = _fakeRepository.AsQueryable() + .Gridify(q => + { + q.Filter = "name=John"; + q.PageSize = 13; + q.OrderBy = "name desc"; + }); + + var query = _fakeRepository.AsQueryable().Where(q => q.Name == "John").OrderByDescending(q => q.Name); + var totalItems = query.Count(); + var items = query.Skip(-2).Take(15).ToList(); + var expected = new Paging() { Data = items, Count = totalItems }; + + Assert.Equal(expected.Count, actual.Count); + Assert.Equal(expected.Data.Count(), actual.Data.Count()); + Assert.True(actual.Data.Any()); + Assert.Equal(expected.Data.First().Id, actual.Data.First().Id); + } + + [Theory] + [InlineData(0, 5, true)] + [InlineData(1, 5, false)] + [InlineData(0, 10, true)] + [InlineData(3, 3, false)] + [InlineData(4, 3, true)] + [InlineData(0, 15, false)] + [InlineData(19, 10, true)] + public void ApplyOrderingAndPaging_UsingCustomValues(int page, int pageSize, bool isSortAsc) + { + var orderByExp = "name " + (isSortAsc ? "asc" : "desc"); + var gq = new GridifyQuery { Page = page, PageSize = pageSize, OrderBy = orderByExp }; + // actual + var actual = _fakeRepository.AsQueryable() + .ApplyOrderingAndPaging(gq) + .ToList(); + + // expected + var skip = (page - 1) * pageSize; + var expectedQuery = _fakeRepository.AsQueryable(); + if (isSortAsc) + expectedQuery = expectedQuery.OrderBy(q => q.Name); + else + expectedQuery = expectedQuery.OrderByDescending(q => q.Name); + var expected = expectedQuery.Skip(skip).Take(pageSize).ToList(); + + Assert.Equal(expected.Count, actual.Count); + Assert.Equal(expected.FirstOrDefault()?.Id, actual.FirstOrDefault()?.Id); + Assert.Equal(expected.LastOrDefault()?.Id, actual.LastOrDefault()?.Id); + } + + #endregion + + #region Validation + + [Theory] + [InlineData("notExist=123", false)] + [InlineData("name=ali", true)] + [InlineData("name123,123", false)] + [InlineData("!name<23", false)] + [InlineData("(id=2,tag=someTag)", true)] + [InlineData("@name=john", false)] + //[InlineData("id(); + Assert.Equal(expected, actual); + } + + [Theory] + [InlineData("notExist", false)] + [InlineData("name", true)] + [InlineData("name,", false)] + [InlineData("!name", false)] + [InlineData("name des", false)] + [InlineData("id,name", true)] + [InlineData("id asc,name desc", true)] + [InlineData("id asc,name2 desc", false)] + public void IsValid_IGridifyOrdering_ShouldReturnExpectedResult(string order, bool expected) + { + var gq = new GridifyQuery() { OrderBy = order }; + var actual = gq.IsValid(); + Assert.Equal(expected, actual); + } + + + #endregion + + #region "Data" + + public static IEnumerable GetSampleData() + { + var lst = new List(); + lst.Add(new TestClass(1, "John", null, Guid.NewGuid(), DateTime.Now)); + lst.Add(new TestClass(2, "Bob", null, Guid.NewGuid(), DateTime.UtcNow)); + lst.Add(new TestClass(3, "Jack", (TestClass)lst[0].Clone(), Guid.Empty, DateTime.Now.AddDays(2))); + lst.Add(new TestClass(4, "Rose", null, Guid.Parse("e2cec5dd-208d-4bb5-a852-50008f8ba366"))); + lst.Add(new TestClass(5, "Ali", null, tag: "123")); + lst.Add(new TestClass(6, "Hamid", (TestClass)lst[0].Clone(), Guid.Parse("de12bae1-93fa-40e4-92d1-2e60f95b468c"))); + lst.Add(new TestClass(7, "Hasan", (TestClass)lst[1].Clone())); + lst.Add(new TestClass(8, "Farhad", (TestClass)lst[2].Clone(), Guid.Empty)); + lst.Add(new TestClass(9, "Sara", null)); + lst.Add(new TestClass(10, "Jorge", null)); + lst.Add(new TestClass(11, "joe", null)); + lst.Add(new TestClass(12, "jimmy", (TestClass)lst[0].Clone())); + lst.Add(new TestClass(13, "Nazanin", null, tag: "test0")); + lst.Add(new TestClass(14, "Reza", null, tag: "test1")); + lst.Add(new TestClass(15, "Korosh", (TestClass)lst[0].Clone())); + lst.Add(new TestClass(16, "Kamran", (TestClass)lst[1].Clone())); + lst.Add(new TestClass(17, "Saeid", (TestClass)lst[2].Clone())); + lst.Add(new TestClass(18, "jessi=ca", null)); + lst.Add(new TestClass(19, "Ped=ram", null, tag: "test3")); + lst.Add(new TestClass(20, "Peyman!", null)); + lst.Add(new TestClass(21, "Fereshte", null, tag: null)); + lst.Add(new TestClass(22, "LIAM", null)); + lst.Add(new TestClass(22, @"\Liam", null, tag: null)); + lst.Add(new TestClass(23, "LI | AM", null)); + lst.Add(new TestClass(24, "(LI,AM)", null, tag: string.Empty)); + lst.Add(new TestClass(25, "Case/i", null, tag: string.Empty)); + lst.Add(new TestClass(26, "/iCase", null)); + lst.Add(new TestClass(27, "ali reza", null)); + + return lst; + } + + #endregion } diff --git a/test/Gridify.Tests/GridifyMapperShould.cs b/test/Gridify.Tests/GridifyMapperShould.cs index eb90b840..6d7f475a 100644 --- a/test/Gridify.Tests/GridifyMapperShould.cs +++ b/test/Gridify.Tests/GridifyMapperShould.cs @@ -3,78 +3,77 @@ using System.Linq.Expressions; using Xunit; -namespace Gridify.Tests +namespace Gridify.Tests; + +public class GridifyMapperShould { - public class GridifyMapperShould - { - private IGridifyMapper _sut; - public GridifyMapperShould() => _sut = new GridifyMapper(); + private IGridifyMapper _sut; + public GridifyMapperShould() => _sut = new GridifyMapper(); - [Fact] - public void GenerateMappings() - { - _sut.GenerateMappings(); + [Fact] + public void GenerateMappings() + { + _sut.GenerateMappings(); - var props = typeof(TestClass).GetProperties() - .Where(q => !q.PropertyType.IsClass || q.PropertyType == typeof(string)); + var props = typeof(TestClass).GetProperties() + .Where(q => !q.PropertyType.IsClass || q.PropertyType == typeof(string)); - Assert.Equal(props.Count(), _sut.GetCurrentMaps().Count()); - Assert.True(_sut.HasMap("Id")); - } + Assert.Equal(props.Count(), _sut.GetCurrentMaps().Count()); + Assert.True(_sut.HasMap("Id")); + } - [Fact] - public void CaseSensitivity() - { - var sut = new GridifyMapper(q => q.CaseSensitive = true); - sut.AddMap("id", q => q.Id); + [Fact] + public void CaseSensitivity() + { + var sut = new GridifyMapper(q => q.CaseSensitive = true); + sut.AddMap("id", q => q.Id); - Assert.True(sut.HasMap("id")); - Assert.False(sut.HasMap("ID")); - } + Assert.True(sut.HasMap("id")); + Assert.False(sut.HasMap("ID")); + } - [Fact] - public void AddMap() - { - _sut.AddMap(nameof(TestClass.Name), p => p.Name); - Assert.Single(_sut.GetCurrentMaps()); - } + [Fact] + public void AddMap() + { + _sut.AddMap(nameof(TestClass.Name), p => p.Name); + Assert.Single(_sut.GetCurrentMaps()); + } - [Fact] - public void RemoveMap() - { - _sut.Configuration.CaseSensitive = false; - _sut.AddMap("name", q => q.Name); - _sut.AddMap("childDate", q => q.ChildClass!.MyDateTime); - _sut.RemoveMap(nameof(TestClass.Name)); - Assert.Single(_sut.GetCurrentMaps()); - } + [Fact] + public void RemoveMap() + { + _sut.Configuration.CaseSensitive = false; + _sut.AddMap("name", q => q.Name); + _sut.AddMap("childDate", q => q.ChildClass!.MyDateTime); + _sut.RemoveMap(nameof(TestClass.Name)); + Assert.Single(_sut.GetCurrentMaps()); + } - [Fact] - public void GridifyMapperToStringShouldReturnFieldsList() - { - _sut.AddMap("name", q => q.Name); - _sut.AddMap("childDate", q => q.ChildClass!.MyDateTime); - var actual = _sut.ToString(); + [Fact] + public void GridifyMapperToStringShouldReturnFieldsList() + { + _sut.AddMap("name", q => q.Name); + _sut.AddMap("childDate", q => q.ChildClass!.MyDateTime); + var actual = _sut.ToString(); - Assert.Equal("name,childDate", actual); - } + Assert.Equal("name,childDate", actual); + } - [Fact] - public void AddMap_DuplicateKey_ShouldThrowErrorIfOverrideIfExistsIsFalse() - { - //arrange - _sut.AddMap("Test", q => q.Name); - //act - Action act = () => _sut.AddMap("test", q => q.Name, overrideIfExists: false); - //assert - var exception = Assert.Throws(act); - //The thrown exception can be used for even more detailed assertions. - Assert.Equal("Duplicate Key. the 'test' key already exists", exception.Message); - } + [Fact] + public void AddMap_DuplicateKey_ShouldThrowErrorIfOverrideIfExistsIsFalse() + { + //arrange + _sut.AddMap("Test", q => q.Name); + //act + Action act = () => _sut.AddMap("test", q => q.Name, overrideIfExists: false); + //assert + var exception = Assert.Throws(act); + //The thrown exception can be used for even more detailed assertions. + Assert.Equal("Duplicate Key. the 'test' key already exists", exception.Message); + } - } } \ No newline at end of file diff --git a/test/Gridify.Tests/GridifyNestedCollectionTests.cs b/test/Gridify.Tests/GridifyNestedCollectionTests.cs index 8634ec2e..a4c1f983 100644 --- a/test/Gridify.Tests/GridifyNestedCollectionTests.cs +++ b/test/Gridify.Tests/GridifyNestedCollectionTests.cs @@ -2,316 +2,315 @@ using System.Linq; using Xunit; -namespace Gridify.Tests +namespace Gridify.Tests; + +public class GridifyNestedCollectionTests { - public class GridifyNestedCollectionTests - { - private readonly List _fakeRepository3Nesting; - private readonly List _fakeRepository2Nesting; + private readonly List _fakeRepository3Nesting; + private readonly List _fakeRepository2Nesting; - public GridifyNestedCollectionTests() - { - _fakeRepository3Nesting = new List(GetSampleDataWith3Nesting()); - _fakeRepository2Nesting = new List(GetSampleDataWith2Nestings()); - } + public GridifyNestedCollectionTests() + { + _fakeRepository3Nesting = new List(GetSampleDataWith3Nesting()); + _fakeRepository2Nesting = new List(GetSampleDataWith2Nestings()); + } - [Fact] - public void Filtering_OnThirdLevelNestedProperty() - { - var gm = new GridifyMapper() - .GenerateMappings() - .AddMap("prop1", l1 => l1.Level2List.SelectMany(l2 => l2.Level3List).Select(l3 => l3.Property1)); + [Fact] + public void Filtering_OnThirdLevelNestedProperty() + { + var gm = new GridifyMapper() + .GenerateMappings() + .AddMap("prop1", l1 => l1.Level2List.SelectMany(l2 => l2.Level3List).Select(l3 => l3.Property1)); - var actual = _fakeRepository3Nesting.AsQueryable() - .ApplyFiltering("prop1 <= 3", gm) - .ToList(); + var actual = _fakeRepository3Nesting.AsQueryable() + .ApplyFiltering("prop1 <= 3", gm) + .ToList(); - var expected = _fakeRepository3Nesting.Where(q => q.Level2List.Any(w => w.Level3List.Any(z => z.Property1 <= 3))).ToList(); + var expected = _fakeRepository3Nesting.Where(q => q.Level2List.Any(w => w.Level3List.Any(z => z.Property1 <= 3))).ToList(); - Assert.Equal(expected.Count, actual.Count); - Assert.Equal(expected, actual); - Assert.True(actual.Any()); - } + Assert.Equal(expected.Count, actual.Count); + Assert.Equal(expected, actual); + Assert.True(actual.Any()); + } - [Fact] //https://github.com/alirezanet/Gridify/issues/17 #17 - public void Filtering_OnThirdLevelNestedPropertyWithMultipleChainedConditions() - { - var gm = new GridifyMapper() - .GenerateMappings() - .AddMap("Level2List_Level3List_Property1", l1 => l1.Level2List.SelectMany(l2 => l2.Level3List).Select(l3 => l3.Property1)) - .AddMap("Level2List_Id", l1 => l1.Level2List.Select(l2 => l2.Id)); + [Fact] //https://github.com/alirezanet/Gridify/issues/17 #17 + public void Filtering_OnThirdLevelNestedPropertyWithMultipleChainedConditions() + { + var gm = new GridifyMapper() + .GenerateMappings() + .AddMap("Level2List_Level3List_Property1", l1 => l1.Level2List.SelectMany(l2 => l2.Level3List).Select(l3 => l3.Property1)) + .AddMap("Level2List_Id", l1 => l1.Level2List.Select(l2 => l2.Id)); - var actual = _fakeRepository3Nesting.AsQueryable() - .ApplyFiltering("(Level2List_Id = 101, Level2List_Level3List_Property1 >= 3) , id < 10", gm) - .ToList(); + var actual = _fakeRepository3Nesting.AsQueryable() + .ApplyFiltering("(Level2List_Id = 101, Level2List_Level3List_Property1 >= 3) , id < 10", gm) + .ToList(); - var expected = _fakeRepository3Nesting.AsQueryable().Where( - l1 => l1.Level2List != null && - l1.Level2List.Any(l2 => l2.Id == 101 && - l2.Level3List != null && - l2.Level3List.Any(l3 => l3.Property1 >= 3))).ToList(); + var expected = _fakeRepository3Nesting.AsQueryable().Where( + l1 => l1.Level2List != null && + l1.Level2List.Any(l2 => l2.Id == 101 && + l2.Level3List != null && + l2.Level3List.Any(l3 => l3.Property1 >= 3))).ToList(); - Assert.Equal(expected.Count, actual.Count); - Assert.Equal(expected, actual); - Assert.False(actual.Any()); - } + Assert.Equal(expected.Count, actual.Count); + Assert.Equal(expected, actual); + Assert.False(actual.Any()); + } - [Fact] //https://github.com/alirezanet/Gridify/issues/17 #17 - public void Filtering_OnThirdLevelNestedPropertyWithMultipleUnChainedConditions() - { - var gm = new GridifyMapper() - .GenerateMappings() - .AddMap("Level2List_Level3List_Property1", l1 => l1.Level2List.SelectMany(l2 => l2.Level3List).Select(l3 => l3.Property1)) - .AddMap("Level2List_Id", l1 => l1.Level2List.Select(l2 => l2.Id)); + [Fact] //https://github.com/alirezanet/Gridify/issues/17 #17 + public void Filtering_OnThirdLevelNestedPropertyWithMultipleUnChainedConditions() + { + var gm = new GridifyMapper() + .GenerateMappings() + .AddMap("Level2List_Level3List_Property1", l1 => l1.Level2List.SelectMany(l2 => l2.Level3List).Select(l3 => l3.Property1)) + .AddMap("Level2List_Id", l1 => l1.Level2List.Select(l2 => l2.Id)); - var actual = _fakeRepository3Nesting.AsQueryable() - .ApplyFiltering("Level2List_Id = 101, Level2List_Level3List_Property1 >= 3,id < 10", gm) - .ToList(); + var actual = _fakeRepository3Nesting.AsQueryable() + .ApplyFiltering("Level2List_Id = 101, Level2List_Level3List_Property1 >= 3,id < 10", gm) + .ToList(); - var expected = _fakeRepository3Nesting.AsQueryable().Where( - l1 => l1.Level2List != null && l1.Level2List.Any(l2 => l2.Id == 101) && - l1.Level2List.Any(l2 => l2.Level3List.Any(l3 => l3.Property1 >= 3))).ToList(); + var expected = _fakeRepository3Nesting.AsQueryable().Where( + l1 => l1.Level2List != null && l1.Level2List.Any(l2 => l2.Id == 101) && + l1.Level2List.Any(l2 => l2.Level3List.Any(l3 => l3.Property1 >= 3))).ToList(); - Assert.Equal(expected.Count, actual.Count); - Assert.Equal(expected, actual); - Assert.True(actual.Any()); - } + Assert.Equal(expected.Count, actual.Count); + Assert.Equal(expected, actual); + Assert.True(actual.Any()); + } - [Fact] - public void Filtering_OnThirdLevelNestedPropertyWithMultipleUnChainedConditionsWithNestedParenthesis() - { - var gm = new GridifyMapper() - .GenerateMappings() - .AddMap("Level2List_Level3List_Property1", l1 => l1.Level2List.SelectMany(l2 => l2.Level3List).Select(l3 => l3.Property1)) - .AddMap("Level2List_Id", l1 => l1.Level2List.Select(l2 => l2.Id)); + [Fact] + public void Filtering_OnThirdLevelNestedPropertyWithMultipleUnChainedConditionsWithNestedParenthesis() + { + var gm = new GridifyMapper() + .GenerateMappings() + .AddMap("Level2List_Level3List_Property1", l1 => l1.Level2List.SelectMany(l2 => l2.Level3List).Select(l3 => l3.Property1)) + .AddMap("Level2List_Id", l1 => l1.Level2List.Select(l2 => l2.Id)); - var actual = _fakeRepository3Nesting.AsQueryable() - .ApplyFiltering("( (id < 10 ), Level2List_Id = 101, Level2List_Level3List_Property1 >= 3)", gm) - .ToList(); + var actual = _fakeRepository3Nesting.AsQueryable() + .ApplyFiltering("( (id < 10 ), Level2List_Id = 101, Level2List_Level3List_Property1 >= 3)", gm) + .ToList(); - var expected = _fakeRepository3Nesting.AsQueryable().Where( - l1 => l1.Level2List != null && l1.Level2List.Any(l2 => l2.Id == 101) && - l1.Level2List.Any(l2 => l2.Level3List.Any(l3 => l3.Property1 >= 3))).ToList(); + var expected = _fakeRepository3Nesting.AsQueryable().Where( + l1 => l1.Level2List != null && l1.Level2List.Any(l2 => l2.Id == 101) && + l1.Level2List.Any(l2 => l2.Level3List.Any(l3 => l3.Property1 >= 3))).ToList(); - Assert.Equal(expected.Count, actual.Count); - Assert.Equal(expected, actual); - Assert.True(actual.Any()); - } + Assert.Equal(expected.Count, actual.Count); + Assert.Equal(expected, actual); + Assert.True(actual.Any()); + } - [Fact] - public void Filtering_OnSecondLevelNestedProperty() - { - var gm = new GridifyMapper() - .GenerateMappings() - .AddMap("prop1", l2 => l2.Level3List.Select(l3 => l3.Property1)); + [Fact] + public void Filtering_OnSecondLevelNestedProperty() + { + var gm = new GridifyMapper() + .GenerateMappings() + .AddMap("prop1", l2 => l2.Level3List.Select(l3 => l3.Property1)); - var actual = _fakeRepository2Nesting.AsQueryable() - .ApplyFiltering("prop1 <= 3", gm) - .ToList(); + var actual = _fakeRepository2Nesting.AsQueryable() + .ApplyFiltering("prop1 <= 3", gm) + .ToList(); - var expected = _fakeRepository2Nesting.Where(q => q.Level3List.Any(z => z.Property1 <= 3)).ToList(); + var expected = _fakeRepository2Nesting.Where(q => q.Level3List.Any(z => z.Property1 <= 3)).ToList(); - Assert.Equal(expected.Count, actual.Count); - Assert.Equal(expected, actual); - Assert.True(actual.Any()); - } + Assert.Equal(expected.Count, actual.Count); + Assert.Equal(expected, actual); + Assert.True(actual.Any()); + } - [Fact] - public void Filtering_OnThirdLevelNestedPropertyUsingSecondLevelProp() - { - var gm = new GridifyMapper() - .GenerateMappings() - .AddMap("lvl", l1 => l1.Level2List.Select(l2 => l2.ChildProp).SelectMany(sl2 => sl2.Level3List).Select(l3 => l3.Level)); + [Fact] + public void Filtering_OnThirdLevelNestedPropertyUsingSecondLevelProp() + { + var gm = new GridifyMapper() + .GenerateMappings() + .AddMap("lvl", l1 => l1.Level2List.Select(l2 => l2.ChildProp).SelectMany(sl2 => sl2.Level3List).Select(l3 => l3.Level)); - var actual = _fakeRepository3Nesting.AsQueryable() - .ApplyFiltering("lvl < 2", gm) - .ToList(); + var actual = _fakeRepository3Nesting.AsQueryable() + .ApplyFiltering("lvl < 2", gm) + .ToList(); - var expected = _fakeRepository3Nesting.Where(l1 => l1.Level2List != null && l1.Level2List.Any(l2 => l2.ChildProp != null && - l2.ChildProp.Level3List != null && - l2.ChildProp.Level3List.Any(l3 => l3.Level < 2))).ToList(); + var expected = _fakeRepository3Nesting.Where(l1 => l1.Level2List != null && l1.Level2List.Any(l2 => l2.ChildProp != null && + l2.ChildProp.Level3List != null && + l2.ChildProp.Level3List.Any(l3 => l3.Level < 2))).ToList(); - Assert.Equal(expected.Count, actual.Count); - Assert.Equal(expected, actual); - Assert.True(actual.Any()); - } + Assert.Equal(expected.Count, actual.Count); + Assert.Equal(expected, actual); + Assert.True(actual.Any()); + } - [Fact] // issue #29 https://github.com/alirezanet/Gridify/issues/29 - public void ApplyFiltering_UsingSubCollection_PassIndexes() - { - var gm = new GridifyMapper() - .GenerateMappings() - .AddMap("l3p2", (l1, index) => l1.Level3List[index].Property2); + [Fact] // issue #29 https://github.com/alirezanet/Gridify/issues/29 + public void ApplyFiltering_UsingSubCollection_PassIndexes() + { + var gm = new GridifyMapper() + .GenerateMappings() + .AddMap("l3p2", (l1, index) => l1.Level3List[index].Property2); - var expected = _fakeRepository2Nesting.Where(q => q.Level3List[0].Property2 > 50).ToList(); - var actual = _fakeRepository2Nesting.AsQueryable().ApplyFiltering("l3p2[1] > 50", gm).ToList(); + var expected = _fakeRepository2Nesting.Where(q => q.Level3List[0].Property2 > 50).ToList(); + var actual = _fakeRepository2Nesting.AsQueryable().ApplyFiltering("l3p2[1] > 50", gm).ToList(); - Assert.Equal(expected.Count, actual.Count); - Assert.Equal(expected, actual); - Assert.True(actual.Any()); - } + Assert.Equal(expected.Count, actual.Count); + Assert.Equal(expected, actual); + Assert.True(actual.Any()); + } - #region TestData + #region TestData - private IEnumerable GetSampleDataWith3Nesting() + private IEnumerable GetSampleDataWith3Nesting() + { + var subLvl2 = new ChildProp() { - var subLvl2 = new ChildProp() - { - Level3List = new List() { new Level3() { Property1 = 2.0, Property2 = 100.0, Level = 1 } } - }; - var subLvl22 = new ChildProp() - { - Level3List = new List() { new Level3() { Property1 = 3.0, Property2 = 100.0, Level = 3 } } - }; + Level3List = new List() { new Level3() { Property1 = 2.0, Property2 = 100.0, Level = 1 } } + }; + var subLvl22 = new ChildProp() + { + Level3List = new List() { new Level3() { Property1 = 3.0, Property2 = 100.0, Level = 3 } } + }; - yield return new Level1() + yield return new Level1() + { + Id = 1, + Name = "Level1Name", + Level2List = new List() { - Id = 1, - Name = "Level1Name", - Level2List = new List() + new Level2() + { + Id = 101, Name = "Level2_1", Level3List = new List() { new Level3() { Property1 = 2.0, Property2 = 100.0, Level = 0 } } + }, + new Level2() { - new Level2() - { - Id = 101, Name = "Level2_1", Level3List = new List() { new Level3() { Property1 = 2.0, Property2 = 100.0, Level = 0 } } - }, - new Level2() - { - Id = 102, Name = "Level2_2", Level3List = new List() { new Level3() { Property1 = 3.0, Property2 = 200.0, Level = 1 } } - }, - new Level2() - { - Id = 103, Name = "Level2_3", Level3List = new List() { new Level3() { Property1 = 4.0, Property2 = 300.0, Level = 2 } } - } + Id = 102, Name = "Level2_2", Level3List = new List() { new Level3() { Property1 = 3.0, Property2 = 200.0, Level = 1 } } + }, + new Level2() + { + Id = 103, Name = "Level2_3", Level3List = new List() { new Level3() { Property1 = 4.0, Property2 = 300.0, Level = 2 } } } - }; + } + }; - yield return new Level1() + yield return new Level1() + { + Id = 1, + Name = "Level1Name", + Level2List = new List() { - Id = 1, - Name = "Level1Name", - Level2List = new List() + new Level2() { - new Level2() - { - Id = 101, Name = "Level2_1", ChildProp = subLvl2, - Level3List = new List() { new Level3() { Property1 = 2.0, Property2 = 100.0, Level = 0 } } - }, - new Level2() - { - Id = 102, Name = "Level2_2", ChildProp = new ChildProp(), - Level3List = new List() { new Level3() { Property1 = 3.0, Property2 = 200.0, Level = 0 } } - }, - new Level2() - { - Id = 103, Name = "Level2_3", Level3List = new List() { new Level3() { Property1 = 4.0, Property2 = 300.0, Level = 0 } } - } - } - }; - yield return new Level1() - { - Id = 2, - Name = "Level1Name2", - Level2List = new List() + Id = 101, Name = "Level2_1", ChildProp = subLvl2, + Level3List = new List() { new Level3() { Property1 = 2.0, Property2 = 100.0, Level = 0 } } + }, + new Level2() { - new Level2() - { - Id = 108, Name = "Level2_1", Level3List = new List() { new Level3() { Property1 = 4.0, Property2 = 100.0, Level = 0 } } - }, - new Level2() - { - Id = 109, Name = "Level2_2", ChildProp = new ChildProp(), - Level3List = new List() { new Level3() { Property1 = 5.0, Property2 = 200.0, Level = 0 } } - }, - new Level2() - { - Id = 110, Name = "Level2_3", ChildProp = subLvl22, - Level3List = new List() { new Level3() { Property1 = 6.0, Property2 = 300.0, Level = 0 } } - } - } - }; - yield return new Level1() - { - Id = 3, - Name = "Level1Name3", - Level2List = new List() + Id = 102, Name = "Level2_2", ChildProp = new ChildProp(), + Level3List = new List() { new Level3() { Property1 = 3.0, Property2 = 200.0, Level = 0 } } + }, + new Level2() { - new Level2() - { - Id = 111, Name = "Level2_1", Level3List = new List() { new Level3() { Property1 = 6.0, Property2 = 100.0, Level = 0 } } - }, - new Level2() - { - Id = 112, Name = "Level2_2", Level3List = new List() { new Level3() { Property1 = 7.0, Property2 = 200.0, Level = 0 } } - }, - new Level2() - { - Id = 113, Name = "Level2_3", Level3List = new List() { new Level3() { Property1 = 8.0, Property2 = 300.0, Level = 0 } } - } + Id = 103, Name = "Level2_3", Level3List = new List() { new Level3() { Property1 = 4.0, Property2 = 300.0, Level = 0 } } } - }; - } - - private IEnumerable GetSampleDataWith2Nestings() + } + }; + yield return new Level1() { - yield return new Level2() + Id = 2, + Name = "Level1Name2", + Level2List = new List() { - Id = 1, - Name = "Level2Name", - Level3List = new List() + new Level2() + { + Id = 108, Name = "Level2_1", Level3List = new List() { new Level3() { Property1 = 4.0, Property2 = 100.0, Level = 0 } } + }, + new Level2() { - new Level3() { Property1 = 2.0, Property2 = 100.0, Level = 0 }, - new Level3() { Property1 = 3.0, Property2 = 100.0, Level = 0 }, - new Level3() { Property1 = 4.0, Property2 = 100.0, Level = 0 } + Id = 109, Name = "Level2_2", ChildProp = new ChildProp(), + Level3List = new List() { new Level3() { Property1 = 5.0, Property2 = 200.0, Level = 0 } } + }, + new Level2() + { + Id = 110, Name = "Level2_3", ChildProp = subLvl22, + Level3List = new List() { new Level3() { Property1 = 6.0, Property2 = 300.0, Level = 0 } } } - }; - yield return new Level2() + } + }; + yield return new Level1() + { + Id = 3, + Name = "Level1Name3", + Level2List = new List() { - Id = 2, - Name = "Level2Name2", - Level3List = new List() + new Level2() + { + Id = 111, Name = "Level2_1", Level3List = new List() { new Level3() { Property1 = 6.0, Property2 = 100.0, Level = 0 } } + }, + new Level2() + { + Id = 112, Name = "Level2_2", Level3List = new List() { new Level3() { Property1 = 7.0, Property2 = 200.0, Level = 0 } } + }, + new Level2() { - new Level3() { Property1 = 4.0, Property2 = 100.0, Level = 0 }, - new Level3() { Property1 = 5.0, Property2 = 100.0, Level = 0 }, - new Level3() { Property1 = 6.0, Property2 = 100.0, Level = 0 } + Id = 113, Name = "Level2_3", Level3List = new List() { new Level3() { Property1 = 8.0, Property2 = 300.0, Level = 0 } } } - }; - } -#nullable disable - public class Level1 - { - public int Id { get; set; } - public string Name { get; set; } - public List Level2List { get; set; } - } + } + }; + } - public class Level2 + private IEnumerable GetSampleDataWith2Nestings() + { + yield return new Level2() { - public int Id { get; set; } - public string Name { get; set; } - public List Level3List { get; set; } + Id = 1, + Name = "Level2Name", + Level3List = new List() + { + new Level3() { Property1 = 2.0, Property2 = 100.0, Level = 0 }, + new Level3() { Property1 = 3.0, Property2 = 100.0, Level = 0 }, + new Level3() { Property1 = 4.0, Property2 = 100.0, Level = 0 } + } + }; + yield return new Level2() + { + Id = 2, + Name = "Level2Name2", + Level3List = new List() + { + new Level3() { Property1 = 4.0, Property2 = 100.0, Level = 0 }, + new Level3() { Property1 = 5.0, Property2 = 100.0, Level = 0 }, + new Level3() { Property1 = 6.0, Property2 = 100.0, Level = 0 } + } + }; + } +#nullable disable + public class Level1 + { + public int Id { get; set; } + public string Name { get; set; } + public List Level2List { get; set; } + } - public ChildProp ChildProp { get; set; } - } + public class Level2 + { + public int Id { get; set; } + public string Name { get; set; } + public List Level3List { get; set; } - public class ChildProp - { - public List Level3List { get; set; } - } + public ChildProp ChildProp { get; set; } + } - public class Level3 - { - public int Level { get; set; } - public double Property1 { get; set; } - public double Property2 { get; set; } - } + public class ChildProp + { + public List Level3List { get; set; } + } - #endregion + public class Level3 + { + public int Level { get; set; } + public double Property1 { get; set; } + public double Property2 { get; set; } } + + #endregion } \ No newline at end of file diff --git a/test/Gridify.Tests/Issue36Tests.cs b/test/Gridify.Tests/Issue36Tests.cs index 2745890a..8008adf1 100644 --- a/test/Gridify.Tests/Issue36Tests.cs +++ b/test/Gridify.Tests/Issue36Tests.cs @@ -4,85 +4,84 @@ // ReSharper disable All #pragma warning disable 8618 -namespace Gridify.Tests +namespace Gridify.Tests; + +// issue #36 - https://github.com/alirezanet/Gridify/issues/36 +public class Issue36Tests { - // issue #36 - https://github.com/alirezanet/Gridify/issues/36 - public class Issue36Tests + [Fact] + private void UserReportTest1() { - [Fact] - private void UserReportTest1() - { - List level1List = new List(); - level1List.Add(new Level1()); + List level1List = new List(); + level1List.Add(new Level1()); - var gridifyMapper = new GridifyMapper().GenerateMappings() - .AddMap("level4_property1", (l1, index) => l1.Level2List.Select(x => x.Level3.Level4List[index].Property1)); + var gridifyMapper = new GridifyMapper().GenerateMappings() + .AddMap("level4_property1", (l1, index) => l1.Level2List.Select(x => x.Level3.Level4List[index].Property1)); - var gridifyQuery = new GridifyQuery() { Filter = "level4_property1[0] > 5" }; - var query = gridifyQuery.GetFilteringExpression(gridifyMapper); - var expression = query.Compile(); + var gridifyQuery = new GridifyQuery() { Filter = "level4_property1[0] > 5" }; + var query = gridifyQuery.GetFilteringExpression(gridifyMapper); + var expression = query.Compile(); - var actual = level1List.Where(expression).ToList(); + var actual = level1List.Where(expression).ToList(); - Assert.Single(actual); - Assert.True(actual.Any()); - } + Assert.Single(actual); + Assert.True(actual.Any()); + } - public class Level1 - { - public string Name { get; set; } + public class Level1 + { + public string Name { get; set; } - public Level2[] Level2List = new Level2[] + public Level2[] Level2List = new Level2[] + { + new Level2() { - new Level2() + Name = "1", + Level3 = new Level3() { - Name = "1", - Level3 = new Level3() + Name = "2", + Level4List = new List() { - Name = "2", - Level4List = new List() - { - new Level4() { Name = "3", Property1 = 3, Property2 = 4 }, - new Level4() { Name = "4", Property1 = 4, Property2 = 5 }, - new Level4() { Name = "5", Property1 = 5, Property2 = 6 } - } + new Level4() { Name = "3", Property1 = 3, Property2 = 4 }, + new Level4() { Name = "4", Property1 = 4, Property2 = 5 }, + new Level4() { Name = "5", Property1 = 5, Property2 = 6 } } - }, + } + }, - new Level2() + new Level2() + { + Name = "6", + Level3 = new Level3() { - Name = "6", - Level3 = new Level3() + Name = "7", + Level4List = new List() { - Name = "7", - Level4List = new List() - { - new Level4() { Name = "8", Property1 = 8, Property2 = 9 }, - new Level4() { Name = "9", Property1 = 9, Property2 = 10 }, - new Level4() { Name = "10", Property1 = 10, Property2 = 11 } - } + new Level4() { Name = "8", Property1 = 8, Property2 = 9 }, + new Level4() { Name = "9", Property1 = 9, Property2 = 10 }, + new Level4() { Name = "10", Property1 = 10, Property2 = 11 } } - }, - }; - } + } + }, + }; + } - public class Level2 - { - public string Name { get; set; } - public Level3 Level3 = new Level3(); - } + public class Level2 + { + public string Name { get; set; } + public Level3 Level3 = new Level3(); + } - public class Level3 - { - public string Name { get; set; } - public List Level4List = new List(); - } + public class Level3 + { + public string Name { get; set; } + public List Level4List = new List(); + } - public class Level4 - { - public string Name { get; set; } - public double Property1 { get; set; } - public double Property2 { get; set; } - } + public class Level4 + { + public string Name { get; set; } + public double Property1 { get; set; } + public double Property2 { get; set; } } -} +} \ No newline at end of file diff --git a/test/Gridify.Tests/QueryBuilderShould.cs b/test/Gridify.Tests/QueryBuilderShould.cs index 3264c21f..fc581dd4 100644 --- a/test/Gridify.Tests/QueryBuilderShould.cs +++ b/test/Gridify.Tests/QueryBuilderShould.cs @@ -2,90 +2,147 @@ using System.Linq; using Xunit; -namespace Gridify.Tests +namespace Gridify.Tests; + +public class QueryBuilderShould { - public class QueryBuilderShould + private readonly List _fakeRepository; + private int _testRecordsCount => _fakeRepository.Count; + + public QueryBuilderShould() { - private readonly List _fakeRepository; - private int _testRecordsCount => _fakeRepository.Count; + _fakeRepository = new List(GridifyExtensionsShould.GetSampleData()); + } - public QueryBuilderShould() - { - _fakeRepository = new List(GridifyExtensionsShould.GetSampleData()); - } + [Fact] + public void Builder_Should_Return_All_Records_When_No_Filters_Are_Specified() + { + var builder = new QueryBuilder(); + + var collection = builder.Build(_fakeRepository); + var query = builder.Build(_fakeRepository.AsQueryable()); + var (count1, _) = builder.BuildWithPaging(_fakeRepository.AsQueryable()); + var (count2, _) = builder.BuildWithPaging(_fakeRepository); + var (count3, _) = builder.BuildWithQueryablePaging(_fakeRepository.AsQueryable()); + + Assert.Equal(_testRecordsCount, collection.Count()); + Assert.Equal(_testRecordsCount, query.Count()); + Assert.Equal(_testRecordsCount, count1); + Assert.Equal(_testRecordsCount, count2); + Assert.Equal(_testRecordsCount, count3); + } - [Fact] - public void Builder_Should_Return_All_Records_When_No_Filters_Are_Specified() - { - var builder = new QueryBuilder(); - - var collection = builder.Build(_fakeRepository); - var query = builder.Build(_fakeRepository.AsQueryable()); - var (count1, _) = builder.BuildWithPaging(_fakeRepository.AsQueryable()); - var (count2, _) = builder.BuildWithPaging(_fakeRepository); - var (count3, _) = builder.BuildWithQueryablePaging(_fakeRepository.AsQueryable()); - - Assert.Equal(_testRecordsCount, collection.Count()); - Assert.Equal(_testRecordsCount, query.Count()); - Assert.Equal(_testRecordsCount, count1); - Assert.Equal(_testRecordsCount, count2); - Assert.Equal(_testRecordsCount, count3); - } + [Fact] // issue #38 + public void Evaluator_Should_Check_All_Conditions_Without_And() + { + var builder = new QueryBuilder() + .AddCondition("name =Ali, id < 6") + .AddCondition("name =Sara, Id > 6"); - [Fact] // issue #38 - public void Evaluator_Should_Check_All_Conditions_Without_And() - { - var builder = new QueryBuilder() - .AddCondition("name =Ali, id < 6") - .AddCondition("name =Sara, Id > 6"); + // using CollectionEvaluator + var evaluator = builder.BuildCompiledEvaluator(); + Assert.True(evaluator(_fakeRepository)); - // using CollectionEvaluator - var evaluator = builder.BuildCompiledEvaluator(); - Assert.True(evaluator(_fakeRepository)); + // using QueryableEvaluator + var queryableEvaluator = builder.BuildEvaluator(); + Assert.True(queryableEvaluator(_fakeRepository.AsQueryable())); - // using QueryableEvaluator - var queryableEvaluator = builder.BuildEvaluator(); - Assert.True(queryableEvaluator(_fakeRepository.AsQueryable())); + // Using Evaluate method (collection) + var isValid = builder.Evaluate(_fakeRepository); + Assert.True(isValid); - // Using Evaluate method (collection) - var isValid = builder.Evaluate(_fakeRepository); - Assert.True(isValid); + // Using Evaluate method (queryable) + var isQueryValid = builder.Evaluate(_fakeRepository.AsQueryable()); + Assert.True(isQueryValid); + } - // Using Evaluate method (queryable) - var isQueryValid = builder.Evaluate(_fakeRepository.AsQueryable()); - Assert.True(isQueryValid); - } + [Fact] + public void Build() + { + var builder = new QueryBuilder() + .AddCondition("name =*al") + .AddOrderBy("name"); - [Fact] - public void Build() - { - var builder = new QueryBuilder() - .AddCondition("name =*al") - .AddOrderBy("name"); + var compiled = builder.Build(); + var result = compiled(_fakeRepository.AsQueryable()); + Assert.True(result.Any()); + } - var compiled = builder.Build(); - var result = compiled(_fakeRepository.AsQueryable()); - Assert.True(result.Any()); + [Fact] + public void BuildFilteringExpression_Should_Return_Correct_Expression() + { + var builder = new QueryBuilder() + .AddCondition("name =*a") + .AddCondition("id > 2"); - } + var expectedExpressionString = new GridifyQuery() { Filter = "name=*a,id>2" }.GetFilteringExpression().ToString(); + var actualExpression = builder.BuildFilteringExpression(); - [Fact] - public void BuildFilteringExpression_Should_Return_Correct_Expression() - { - var builder = new QueryBuilder() - .AddCondition("name =*a") - .AddCondition("id > 2"); + var expectedResult = _fakeRepository.Where(q => + q.Id > 2 && q.Name != null && q.Name.Contains("a")); - var expectedExpressionString = new GridifyQuery() { Filter = "name=*a,id>2" }.GetFilteringExpression().ToString(); - var actualExpression = builder.BuildFilteringExpression(); + var actualResult = _fakeRepository.AsQueryable().Where(actualExpression); - var expectedResult = _fakeRepository.Where(q => - q.Id > 2 && q.Name != null && q.Name.Contains("a")); + Assert.Equal(expectedExpressionString, actualExpression.ToString()); + Assert.Equal(expectedResult, actualResult); + } + + #region Validation + + [Theory] + [InlineData(new[] { "notExist=123" }, false)] + [InlineData(new[] { "name=ali" }, true)] + [InlineData(new[] { "name=ali", "id>24" }, true)] + [InlineData(new[] { "name=ali", "id," }, false)] + [InlineData(new[] { "name123,123" }, false)] + [InlineData(new[] { "!name<23" }, false)] + [InlineData(new[] { "(id=2,tag=someTag)" }, true)] + [InlineData(new[] { "@name=john" }, false)] + //[InlineData("id(); + var actual = true; + foreach (var filter in filters) + { + qb.AddCondition(filter); + // act + actual = actual && qb.IsValid(); + } - var actualResult = _fakeRepository.AsQueryable().Where(actualExpression); + // assert + Assert.Equal(expected, actual); + } - Assert.Equal(expectedExpressionString, actualExpression.ToString()); - Assert.Equal(expectedResult, actualResult); + [Theory] + [InlineData(new[] { "notExist" }, false)] + [InlineData(new[] { "name" }, true)] + [InlineData(new[] { "name" , "id asc" } , true)] + [InlineData(new[] { "name" , "id des" } , false)] + [InlineData(new[] { "name" , "dss" } , false)] + [InlineData(new[] { "id," , "name" } , false)] + [InlineData(new[] { "id" , "name" , "date" } , false)] + [InlineData(new[] { "id" , "name" , "date2" } , false)] + [InlineData(new[] { "name," }, false)] + [InlineData(new[] { "!name" }, false)] + [InlineData(new[] { "name des" }, false)] + [InlineData(new[] { "id,name" }, true)] + [InlineData(new[] { "id asc,name desc" }, true)] + [InlineData(new[] { "id asc,name2 desc" }, false)] + public void IsValid_IGridifyOrdering_ShouldReturnExpectedResult(string[] orders, bool expected) + { + var qb = new QueryBuilder(); + var actual = true; + foreach (var order in orders) + { + qb.AddOrderBy(order); + // act + actual = actual && qb.IsValid(); } + + // assert + Assert.Equal(expected, actual); } + + #endregion } diff --git a/test/Gridify.Tests/TestClass.cs b/test/Gridify.Tests/TestClass.cs index 54bd05bd..eda71ad7 100644 --- a/test/Gridify.Tests/TestClass.cs +++ b/test/Gridify.Tests/TestClass.cs @@ -1,41 +1,40 @@ using System; -namespace Gridify.Tests +namespace Gridify.Tests; + +public class TestClass : ICloneable { - public class TestClass : ICloneable + public TestClass() { - public TestClass() - { - } + } - public TestClass(int id, string name, TestClass? classProp, Guid myGuid = default, DateTime? date = default, string? tag = "") - { - Id = id; - Name = name; - ChildClass = classProp; - MyGuid = myGuid; - MyDateTime = date; - Tag = tag; - } + public TestClass(int id, string name, TestClass? classProp, Guid myGuid = default, DateTime? date = default, string? tag = "") + { + Id = id; + Name = name; + ChildClass = classProp; + MyGuid = myGuid; + MyDateTime = date; + Tag = tag; + } - public int Id { get; set; } - public string? Name { get; set; } = string.Empty; - public TestClass? ChildClass { get; set; } - public DateTime? MyDateTime { get; set; } - public Guid MyGuid { get; set; } - public string? Tag { get; set; } + public int Id { get; set; } + public string? Name { get; set; } = string.Empty; + public TestClass? ChildClass { get; set; } + public DateTime? MyDateTime { get; set; } + public Guid MyGuid { get; set; } + public string? Tag { get; set; } - public object Clone() + public object Clone() + { + return new TestClass { - return new TestClass - { - Id = Id, - Name = Name, - ChildClass = (TestClass)ChildClass?.Clone()!, - MyGuid = MyGuid, - Tag = Tag - }; - } + Id = Id, + Name = Name, + ChildClass = (TestClass)ChildClass?.Clone()!, + MyGuid = MyGuid, + Tag = Tag + }; } } \ No newline at end of file