diff --git a/PoweredSoft.DynamicLinq.Test/ComplexQueriesTests.cs b/PoweredSoft.DynamicLinq.Test/ComplexQueriesTests.cs index 2836393..21ad23c 100644 --- a/PoweredSoft.DynamicLinq.Test/ComplexQueriesTests.cs +++ b/PoweredSoft.DynamicLinq.Test/ComplexQueriesTests.cs @@ -1,7 +1,4 @@ using Microsoft.VisualStudio.TestTools.UnitTesting; -using PoweredSoft.DynamicLinq.Dal.Pocos; -using PoweredSoft.DynamicLinq.Fluent; -using PoweredSoft.DynamicLinq.Helpers; using System; using System.Collections.Generic; using System.Data.SqlClient; @@ -10,6 +7,9 @@ using System.Linq.Expressions; using System.Reflection; using System.Text; using System.Threading.Tasks; +using PoweredSoft.DynamicLinq; +using PoweredSoft.DynamicLinq.Dal.Pocos; +using PoweredSoft.DynamicLinq.Fluent; namespace PoweredSoft.DynamicLinq.Test { @@ -82,7 +82,7 @@ namespace PoweredSoft.DynamicLinq.Test // the query. var query = posts.AsQueryable(); - var queryBuilder = new QueryBuilder(query); + var queryBuilder = new PoweredSoft.DynamicLinq.Fluent.QueryBuilder(query); // add some sorting. queryBuilder diff --git a/PoweredSoft.DynamicLinq.Test/ConstantTests.cs b/PoweredSoft.DynamicLinq.Test/ConstantTests.cs index aaacda9..2ccf98d 100644 --- a/PoweredSoft.DynamicLinq.Test/ConstantTests.cs +++ b/PoweredSoft.DynamicLinq.Test/ConstantTests.cs @@ -35,9 +35,8 @@ namespace PoweredSoft.DynamicLinq.Test Assert.Fail("Should have thrown an exception"); } - catch(Exception ex) + catch { - } Assert.IsTrue(Posts.AsQueryable().Query(t => t.Equal("Id", 1, QueryConvertStrategy.LeaveAsIs)).Any()); diff --git a/PoweredSoft.DynamicLinq.Test/GroupingTests.cs b/PoweredSoft.DynamicLinq.Test/GroupingTests.cs index 0fa6ef4..498d13e 100644 --- a/PoweredSoft.DynamicLinq.Test/GroupingTests.cs +++ b/PoweredSoft.DynamicLinq.Test/GroupingTests.cs @@ -4,6 +4,7 @@ using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; +using PoweredSoft.DynamicLinq; namespace PoweredSoft.DynamicLinq.Test { @@ -12,7 +13,7 @@ namespace PoweredSoft.DynamicLinq.Test { [TestMethod] public void WantedSyntax() - { + {/* var regularSyntax = TestData.Sales .GroupBy(t => t.ClientId); @@ -20,18 +21,18 @@ namespace PoweredSoft.DynamicLinq.Test .AsQueryable() .GroupBy("ClientId"); - /* + var regularSyntax2 = TestData.Sales .GroupBy(t => new { t.ClientId, - t.NetSales - }); + B = t.NetSales + });*/ var dynamicSyntax2 = TestData.Sales .AsQueryable() - .GroupBy("ClientId", "NetSales");*/ + .GroupBy(t => t.Path("ClientId").Path("NetSales", "B")); /* .Select(t => new diff --git a/PoweredSoft.DynamicLinq/Extensions/QueryableExtensions.cs b/PoweredSoft.DynamicLinq/Extensions/QueryableExtensions.cs index 72907e1..8297368 100644 --- a/PoweredSoft.DynamicLinq/Extensions/QueryableExtensions.cs +++ b/PoweredSoft.DynamicLinq/Extensions/QueryableExtensions.cs @@ -69,18 +69,24 @@ namespace PoweredSoft.DynamicLinq var ret = qb.Build(); return ret; } - + public static IQueryable GroupBy(this IQueryable query, string path) - where T : class - { - var ret = query.GroupBy(typeof(T), path); - return ret as IQueryable; - } + => QueryableHelpers.GroupBy(query, typeof(T), path); public static IQueryable GroupBy(this IQueryable query, Type type, string path) + => QueryableHelpers.GroupBy(query, type, path); + + public static IQueryable GroupBy(this IQueryable query, Action callback) + => query.GroupBy(typeof(T), callback); + + public static IQueryable GroupBy(this IQueryable query, Type type, Action callback) { - var ret = QueryableHelpers.GroupBy(query, type, path); - return ret; + var groupBuilder = new GroupBuilder(); + callback(groupBuilder); + if (groupBuilder.Empty) + throw new Exception("No group specified, please specify at least one group"); + + return QueryableHelpers.GroupBy(query, type, groupBuilder.Parts); } } } diff --git a/PoweredSoft.DynamicLinq/Fluent/Group/GroupBuilder.cs b/PoweredSoft.DynamicLinq/Fluent/Group/GroupBuilder.cs new file mode 100644 index 0000000..ebfa1e1 --- /dev/null +++ b/PoweredSoft.DynamicLinq/Fluent/Group/GroupBuilder.cs @@ -0,0 +1,34 @@ +using System; +using System.Collections.Generic; +using System.Text; +using System.Linq; + +namespace PoweredSoft.DynamicLinq.Fluent +{ + public class GroupBuilder + { + public List<(string path, string propertyName)> Parts { get; set; } = new List<(string path, string propertyName)>(); + public bool Empty => !Parts.Any(); + + public GroupBuilder Path(string path, string propertyName = null) + { + if (propertyName == null) + { + var name = path; + if (name.Contains(".")) + { + var parts = name.Split('.'); + name = parts[parts.Length - 1]; // the last one. + } + + if (Parts.Any(t => t.propertyName == name)) + throw new Exception($"{name} is already taken by another group part, you can specify a property name instead to resolve this issue"); + + propertyName = name; + } + + Parts.Add((path, propertyName)); + return this; + } + } +} diff --git a/PoweredSoft.DynamicLinq/Fluent/QueryBuilder.cs b/PoweredSoft.DynamicLinq/Fluent/QueryBuilder/QueryBuilder.cs similarity index 100% rename from PoweredSoft.DynamicLinq/Fluent/QueryBuilder.cs rename to PoweredSoft.DynamicLinq/Fluent/QueryBuilder/QueryBuilder.cs diff --git a/PoweredSoft.DynamicLinq/Fluent/QueryBuilderBase.cs b/PoweredSoft.DynamicLinq/Fluent/QueryBuilder/QueryBuilderBase.cs similarity index 100% rename from PoweredSoft.DynamicLinq/Fluent/QueryBuilderBase.cs rename to PoweredSoft.DynamicLinq/Fluent/QueryBuilder/QueryBuilderBase.cs diff --git a/PoweredSoft.DynamicLinq/Fluent/QueryBuilderBase.shortcuts.cs b/PoweredSoft.DynamicLinq/Fluent/QueryBuilder/QueryBuilderBase.shortcuts.cs similarity index 100% rename from PoweredSoft.DynamicLinq/Fluent/QueryBuilderBase.shortcuts.cs rename to PoweredSoft.DynamicLinq/Fluent/QueryBuilder/QueryBuilderBase.shortcuts.cs diff --git a/PoweredSoft.DynamicLinq/Fluent/QueryBuilderFilter.cs b/PoweredSoft.DynamicLinq/Fluent/QueryBuilder/QueryBuilderFilter.cs similarity index 100% rename from PoweredSoft.DynamicLinq/Fluent/QueryBuilderFilter.cs rename to PoweredSoft.DynamicLinq/Fluent/QueryBuilder/QueryBuilderFilter.cs diff --git a/PoweredSoft.DynamicLinq/Fluent/QueryBuilderSort.cs b/PoweredSoft.DynamicLinq/Fluent/QueryBuilder/QueryBuilderSort.cs similarity index 100% rename from PoweredSoft.DynamicLinq/Fluent/QueryBuilderSort.cs rename to PoweredSoft.DynamicLinq/Fluent/QueryBuilder/QueryBuilderSort.cs diff --git a/PoweredSoft.DynamicLinq/Helpers/QueryableHelpers.cs b/PoweredSoft.DynamicLinq/Helpers/QueryableHelpers.cs index 09ce236..e439333 100644 --- a/PoweredSoft.DynamicLinq/Helpers/QueryableHelpers.cs +++ b/PoweredSoft.DynamicLinq/Helpers/QueryableHelpers.cs @@ -6,7 +6,7 @@ using System.Linq.Expressions; using System.Reflection; using System.Text; using System.Threading.Tasks; -using System.Reflection.Emit; + namespace PoweredSoft.DynamicLinq.Helpers { @@ -77,6 +77,28 @@ namespace PoweredSoft.DynamicLinq.Helpers return ret; } + internal static IQueryable GroupBy(IQueryable query, Type type, List<(string path, string propertyName)> parts) + { + // EXPRESSION + var parameter = Expression.Parameter(type, "t"); + var partExpressions = new List(); + + var fields = new List<(Type type, string propertyName)>(); + + // resolve part expression and create the fields inside the anonymous type. + parts.ForEach(part => + { + var partExpression = ResolvePathForExpression(parameter, part.path); + fields.Add((partExpression.Type, part.propertyName)); + partExpressions.Add(partExpression); + }); + + var anonymousType = TypeHelpers.CreateSimpleAnonymousType(fields); + + + return query; + } + public static IQueryable GroupBy(IQueryable query, Type type, string path) { var parameter = Expression.Parameter(type, "t"); @@ -88,31 +110,6 @@ namespace PoweredSoft.DynamicLinq.Helpers return result; } - private static IQueryable GroupByAnonymousObject(IQueryable query, Type type, ParameterExpression parameter, List fields) - { - throw new NotSupportedException(); - /* - var dynamicAssemblyName = new AssemblyName("PoweredSoft.DynamicLinq.DynamicTypes"); - var dynamicAssembly = AssemblyBuilder.DefineDynamicAssembly(dynamicAssemblyName, System.Reflection.Emit.AssemblyBuilderAccess.Run); - System.Reflection.Emit.ModuleBuilder dynamicModule = dynamicAssembly.DefineDynamicModule("TempAssembly"); - - TypeBuilder dynamicAnonymousType = dynamicModule.DefineType("AnonymousType", TypeAttributes.Public); - - int i = 0; - fields.ForEach(field => - { - var fieldName = $"A_{++i}"; // tODO - dynamicAnonymousType.DefineField(fieldName. - }); - - dynamicAnonymousType.DefineField(, typeof(TFieldA), FieldAttributes.Public); - dynamicAnonymousType.DefineField(fieldNameB, typeof(TFieldB), FieldAttributes.Public); - - return dynamicAnonymousType.CreateType(); - - throw new NotImplementedException(); */ - } - /// /// Returns the right expression for a path supplied. /// diff --git a/PoweredSoft.DynamicLinq/Helpers/TypeHelpers.cs b/PoweredSoft.DynamicLinq/Helpers/TypeHelpers.cs index f6df8d6..f2a5a18 100644 --- a/PoweredSoft.DynamicLinq/Helpers/TypeHelpers.cs +++ b/PoweredSoft.DynamicLinq/Helpers/TypeHelpers.cs @@ -3,11 +3,17 @@ using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; +using System.Reflection.Emit; +using System.Reflection; namespace PoweredSoft.DynamicLinq.Helpers { public static class TypeHelpers { + internal static Lazy DynamicAssemblyName = new Lazy(() => new AssemblyName("PoweredSoft.DynamicLinq.DynamicTypes")); + internal static Lazy DynamicAssembly = new Lazy(() => AssemblyBuilder.DefineDynamicAssembly(DynamicAssemblyName.Value, AssemblyBuilderAccess.Run)); + internal static Lazy DynamicModule = new Lazy(() => DynamicAssembly.Value.DefineDynamicModule("PoweredSoft.DynamicLinq.DynamicTypes")); + public static object ConvertFrom(Type type, object source) { object ret = null; @@ -28,5 +34,45 @@ namespace PoweredSoft.DynamicLinq.Helpers ret = Convert.ChangeType(source, notNullableType); return ret; } + + internal static TypeInfo CreateSimpleAnonymousType(List<(Type type, string name)> fields) + { + // DYNAMIC TYPE CREATION + var typeName = $"PSDLProxy_{Guid.NewGuid().ToString("N")}"; + var dynamicType = DynamicModule.Value.DefineType(typeName, TypeAttributes.Class | TypeAttributes.Public); + fields.ForEach(field => + { + CreatePropertyOnType(dynamicType, field.name, field.type); + }); + var ret = dynamicType.CreateTypeInfo(); + return ret; + } + + internal static void CreatePropertyOnType(TypeBuilder typeBuilder, string propertyName, Type propertyType) + { + // Generate a property called "Name" + var field = typeBuilder.DefineField("_" + propertyName, propertyType, FieldAttributes.Private); + var attributes = MethodAttributes.Public | MethodAttributes.SpecialName | MethodAttributes.HideBySig; + + // generate property + var propertyBuilder = typeBuilder.DefineProperty(propertyName, PropertyAttributes.HasDefault, propertyType, null); + + // Generate getter method + var getter = typeBuilder.DefineMethod("get_" + propertyName, attributes, propertyType, Type.EmptyTypes); + var il = getter.GetILGenerator(); + il.Emit(OpCodes.Ldarg_0); // Push "this" on the stack + il.Emit(OpCodes.Ldfld, field); // Load the field "_Name" + il.Emit(OpCodes.Ret); // Return + propertyBuilder.SetGetMethod(getter); + + // Generate setter method + var setter = typeBuilder.DefineMethod("set_" + propertyName, attributes, null, new[] { propertyType }); + il = setter.GetILGenerator(); + il.Emit(OpCodes.Ldarg_0); // Push "this" on the stack + il.Emit(OpCodes.Ldarg_1); // Push "value" on the stack + il.Emit(OpCodes.Stfld, field); // Set the field "_Name" to "value" + il.Emit(OpCodes.Ret); // Return + propertyBuilder.SetSetMethod(setter); + } } } diff --git a/PoweredSoft.DynamicLinq/PoweredSoft.DynamicLinq.csproj b/PoweredSoft.DynamicLinq/PoweredSoft.DynamicLinq.csproj index 3e04c57..4fe019d 100644 --- a/PoweredSoft.DynamicLinq/PoweredSoft.DynamicLinq.csproj +++ b/PoweredSoft.DynamicLinq/PoweredSoft.DynamicLinq.csproj @@ -21,4 +21,8 @@ + + + +