diff --git a/.vscode/launch.json b/.vscode/launch.json index 5160a20..af35fd9 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -5,7 +5,7 @@ "version": "0.2.0", "configurations": [ { - "name": ".NET Core Launch (console)", + "name": ".NET Core Launch 1.0(console)", "type": "coreclr", "request": "launch", "preLaunchTask": "build", @@ -16,7 +16,33 @@ // For more information about the 'console' field, see https://github.com/OmniSharp/omnisharp-vscode/blob/master/debugger-launchjson.md#console-terminal-window "console": "internalConsole", "stopAtEntry": false, - "internalConsoleOptions": "openOnSessionStart" + "internalConsoleOptions": "openOnSessionStart", + "logging": { + "engineLogging": false, + "moduleLoad": false, + "exceptions": true, + "browserStdOut": false + } + }, + { + "name": ".NET Core Launch 2.0(console)", + "type": "coreclr", + "request": "launch", + "preLaunchTask": "build", + // If you have changed target frameworks, make sure to update the program path. + "program": "${workspaceFolder}/src/Kendo.DynamicLinqCore.Test/bin/Debug/netcoreapp2.1/Kendo.DynamicLinqCore.Test.dll", + "args": [], + "cwd": "${workspaceFolder}/src/Kendo.DynamicLinqCore.Test", + // For more information about the 'console' field, see https://github.com/OmniSharp/omnisharp-vscode/blob/master/debugger-launchjson.md#console-terminal-window + "console": "internalConsole", + "stopAtEntry": false, + "internalConsoleOptions": "openOnSessionStart", + "logging": { + "engineLogging": false, + "moduleLoad": false, + "exceptions": true, + "browserStdOut": false + } }, { "name": ".NET Core Attach", diff --git a/README.md b/README.md index e1e6221..df1ff61 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ [![Downloads](https://img.shields.io/nuget/dt/Kendo.DynamicLinqCore.svg)](https://www.nuget.org/packages/Kendo.DynamicLinqCore) ## Description -Kendo.DynamicLinqCore implements server paging, filtering, sorting and aggregating to Kendo UI via Dynamic Linq for .Net Core App(1.x ~ 2.x). +Kendo.DynamicLinqCore implements server paging, filtering, sorting, grouping and aggregating to Kendo UI via Dynamic Linq for .Net Core App(1.x ~ 2.x). ## Usage 1. Add the Kendo.DynamicLinqCore NuGet package to your project. @@ -22,7 +22,7 @@ schema: { data: "Data", total: "Total", aggregates: "Aggregates", - groups: "Group", + groups: "Groups", errors: "Errors" } ``` @@ -37,7 +37,7 @@ dataSource: { data: "Data", total: "Total", aggregates: "Aggregates", - groups: "Group", + groups: "Groups", errors: "Errors", ... }, @@ -72,12 +72,12 @@ dataSource: { ..... Other kendo grid code ..... ``` 5. Import the Kendo.DynamicLinqCore namespace. -6. Use the `ToDataSourceResult` extension method to apply paging, sorting and filtering +6. Use the `ToDataSourceResult` extension method to apply paging, sorting, filtering, grouping and aggregating. ```c# using Kendo.DynamicLinqCore [WebMethod] -public static DataSourceResult Products(int take, int skip, IEnumerable sort, Filter filter, IEnumerable aggregates, IEnumerable group) +public static DataSourceResult Products(int take, int skip, IEnumerable sort, Filter filter, IEnumerable aggregates, IEnumerable groups) { using (var northwind = new Northwind()) { @@ -91,7 +91,7 @@ public static DataSourceResult Products(int take, int skip, IEnumerable so UnitsInStock = p.UnitsInStock, Discontinued = p.Discontinued }) - .ToDataSourceResult(take, skip, sort, filter, aggregates, group); + .ToDataSourceResult(take, skip, sort, filter, aggregates, groups); } } ``` @@ -114,7 +114,7 @@ public IActionResult Products([FromBody] DataSourceRequest requestModel) UnitsInStock = p.UnitsInStock, Discontinued = p.Discontinued }) - .ToDataSourceResult(requestModel.Take, requestModel.Skip, requestModel.Sort, requestModel.Filter); + .ToDataSourceResult(requestModel.Take, requestModel.Skip, requestModel.Sort, requestModel.Filter, requestModel.Aggregate, requestModel.Group); } } ``` @@ -143,17 +143,14 @@ public class MyContext : DbContext 4. Run "dotnet pack --configuration Release" ## Note -Kendo.DynamicLinqCore is referred to Kendo.DynamicLinq by [kendo-labs](https://github.com/kendo-labs/dlinq-helpers). Related notes can refer it. +Kendo.DynamicLinqCore is referred to Kendo.DynamicLinq by [Ali Sarkis](https://github.com/mshtawythug/dlinq-helpers). -## Examples -The following examples use Kendo.DynamicLinq(Not Kendo.DynamicLinqCore, but similar) and you can consult. +## Kendo UI Documentation +The following links are Kendo UI online docs(related to this package) and you can refer to. -- [ASP.NET MVC](https://github.com/telerik/kendo-examples-asp-net-mvc/tree/master/grid-crud) -- [ASP.NET Web Forms and Page Methods](https://github.com/telerik/kendo-examples-asp-net/tree/master/grid-page-methods-crud) -- [ASP.NET Web Forms and WCF](https://github.com/telerik/kendo-examples-asp-net/tree/master/grid-wcf-crud) -- [ASP.NET Web Forms and Web Services](https://github.com/telerik/kendo-examples-asp-net/tree/master/grid-web-service-crud) -- [ASP.NET Web Forms and Web API](https://github.com/telerik/kendo-examples-asp-net/tree/master/grid-webapi-crud) +- [Kendo UI Grid](https://docs.telerik.com/kendo-ui/api/javascript/ui/grid) +- [Kendo DataSource](https://docs.telerik.com/kendo-ui/api/javascript/data/datasource) -More Kendo UI Grid configuration can refer to [here](https://demos.telerik.com/kendo-ui/) +More Kendo UI configuration can refer to [here](https://demos.telerik.com/kendo-ui/) diff --git a/src/Kendo.DynamicLinqCore.Test/Kendo.DynamicLinqCore.Test.csproj b/src/Kendo.DynamicLinqCore.Test/Kendo.DynamicLinqCore.Test.csproj index ac1a00b..0f0d4bb 100644 --- a/src/Kendo.DynamicLinqCore.Test/Kendo.DynamicLinqCore.Test.csproj +++ b/src/Kendo.DynamicLinqCore.Test/Kendo.DynamicLinqCore.Test.csproj @@ -2,9 +2,13 @@ Exe - netcoreapp1.0 + netcoreapp1.0;netcoreapp2.1 + + + + diff --git a/src/Kendo.DynamicLinqCore.Test/Program.cs b/src/Kendo.DynamicLinqCore.Test/Program.cs index 68494d9..2605121 100644 --- a/src/Kendo.DynamicLinqCore.Test/Program.cs +++ b/src/Kendo.DynamicLinqCore.Test/Program.cs @@ -12,7 +12,7 @@ class Program Identification = Guid.Parse("F057D609-F1F3-4E5C-BC09-0AC0BBE1007D"), Name = "Monie", Introduce = "I'm Monie", - Salary = 1000000, + Salary = 1000, EmployeeNumber = 10, Birthday = new DateTime(2000,5,5) }, @@ -20,14 +20,14 @@ class Program Identification = Guid.Parse("F586A608-4095-4E8E-8F21-AEFC0DFDB61F"), Name = "CoCo", Introduce = "I'm CoCo", - Salary = 2500000, + Salary = 2500, EmployeeNumber = 77, Birthday = new DateTime(1986,10,10) }, new Person { Identification = Guid.Parse("F4FFE20C-4DE5-4DC5-9686-955FB74EE05E"), Name = "Kirin", - Salary = 3000000, + Salary = 3000, EmployeeNumber = 66, Birthday = new DateTime(1984,7,8) }, @@ -35,7 +35,7 @@ class Program Identification = Guid.Parse("CCAB16DB-070B-4A93-846A-81AEEFDD42EE"), Name = "Rock", Introduce = "", - Salary = 1750000, + Salary = 1750, EmployeeNumber = 35, Birthday = new DateTime(1976,11,6) }, @@ -43,14 +43,22 @@ class Program Identification = null, Name = "Pikachu", Introduce = "Pika~ Pika~", - Salary = 66000, + Salary = 6600, EmployeeNumber = 18, Birthday = new DateTime(2005,3,16) } }; static void Main(string[] args) - { + { + #if NETCOREAPP1_0 || NETCOREAPP1_1 + Console.WriteLine("/---------- Net Core App 1.x ----------/"); + #else + Console.WriteLine("/---------- Net Core App 2.x ----------/"); + #endif + + Console.WriteLine("----------------------------------------"); + /* Test 1 */ var result = people.AsQueryable().ToDataSourceResult(1, 2, null, null, new[] { @@ -58,11 +66,16 @@ static void Main(string[] args) { Aggregate = "sum", Field = "Salary" + }, + new Aggregator + { + Aggregate = "average", + Field = "Salary" } }, null); - Console.WriteLine("/********** Test 1 **********/"); - Console.WriteLine(result.Aggregates); + Console.WriteLine("\r\n/********** Test 1 **********/"); + Console.WriteLine(result.Aggregates); // { Salary = { sum = 14850, average = 2970 } } /* Test 2 */ @@ -79,10 +92,10 @@ static void Main(string[] args) Logic = "and" }, null, null); - Console.WriteLine("/********** Test 2 **********/"); + Console.WriteLine("\r\n/********** Test 2 **********/"); foreach (var p in result.Data) { - Console.WriteLine((p as Person).Name); + Console.WriteLine((p as Person).Name); // Kirin, Rock } @@ -115,10 +128,10 @@ static void Main(string[] args) Logic = "and" }, null, null); - Console.WriteLine("/********** Test 3 **********/"); + Console.WriteLine("\r\n/********** Test 3 **********/"); foreach (var p in result.Data) { - Console.WriteLine((p as Person).Name); + Console.WriteLine((p as Person).Name); // CoCo, Monie } Console.ReadKey(); diff --git a/src/Kendo.DynamicLinqCore/Aggregator.cs b/src/Kendo.DynamicLinqCore/Aggregator.cs index b54e585..55808e1 100644 --- a/src/Kendo.DynamicLinqCore/Aggregator.cs +++ b/src/Kendo.DynamicLinqCore/Aggregator.cs @@ -1,5 +1,4 @@ using System; -using System.Globalization; using System.Linq; using System.Linq.Expressions; using System.Reflection; diff --git a/src/Kendo.DynamicLinqCore/DataSourceRequest.cs b/src/Kendo.DynamicLinqCore/DataSourceRequest.cs index d3211da..db3280e 100644 --- a/src/Kendo.DynamicLinqCore/DataSourceRequest.cs +++ b/src/Kendo.DynamicLinqCore/DataSourceRequest.cs @@ -28,5 +28,10 @@ public class DataSourceRequest /// Specifies the requested grouping . /// public IEnumerable Group { get; set; } + + /// + /// Specifies the requested aggregators. + /// + public IEnumerable Aggregate { get; set; } } } diff --git a/src/Kendo.DynamicLinqCore/DataSourceResult.cs b/src/Kendo.DynamicLinqCore/DataSourceResult.cs index 796b259..2b8863e 100644 --- a/src/Kendo.DynamicLinqCore/DataSourceResult.cs +++ b/src/Kendo.DynamicLinqCore/DataSourceResult.cs @@ -20,17 +20,17 @@ public class DataSourceResult /// /// Represents a single page of processed grouped data. /// - public IEnumerable Group { get; set; } + public IEnumerable Groups { get; set; } /// - /// The total number of records available. + /// Represents a requested aggregates. /// - public int Total { get; set; } + public object Aggregates { get; set; } /// - /// Represents a requested aggregates. + /// The total number of records available. /// - public object Aggregates { get; set; } + public int Total { get; set; } /// /// Represents error information from server-side. diff --git a/src/Kendo.DynamicLinqCore/EnumerableExtensions.cs b/src/Kendo.DynamicLinqCore/EnumerableExtensions.cs index 386c7ab..4166c17 100644 --- a/src/Kendo.DynamicLinqCore/EnumerableExtensions.cs +++ b/src/Kendo.DynamicLinqCore/EnumerableExtensions.cs @@ -2,6 +2,8 @@ using System.Collections.Generic; using System.Linq; using System.Linq.Dynamic.Core; +using System.Linq.Expressions; +using System.Reflection; namespace Kendo.DynamicLinqCore { @@ -14,7 +16,9 @@ public static dynamic GroupByMany(this IEnumerable elements, foreach (var selector in groupSelectors) { //compile the Dynamic Expression Lambda for each one + //var expression = DynamicExpressionParser.ParseLambda(typeof(TElement), typeof(object), selector.Field); var expression = DynamicExpressionParser.ParseLambda(false, typeof(TElement), typeof(object), selector.Field); + //add it to the list selectors.Add(new GroupSelector { @@ -41,7 +45,7 @@ public static dynamic GroupByMany(this IEnumerable elements, g => new GroupResult { Value = g.Key, - Aggregates = selector.Aggregates, + Aggregates = Aggregates(g.AsQueryable(),selector.Aggregates), HasSubgroups = groupSelectors.Length > 1, Count = g.Count(), Items = g.GroupByMany(nextSelectors), //recursivly group the next selectors @@ -52,6 +56,57 @@ public static dynamic GroupByMany(this IEnumerable elements, //if there are not more group selectors return data return elements; } + + private static object Aggregates(IQueryable queryable, IEnumerable aggregates) + { + if (aggregates != null && aggregates.Any()) + { + var objProps = new Dictionary(); + var groups = aggregates.GroupBy(g => g.Field); + Type type = null; + + foreach (var group in groups) + { + var fieldProps = new Dictionary(); + foreach (var aggregate in group) + { + var prop = typeof(T).GetProperty(aggregate.Field); + var param = Expression.Parameter(typeof(T), "s"); + var selector = aggregate.Aggregate == "count" && (Nullable.GetUnderlyingType(prop.PropertyType) != null) + ? Expression.Lambda(Expression.NotEqual(Expression.MakeMemberAccess(param, prop), Expression.Constant(null, prop.PropertyType)), param) + : Expression.Lambda(Expression.MakeMemberAccess(param, prop), param); + var mi = aggregate.MethodInfo(typeof(T)); + if (mi == null) continue; + + var val = queryable.Provider.Execute(Expression.Call(null, mi, aggregate.Aggregate == "count" && (Nullable.GetUnderlyingType(prop.PropertyType) == null) + ? new[] { queryable.Expression } + : new[] { queryable.Expression, Expression.Quote(selector) })); + + fieldProps.Add(new DynamicProperty(aggregate.Aggregate, typeof(object)), val); + } + + type = DynamicClassFactory.CreateType(fieldProps.Keys.ToList()); + var fieldObj = Activator.CreateInstance(type); + foreach (var p in fieldProps.Keys) + { + type.GetProperty(p.Name).SetValue(fieldObj, fieldProps[p], null); + } + objProps.Add(new DynamicProperty(group.Key, fieldObj.GetType()), fieldObj); + } + + type = DynamicClassFactory.CreateType(objProps.Keys.ToList()); + + var obj = Activator.CreateInstance(type); + foreach (var p in objProps.Keys) + { + type.GetProperty(p.Name).SetValue(obj, objProps[p], null); + } + + return obj; + } + + return null; + } public static IEnumerable Append(this IEnumerable source, T item) { diff --git a/src/Kendo.DynamicLinqCore/GroupResult.cs b/src/Kendo.DynamicLinqCore/GroupResult.cs index 0099d7b..aef39d1 100644 --- a/src/Kendo.DynamicLinqCore/GroupResult.cs +++ b/src/Kendo.DynamicLinqCore/GroupResult.cs @@ -1,13 +1,12 @@ -using System.Collections.Generic; -using System.Runtime.Serialization; +using System.Runtime.Serialization; namespace Kendo.DynamicLinqCore { + //The response format of the group schema : https://docs.telerik.com/kendo-ui/api/javascript/data/datasource/configuration/schema#schemagroups [DataContract(Name = "groupresult")] public class GroupResult { - //small letter properties are kendo js properties so please excuse the warnings - //for more info check http://docs.telerik.com/kendo-ui/api/javascript/data/datasource#configuration-schema.groups + //Small letter properties are kendo js properties so please excuse the warnings [DataMember(Name = "value")] public object Value { get; set; } @@ -17,21 +16,17 @@ public class GroupResult public string Field { get { return string.Format("{0} ({1})", this.SelectorField, this.Count); } + //get { return SelectorField; } } public int Count { get; set; } [DataMember(Name = "aggregates")] - public IEnumerable Aggregates { get; set; } + public object Aggregates { get; set; } [DataMember(Name = "items")] public dynamic Items { get; set; } [DataMember(Name = "hasSubgroups")] public bool HasSubgroups { get; set; } // true if there are subgroups - - public override string ToString() - { - return string.Format("{0} ({1})", this.Value, this.Count); - } } } diff --git a/src/Kendo.DynamicLinqCore/Kendo.DynamicLinqCore.csproj b/src/Kendo.DynamicLinqCore/Kendo.DynamicLinqCore.csproj index 47bfac0..3db54cd 100644 --- a/src/Kendo.DynamicLinqCore/Kendo.DynamicLinqCore.csproj +++ b/src/Kendo.DynamicLinqCore/Kendo.DynamicLinqCore.csproj @@ -9,7 +9,7 @@ Kendo.DynamicLinqCore Kendo.DynamicLinqCore CoCo Lin - Kendo.DynamicLinqCore implements server paging, filtering, sorting and aggregating to Kendo UI via Dynamic Linq for .Net Core App(1.x ~ 2.x). + Kendo.DynamicLinqCore implements server paging, filtering, sorting, grouping and aggregating to Kendo UI via Dynamic Linq for .Net Core App(1.x ~ 2.x). Supported platforms: @@ -21,15 +21,16 @@ Supported platforms: -1. Support new filtering operators of "is null", "is not null", "is empty", "is not empty", "has value", and "has no value" in grid. -2. Filtering Operators of "is empty", "is not empty", "has value", and "has no value" doesn't support non-string types. +1. Change the property "Group" of DataSourceResult to "Groups". +2. Add new property "Aggregate" to DataSourceRequest. +3. Fixed getting wrong grouping data in the request using aggregates in grouping configuration. https://github.com/linmasaki/Kendo.DynamicLinqCore https://raw.githubusercontent.com/linmasaki/CoCoPackageIcon/master/cocodotnet64.png netcore netstandard kendo kendo-ui linq dynamic - 2.1.0 - 2.1.0 + 2.2.0 + 2.2.0 Copyright © 2017-2019 CoCo Lin @@ -43,6 +44,4 @@ Supported platforms: - - \ No newline at end of file diff --git a/src/Kendo.DynamicLinqCore/QueryableExtensions.cs b/src/Kendo.DynamicLinqCore/QueryableExtensions.cs index aca94cb..4542d08 100644 --- a/src/Kendo.DynamicLinqCore/QueryableExtensions.cs +++ b/src/Kendo.DynamicLinqCore/QueryableExtensions.cs @@ -59,7 +59,7 @@ public static DataSourceResult ToDataSourceResult(this IQueryable queryabl var total = queryable.Count(); // Calculate the aggregates - var aggregate = Aggregate(queryable, aggregates); + var aggregate = Aggregates(queryable, aggregates); if (group != null && group.Any()) { @@ -94,8 +94,8 @@ public static DataSourceResult ToDataSourceResult(this IQueryable queryabl // Group By if ((group != null) && group.Any()) { - var groupedQuery = queryable.ToList().GroupByMany(group); - result.Group = groupedQuery; + //result.Groups = queryable.ToList().GroupByMany(group); + result.Groups = queryable.GroupByMany(group); } else { @@ -143,7 +143,7 @@ private static IQueryable Filter(IQueryable queryable, Filter filter, L return queryable; } - private static object Aggregate(IQueryable queryable, IEnumerable aggregates) + private static object Aggregates(IQueryable queryable, IEnumerable aggregates) { if (aggregates != null && aggregates.Any()) { diff --git a/src/Kendo.DynamicLinqCore/Sort.cs b/src/Kendo.DynamicLinqCore/Sort.cs index 70c6d09..a6fd6ad 100644 --- a/src/Kendo.DynamicLinqCore/Sort.cs +++ b/src/Kendo.DynamicLinqCore/Sort.cs @@ -20,6 +20,12 @@ public class Sort [DataMember(Name = "dir")] public string Dir { get; set; } + /// + /// Gets or sets the compare expression. + /// + //[DataMember(Name = "compare")] + //public string Compare { get; set; } + /// /// Converts to form required by Dynamic Linq e.g. "Field1 desc" ///