From 6171d256a4260344f4ad123e745aa77e5994e45d Mon Sep 17 00:00:00 2001 From: AliReZa Sabouri <7004080+alirezanet@users.noreply.github.com> Date: Fri, 10 Dec 2021 17:42:38 +0330 Subject: [PATCH] Next- v2.4.6 (#54) * update ordering description * update packages * Lazy map generation. performance improvement * benchmark lazy map generation * remove public access to fixMapper method * update to v2.4.6 * update library comparision benchmark result * fix spelling mistake * AddMap always throw exception * check IgnoreNotMappedFields in fixMapper --- benchmark/Benchmark.csproj | 6 +- benchmark/GridifyMapperUsages.cs | 149 ++++++++++++++++++ .../LibraryComparisionFilteringBenchmark.cs | 56 ++----- benchmark/Program.cs | 1 + docs/guide/README.md | 22 +-- docs/guide/autoMapper.md | 2 - docs/guide/filtering.md | 2 +- docs/guide/getting-started.md | 2 +- docs/guide/ordering.md | 2 +- .../Gridify.EntityFramework.csproj | 2 +- .../GridifyExtensions.cs | 15 +- src/Gridify/Gridify.csproj | 2 +- src/Gridify/GridifyExtensions.cs | 133 ++++++++++++---- src/Gridify/GridifyMapper.cs | 28 +++- src/Gridify/IGridifyMapper.cs | 3 +- .../EntityFramework6IntegrationTests.csproj | 4 +- .../packages.config | 2 +- .../EntityFrameworkIntegrationTests.cs.csproj | 4 +- ...rameworkSqlProviderIntegrationTests.csproj | 6 +- test/Gridify.Tests/Gridify.Tests.csproj | 7 +- 20 files changed, 334 insertions(+), 114 deletions(-) create mode 100644 benchmark/GridifyMapperUsages.cs diff --git a/benchmark/Benchmark.csproj b/benchmark/Benchmark.csproj index 803c242c..034107fe 100644 --- a/benchmark/Benchmark.csproj +++ b/benchmark/Benchmark.csproj @@ -7,10 +7,10 @@ - + - - + + diff --git a/benchmark/GridifyMapperUsages.cs b/benchmark/GridifyMapperUsages.cs new file mode 100644 index 00000000..d29d38c6 --- /dev/null +++ b/benchmark/GridifyMapperUsages.cs @@ -0,0 +1,149 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using BenchmarkDotNet.Attributes; +using BenchmarkDotNet.Engines; +using BenchmarkDotNet.Order; +using Gridify; +using Gridify.Tests; + +namespace Benchmarks +{ + [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 IQueryable Ds => _data.AsQueryable(); + private IEnumerable EnumerableDs => _data.ToList(); + private IGridifyMapper ggm { get; set; } + + [GlobalSetup] + public void Setup() + { + _data = GetSampleData().ToArray(); + + 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(); + } + + + [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"); + + 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 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; + } + } +} + +// 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 +// +// +// | Method | Mean | Error | StdDev | Ratio | RatioSD | Gen 0 | Gen 1 | Allocated | +// |------------------------------- |---------:|---------:|---------:|------:|--------:|-------:|-------:|----------:| +// | NativeLinQ | 826.9 us | 13.49 us | 12.61 us | 1.00 | 0.00 | 4.8828 | 1.9531 | 35 KB | +// | Gridify_SingleMapper_Manual | 846.4 us | 8.64 us | 7.66 us | 1.02 | 0.02 | 5.8594 | 2.9297 | 41 KB | +// | Gridify_SingleMapper_Generated | 847.8 us | 10.72 us | 9.51 us | 1.02 | 0.02 | 6.8359 | 2.9297 | 43 KB | +// | Gridify_GlobalMapper | 854.6 us | 15.35 us | 19.42 us | 1.04 | 0.03 | 5.8594 | 2.9297 | 40 KB | +// | Gridify_NoMapper | 876.5 us | 17.33 us | 28.48 us | 1.07 | 0.04 | 5.8594 | 1.9531 | 44 KB | +// | Gridify_EachAction_Generated | 877.4 us | 16.82 us | 20.66 us | 1.06 | 0.03 | 7.8125 | 3.9063 | 48 KB | diff --git a/benchmark/LibraryComparisionFilteringBenchmark.cs b/benchmark/LibraryComparisionFilteringBenchmark.cs index 6828f6a4..a861238c 100644 --- a/benchmark/LibraryComparisionFilteringBenchmark.cs +++ b/benchmark/LibraryComparisionFilteringBenchmark.cs @@ -22,35 +22,20 @@ public class LibraryComparisionFilteringBenchmark { 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 gm { get; set; } [GlobalSetup] public void Setup() { _data = GetSampleData().ToArray(); - - gm = 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(gm).Compile(); - compiled2 = gq2.GetFilteringExpression(gm).Compile(); - compiled3 = gq3.GetFilteringExpression(gm).Compile(); } [Benchmark(Baseline = true)] public void NativeLinQ() { - Ds.Where(q => q.Name.Contains("a")).Consume(Consumer); + 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); } @@ -58,19 +43,12 @@ public void NativeLinQ() [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] // compiled query (this is not included in our readme benchmarks)w - public void GridifyCompiled() - { - EnumerableDs.Where(compiled1).Consume(Consumer); - EnumerableDs.Where(compiled2).Consume(Consumer); - EnumerableDs.Where(compiled3).Consume(Consumer); - } - [Benchmark] public void Fop() { @@ -131,19 +109,17 @@ public static IEnumerable GetSampleData() } } -/* Last Run: - BenchmarkDotNet=v0.13.0, OS=Windows 10.0.19043.1237 (21H1/May2021Update) -11th Gen Intel Core i5-11400F 2.60GHz, 1 CPU, 12 logical and 6 physical cores -.NET SDK=5.0.301 -[Host] : .NET 5.0.7 (5.0.721.25508), X64 RyuJIT -DefaultJob : .NET 5.0.7 (5.0.721.25508), X64 RyuJIT - - -| Method | Mean | Error | StdDev | Ratio | Gen 0 | Gen 1 | Allocated | -|------------ |-----------:|---------:|---------:|------:|--------:|--------:|----------:| -| Native LINQ | 740.9 us | 7.80 us | 6.92 us | 1.00 | 5.8594 | 2.9297 | 37 KB | -| Gridify | 762.6 us | 10.06 us | 9.41 us | 1.03 | 5.8594 | 2.9297 | 39 KB | -| DynamicLinq | 902.1 us | 11.56 us | 10.81 us | 1.22 | 19.5313 | 9.7656 | 122 KB | -| Sieve | 977.9 us | 6.80 us | 6.37 us | 1.32 | 7.8125 | 3.9063 | 54 KB | -| Fop | 2,959.8 us | 39.11 us | 36.58 us | 3.99 | 46.8750 | 23.4375 | 306 KB | -*/ +// 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 +// +// +// | 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 | diff --git a/benchmark/Program.cs b/benchmark/Program.cs index 718ffe99..3d62ac0a 100644 --- a/benchmark/Program.cs +++ b/benchmark/Program.cs @@ -8,6 +8,7 @@ public class Program private static void Main() { BenchmarkRunner.Run(); + // BenchmarkRunner.Run(); // BenchmarkRunner.Run(); // BenchmarkRunner.Run(); Console.Read(); diff --git a/docs/guide/README.md b/docs/guide/README.md index 5d787cea..328c625b 100644 --- a/docs/guide/README.md +++ b/docs/guide/README.md @@ -17,18 +17,18 @@ 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 | StdDev | Ratio | Gen 0 | Gen 1 | Allocated | -|------------ |-----------:|---------:|---------:|------:|--------:|--------:|----------:| -| Native LINQ | 740.9 us | 7.80 us | 6.92 us | 1.00 | 5.8594 | 2.9297 | 37 KB | -| **Gridify*** | 762.6 us | 10.06 us | 9.41 us | 1.03 | 5.8594 | 2.9297 | 39 KB | -| DynamicLinq | 902.1 us | 11.56 us | 10.81 us | 1.22 | 19.5313 | 9.7656 | 122 KB | -| Sieve | 977.9 us | 6.80 us | 6.37 us | 1.32 | 7.8125 | 3.9063 | 54 KB | -| Fop | 2,959.8 us | 39.11 us | 36.58 us | 3.99 | 46.8750 | 23.4375 | 306 KB | +| 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 | ::: details -BenchmarkDotNet=v0.13.0, OS=Windows 10.0.19043.1237 (21H1/May2021Update) +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=5.0.301 -[Host] : .NET 5.0.7 (5.0.721.25508), X64 RyuJIT -DefaultJob : .NET 5.0.7 (5.0.721.25508), X64 RyuJIT +.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 ::: diff --git a/docs/guide/autoMapper.md b/docs/guide/autoMapper.md index 2e25c09d..a24d0bc3 100644 --- a/docs/guide/autoMapper.md +++ b/docs/guide/autoMapper.md @@ -23,7 +23,6 @@ Gridify library does not have a built-in GridifyTo extension method because we d public static Paging GridifyTo(this IQueryable query, IMapper autoMapper, IGridifyQuery gridifyQuery, IGridifyMapper mapper = null) { - mapper = mapper.FixMapper(); var res = query.GridifyQueryable(gridifyQuery, mapper); return new Paging (res.Count , res.Query.ProjectTo(autoMapper.ConfigurationProvider).ToList()); } @@ -34,7 +33,6 @@ public static Paging GridifyTo(this IQuerya public static async Task> GridifyToAsync(this IQueryable query, IMapper autoMapper, IGridifyQuery gridifyQuery, IGridifyMapper mapper = null) { - mapper = mapper.FixMapper(); var res = await query.GridifyQueryableAsync(gridifyQuery, mapper); return new Paging (res.Count , await res.Query.ProjectTo(autoMapper.ConfigurationProvider).ToListAsync()); } diff --git a/docs/guide/filtering.md b/docs/guide/filtering.md index 43d7f258..a4fc3c3f 100644 --- a/docs/guide/filtering.md +++ b/docs/guide/filtering.md @@ -57,7 +57,7 @@ this query matches with JOHN - john - John - jOHn ... ## Escaping -Gridify have five special operators `, | ( ) /i` to handle complex queries and case-insensitive searchs. If you want to use these characters in your query values (after conditional operator), you should add a backslash \ before them. having this regex could be helpfull `([(),|]|\/i)`. +Gridify have five special operators `, | ( ) /i` to handle complex queries and case-insensitive searches. If you want to use these characters in your query values (after conditional operator), you should add a backslash \ before them. having this regex could be helpfull `([(),|]|\/i)`. JavaScript escape example: ``` javascript diff --git a/docs/guide/getting-started.md b/docs/guide/getting-started.md index 1a82b961..ad389dd9 100644 --- a/docs/guide/getting-started.md +++ b/docs/guide/getting-started.md @@ -2,7 +2,7 @@ export default { setup() { return { - version: '2.4.5' + version: '2.4.6' } } } diff --git a/docs/guide/ordering.md b/docs/guide/ordering.md index 09d4ad33..0d7633d7 100644 --- a/docs/guide/ordering.md +++ b/docs/guide/ordering.md @@ -1,6 +1,6 @@ # Ordering Syntax -The ordering query expression can be built with a comma-separated field names followed by **`asc`** or **`desc`** keywords. +The ordering query expression can be built with a comma-delimited ordered list of field/property names followed by **`asc`** or **`desc`** keywords. by default, if you don't add these keywords, gridify assumes you need Ascending ordering. diff --git a/src/Gridify.EntityFramework/Gridify.EntityFramework.csproj b/src/Gridify.EntityFramework/Gridify.EntityFramework.csproj index 111ce60f..4dcfe053 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.5 + 2.4.6 Alireza Sabouri TuxTeam Gridify (EntityFramework), Easy and optimized way to apply Filtering, Sorting, and Pagination using text-based data. diff --git a/src/Gridify.EntityFramework/GridifyExtensions.cs b/src/Gridify.EntityFramework/GridifyExtensions.cs index a87be682..9cf11765 100644 --- a/src/Gridify.EntityFramework/GridifyExtensions.cs +++ b/src/Gridify.EntityFramework/GridifyExtensions.cs @@ -21,9 +21,8 @@ public async static Task> GridifyQueryableAsync(this IQuer public static async Task> GridifyAsync(this IQueryable query, IGridifyQuery gridifyQuery, IGridifyMapper mapper = null) { - mapper = mapper.FixMapper(); - var res = await query.GridifyQueryableAsync(gridifyQuery, mapper); - return new Paging(res.Count, await res.Query.ToListAsync()); + var (count, queryable) = await query.GridifyQueryableAsync(gridifyQuery, mapper); + return new Paging(count, await queryable.ToListAsync()); } public async static Task> GridifyQueryableAsync(this IQueryable query, IGridifyQuery gridifyQuery, @@ -36,13 +35,13 @@ public async static Task> GridifyQueryableAsync(this IQuer return new QueryablePaging(count, query); } - public static async Task> GridifyAsync(this IQueryable query, IGridifyQuery gridifyQuery, CancellationToken token, IGridifyMapper mapper = null) + public static async Task> GridifyAsync(this IQueryable query, IGridifyQuery gridifyQuery, CancellationToken token, + IGridifyMapper mapper = null) { - mapper = mapper.FixMapper(); - var res = await query.GridifyQueryableAsync(gridifyQuery, mapper, token); - return new Paging(res.Count, await res.Query.ToListAsync(token)); + var (count, queryable) = await query.GridifyQueryableAsync(gridifyQuery, mapper, token); + return new Paging(count, await queryable.ToListAsync(token)); } #endregion } -} \ No newline at end of file +} diff --git a/src/Gridify/Gridify.csproj b/src/Gridify/Gridify.csproj index 6b9bd64b..e3eabebc 100644 --- a/src/Gridify/Gridify.csproj +++ b/src/Gridify/Gridify.csproj @@ -3,7 +3,7 @@ netstandard2.0 Gridify - 2.4.5 + 2.4.6 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 a8b76cc6..b9ae386f 100644 --- a/src/Gridify/GridifyExtensions.cs +++ b/src/Gridify/GridifyExtensions.cs @@ -41,13 +41,12 @@ public static Expression> GetFilteringExpression(this IGridifyF if (string.IsNullOrWhiteSpace(gridifyFiltering.Filter)) throw new GridifyQueryException("Filter is not defined"); - mapper = mapper.FixMapper(); - var syntaxTree = SyntaxTree.Parse(gridifyFiltering.Filter!); if (syntaxTree.Diagnostics.Any()) throw new GridifyFilteringException(syntaxTree.Diagnostics.Last()!); + 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; @@ -56,17 +55,34 @@ public static Expression> GetFilteringExpression(this IGridifyF public static IEnumerable>> GetOrderingExpressions(this IGridifyOrdering gridifyOrdering, IGridifyMapper? mapper = null) { - mapper = mapper.FixMapper(); - if (string.IsNullOrWhiteSpace(gridifyOrdering.OrderBy)) throw new GridifyQueryException("OrderBy is not defined or not Found"); - foreach (var (member, _) in ParseOrderings(gridifyOrdering.OrderBy!)) + + var members = ParseOrderings(gridifyOrdering.OrderBy!).Select(q => q.memberName).ToList(); + if (mapper is null) { - // skip if there is no mappings available - if (!mapper.HasMap(member)) continue; + foreach (var member in members) + { + Expression>? exp = null; + try + { + exp = GridifyMapper.CreateExpression(member); + } + catch (Exception) + { + // skip if there is no mappings available + } - yield return mapper.GetExpression(member)!; + if (exp != null) yield return exp; + } + } + else + { + foreach (var member in members.Where(mapper.HasMap)) + { + yield return mapper.GetExpression(member)!; + } } } @@ -86,11 +102,41 @@ private static IGridifyMapper GetDefaultMapper() /// 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 - public static IGridifyMapper FixMapper(this IGridifyMapper? mapper) + private static IGridifyMapper FixMapper(this IGridifyMapper? mapper, SyntaxTree syntaxTree) { - return mapper ?? GetDefaultMapper(); + if (mapper != null) return mapper; + + mapper = new GridifyMapper(); + foreach (var field in syntaxTree.Root.Descendants() + .Where(q => q.Kind == SyntaxKind.FieldExpression) + .Cast()) + { + try + { + mapper.AddMap(field.FieldToken.Text); + } + catch (Exception) + { + if (!mapper.Configuration.IgnoreNotMappedFields) + throw new GridifyMapperException($"Property '{field.FieldToken.Text}' not found."); + } + } + + return mapper; + } + + private static IEnumerable Descendants(this SyntaxNode root) + { + var nodes = new Stack(new[] { root }); + while (nodes.Any()) + { + SyntaxNode node = nodes.Pop(); + yield return node; + foreach (var n in node.GetChildren()) nodes.Push(n); + } } /// @@ -105,7 +151,6 @@ public static IQueryable ApplyFilteringOrderingPaging(this IQueryable q IGridifyMapper? mapper = null) { if (gridifyQuery == null) return query; - mapper = mapper.FixMapper(); query = query.ApplyFiltering(gridifyQuery, mapper); query = query.ApplyOrdering(gridifyQuery, mapper); @@ -126,7 +171,6 @@ public static IQueryable ApplyOrdering(this IQueryable query, IGridifyO bool startWithThenBy = false) { if (gridifyOrdering == null) return query; - mapper = mapper.FixMapper(); return string.IsNullOrWhiteSpace(gridifyOrdering.OrderBy) ? query : ProcessOrdering(query, gridifyOrdering.OrderBy!, startWithThenBy, mapper); @@ -144,16 +188,50 @@ public static IQueryable ApplyOrdering(this IQueryable query, IGridifyO public static IQueryable ApplyOrdering(this IQueryable query, string orderBy, IGridifyMapper? mapper = null, bool startWithThenBy = false) { - mapper = mapper.FixMapper(); return string.IsNullOrWhiteSpace(orderBy) ? query : ProcessOrdering(query, orderBy, startWithThenBy, mapper); } - private static IQueryable ProcessOrdering(IQueryable query, string orderings, bool startWithThenBy, IGridifyMapper 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; + } + + private static IQueryable ProcessOrdering(IQueryable query, string orderings, bool startWithThenBy, IGridifyMapper? mapper) { var isFirst = !startWithThenBy; - foreach (var (member, isAscending) in ParseOrderings(orderings)) + + var orders = ParseOrderings(orderings).ToList(); + + // build the mapper if it is null + if (mapper is null) + { + mapper = new GridifyMapper(); + foreach (var (member, _) in orders) + { + try + { + mapper.AddMap(member); + } + catch (Exception e) + { + if (!mapper.Configuration.IgnoreNotMappedFields) + throw new GridifyMapperException($"Mapping '{member}' not found"); + } + } + } + + foreach (var (member, isAscending) in orders) { if (!mapper.HasMap(member)) { @@ -214,7 +292,7 @@ public static IQueryable ApplyOrdering(this IQueryable query, IGr IGridifyMapper? mapper = null) { if (gridifyOrdering == null) return query; - mapper = mapper.FixMapper(); + if (string.IsNullOrWhiteSpace(gridifyOrdering.OrderBy)) return query; @@ -251,13 +329,13 @@ public static IQueryable ApplyFiltering(this IQueryable query, string? if (string.IsNullOrWhiteSpace(filter)) return query; - mapper = mapper.FixMapper(); - var syntaxTree = SyntaxTree.Parse(filter!); if (syntaxTree.Diagnostics.Any()) throw new GridifyFilteringException(syntaxTree.Diagnostics.Last()!); + mapper = mapper.FixMapper(syntaxTree); + var (queryExpression, _) = ExpressionToQueryConvertor.GenerateQuery(syntaxTree.Root, mapper); query = query.Where(queryExpression); @@ -268,15 +346,14 @@ public static IQueryable ApplyFiltering(this IQueryable query, string? public static IQueryable ApplyFilteringAndOrdering(this IQueryable query, IGridifyQuery? gridifyQuery, IGridifyMapper? mapper = null) { - mapper = mapper.FixMapper(); query = query.ApplyFiltering(gridifyQuery, mapper); - query = query.ApplyOrdering(gridifyQuery); + query = query.ApplyOrdering(gridifyQuery, mapper); return query; } public static Expression> CreateQuery(this SyntaxTree syntaxTree, IGridifyMapper? mapper = null) { - mapper = mapper.FixMapper(); + mapper = mapper.FixMapper(syntaxTree); var exp = ExpressionToQueryConvertor.GenerateQuery(syntaxTree.Root, mapper).Expression; if (exp == null) throw new GridifyQueryException("Invalid SyntaxTree."); return exp; @@ -284,7 +361,6 @@ public static Expression> CreateQuery(this SyntaxTree syntaxTre public static IQueryable ApplyOrderingAndPaging(this IQueryable query, IGridifyQuery? gridifyQuery, IGridifyMapper? mapper = null) { - mapper = mapper.FixMapper(); query = query.ApplyOrdering(gridifyQuery, mapper); query = query.ApplyPaging(gridifyQuery); return query; @@ -304,7 +380,6 @@ public static IQueryable ApplyOrderingAndPaging(this IQueryable query, /// returns a QueryablePaging after applying filtering, ordering and paging public static QueryablePaging GridifyQueryable(this IQueryable query, IGridifyQuery? gridifyQuery, IGridifyMapper? mapper = null) { - mapper = mapper.FixMapper(); query = query.ApplyFiltering(gridifyQuery, mapper); var count = query.Count(); query = query.ApplyOrdering(gridifyQuery, mapper); @@ -326,9 +401,8 @@ public static QueryablePaging GridifyQueryable(this IQueryable query, I /// public static Paging Gridify(this IQueryable query, IGridifyQuery? gridifyQuery, IGridifyMapper? mapper = null) { - mapper = mapper.FixMapper(); - var res = query.GridifyQueryable(gridifyQuery, mapper); - return new Paging(res.Count, res.Query.ToList()); + var (count, queryable) = query.GridifyQueryable(gridifyQuery, mapper); + return new Paging(count, queryable.ToList()); } /// @@ -345,11 +419,10 @@ public static Paging Gridify(this IQueryable query, IGridifyQuery? grid /// public static Paging Gridify(this IQueryable query, Action queryOption, IGridifyMapper? mapper = null) { - mapper = mapper.FixMapper(); var gridifyQuery = new GridifyQuery(); - queryOption?.Invoke(gridifyQuery); - var res = query.GridifyQueryable(gridifyQuery, mapper); - return new Paging(res.Count, res.Query.ToList()); + queryOption.Invoke(gridifyQuery); + var (count, queryable) = query.GridifyQueryable(gridifyQuery, mapper); + return new Paging(count, queryable.ToList()); } #endregion diff --git a/src/Gridify/GridifyMapper.cs b/src/Gridify/GridifyMapper.cs index d351c935..ebc21974 100644 --- a/src/Gridify/GridifyMapper.cs +++ b/src/Gridify/GridifyMapper.cs @@ -29,7 +29,7 @@ public GridifyMapper(GridifyMapperConfiguration configuration, bool autoGenerate GenerateMappings(); } - public GridifyMapper(Action configuration,bool autoGenerateMappings = false) + public GridifyMapper(Action configuration, bool autoGenerateMappings = false) { Configuration = new GridifyMapperConfiguration(); configuration.Invoke(Configuration); @@ -39,6 +39,26 @@ public GridifyMapper(Action configuration,bool autoG 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"); + + 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; + } + public IGridifyMapper GenerateMappings() { foreach (var item in typeof(T).GetProperties()) @@ -124,14 +144,14 @@ public LambdaExpression GetLambdaExpression(string key) return expression!; } - public Expression> GetExpression(string key) + 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."); + return expression as Expression> ?? throw new GridifyMapperException($"Expression fir the `{key}` not found."); } public IEnumerable> GetCurrentMaps() @@ -146,7 +166,7 @@ public IEnumerable> GetCurrentMaps() /// a comma seperated string public override string ToString() => string.Join(",", _mappings.Select(q => q.From)); - private static Expression> CreateExpression(string from) + internal static Expression> CreateExpression(string from) { // x => var parameter = Expression.Parameter(typeof(T)); diff --git a/src/Gridify/IGridifyMapper.cs b/src/Gridify/IGridifyMapper.cs index e17e77f4..8635f1ec 100644 --- a/src/Gridify/IGridifyMapper.cs +++ b/src/Gridify/IGridifyMapper.cs @@ -12,6 +12,7 @@ IGridifyMapper AddMap(string from, Expression> to, Func 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); @@ -22,4 +23,4 @@ IGridifyMapper AddMap(string from, Expression> to, Func public GridifyMapperConfiguration Configuration { get; } IEnumerable> GetCurrentMaps(); } -} \ No newline at end of file +} diff --git a/test/EntityFramework6IntegrationTests/EntityFramework6IntegrationTests.csproj b/test/EntityFramework6IntegrationTests/EntityFramework6IntegrationTests.csproj index 18f50a96..f8b5dde7 100644 --- a/test/EntityFramework6IntegrationTests/EntityFramework6IntegrationTests.csproj +++ b/test/EntityFramework6IntegrationTests/EntityFramework6IntegrationTests.csproj @@ -34,8 +34,8 @@ 4 - - ..\..\packages\Effort.EF6.2.2.14\lib\net45\Effort.dll + + ..\..\packages\Effort.EF6.2.2.15\lib\net45\Effort.dll True diff --git a/test/EntityFramework6IntegrationTests/packages.config b/test/EntityFramework6IntegrationTests/packages.config index 851ec52b..d639c915 100644 --- a/test/EntityFramework6IntegrationTests/packages.config +++ b/test/EntityFramework6IntegrationTests/packages.config @@ -1,6 +1,6 @@  - + diff --git a/test/EntityFrameworkIntegrationTests/EntityFrameworkIntegrationTests.cs.csproj b/test/EntityFrameworkIntegrationTests/EntityFrameworkIntegrationTests.cs.csproj index 257c3ba9..d72bbc58 100644 --- a/test/EntityFrameworkIntegrationTests/EntityFrameworkIntegrationTests.cs.csproj +++ b/test/EntityFrameworkIntegrationTests/EntityFrameworkIntegrationTests.cs.csproj @@ -5,8 +5,8 @@ - - + + diff --git a/test/EntityFrameworkSqlProviderIntegrationTests/EntityFrameworkSqlProviderIntegrationTests.csproj b/test/EntityFrameworkSqlProviderIntegrationTests/EntityFrameworkSqlProviderIntegrationTests.csproj index 5141bc2d..20605103 100644 --- a/test/EntityFrameworkSqlProviderIntegrationTests/EntityFrameworkSqlProviderIntegrationTests.csproj +++ b/test/EntityFrameworkSqlProviderIntegrationTests/EntityFrameworkSqlProviderIntegrationTests.csproj @@ -6,9 +6,9 @@ - - - + + + diff --git a/test/Gridify.Tests/Gridify.Tests.csproj b/test/Gridify.Tests/Gridify.Tests.csproj index 7ecf3375..e69e49cd 100644 --- a/test/Gridify.Tests/Gridify.Tests.csproj +++ b/test/Gridify.Tests/Gridify.Tests.csproj @@ -8,9 +8,12 @@ - + - + + all + runtime; build; native; contentfiles; analyzers; buildtransitive +