Warning: file_get_contents(/data/phpspider/zhask/data//catemap/2/csharp/300.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
构建关系分组表达式树c#_C#_Lambda_Group By_Expression_Expression Trees - Fatal编程技术网

构建关系分组表达式树c#

构建关系分组表达式树c#,c#,lambda,group-by,expression,expression-trees,C#,Lambda,Group By,Expression,Expression Trees,上下文: 使用Ag Grid,用户应该能够拖放他们想要分组的列 假设我有以下模型和功能分组: List<OrderModel> orders = new List<OrderModel>() { new OrderModel() { OrderId = 184214, Contact = new ContactModel() { ContactId = 1000 }

上下文:

使用Ag Grid,用户应该能够拖放他们想要分组的列

假设我有以下模型和功能分组:

List<OrderModel> orders = new List<OrderModel>()
{
    new OrderModel()
    {
        OrderId = 184214,
        Contact = new ContactModel()
        {
            ContactId = 1000
        }
    }
};

var queryOrders = orders.AsQueryable();
多列

在我想引入一种关系之前,这种方法很有效,因此在我的示例中,
item.Contact.ContactId

我尝试过这样做:

public static IQueryable<TModel> GroupByExpression(List<string> propertyNames, IQueryable<TModel> sequence)
{
    var param = Expression.Parameter(typeof(TModel), "item");
    Expression propertyExp = param;
    var body = Expression.New(typeof(TModel).GetConstructors()[0]);
    var bindings = new List<MemberAssignment>();
    foreach (var property in propertyNames)
    {
        if (property.Contains("."))
        {
            //support nested, relation grouping
            string[] childProperties = property.Split('.');
            var prop = typeof(TModel).GetProperty(childProperties[0], BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.IgnoreCase);
            propertyExp = Expression.MakeMemberAccess(param, prop);
            //loop over the rest of the childs until we have reached the correct property
            for (int i = 1; i < childProperties.Length; i++)
            {
                prop = prop.PropertyType.GetProperty(childProperties[i],
                    BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.IgnoreCase);
                propertyExp = Expression.MakeMemberAccess(propertyExp, prop);

                if (i == childProperties.Length - 1)//last item, this would be the grouping field item
                {
                    var memberAssignment = Expression.Bind(prop, propertyExp);
                    bindings.Add(memberAssignment);
                }
            }
        }
        else
        {
            var fieldValue = typeof(TModel).GetProperty(property, BindingFlags.Public | BindingFlags.Static | BindingFlags.Instance | BindingFlags.IgnoreCase);

            var fieldValueOriginal = Expression.Property(param, fieldValue ?? throw new InvalidOperationException());

            var memberAssignment = Expression.Bind(fieldValue, fieldValueOriginal);
            bindings.Add(memberAssignment);
        }


    }
    var memInitExpress = Expression.MemberInit(body, bindings);
    var result = sequence.Select(Expression.Lambda<Func<TModel, TModel>>(memInitExpress, param));
    return result;
}
公共静态IQueryable GroupByExpression(列表属性名称,IQueryable序列)
{
var param=表达式参数(typeof(TModel),“item”);
表达式propertyExp=param;
var body=Expression.New(typeof(TModel).GetConstructors()[0]);
var bindings=newlist();
foreach(propertyNames中的var属性)
{
if(property.Contains(“.”)
{
//支持嵌套、关系分组
字符串[]childProperties=property.Split('.');
var prop=typeof(TModel).GetProperty(childProperties[0],BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.IgnoreCase);
propertyExp=Expression.MakeMemberAccess(param,prop);
//循环遍历其余的child,直到找到正确的属性
for(int i=1;i
可能看起来很有希望,但不幸的是,它在
var memInitExpress=Expression.MemberInit(body,bindings)处抛出了一个错误

ArgumentException“ContactId”不是“OrderModel”类型的成员

这就是在多个列上分组时表达式的外观:

Expression.MemberInit(body,bindings)
的结果是:
{new OrderModel(){TotalInclPrice=item.TotalInclPrice,OrderId=item.OrderId}}

因此,整个表达式是:
{item=>newordermodel(){TotalInclPrice=item.TotalInclPrice,OrderId=item.OrderId}}

所以现在不难理解为什么我会遇到我提到的异常,因为它使用
OrderModel
选择属性,而ContactId不在该模型中。但是,我受到限制,需要坚持使用
IQueryable
,因此现在的问题是如何使用相同的模型创建表达式以通过
ContactId
分组。我想我真的需要一个这样的表达:

Expression.MemberInit(body,bindings)
的结果必须是:
{new OrderModel(){Contact=new ContactModel(){ContactId=item.Contact.ContactId},OrderId=item.OrderId}}
。像这样的

所以,我想让我们回到基础上来,一步一步地做。最后,for循环创建以下表达式。我是如何解决这一部分的,似乎已经以一种通用的方式解决了这个问题,但我还没有测试那个代码。但是,这还不能进行分组,因此在应用分组后,这些答案可能不再有效


仅供参考:AgGrid只需提供列字段
contact.contactId
,即可找到属性关系。因此,当加载数据时,它只会尝试查找该属性。我认为当创建上述表达式时,它将在网格中工作。我现在也在尝试如何创建sub-
MemberInit
,因为我认为这是成功实现它的解决方案

这个答案有两个部分:

  • 创建一个
    GroupBy
    表达式,并确保使用相同的返回类型
  • GroupBy
    表达式的结果中创建
    Select
    表达式
  • 选择和分组-非通用

    下面是完整的解决方案,但为了让您了解它是如何工作的,请看这段代码,它是用非通用版本编写的。分组的代码几乎是相同的,微小的区别是在开头添加了一个
    键。
    属性

    public static IQueryable<TModel> GroupByExpression(List<string> propertyNames, IQueryable<TModel> sequence)
    {
        var param = Expression.Parameter(typeof(TModel), "item");
        Expression propertyExp = param;
        var body = Expression.New(typeof(TModel).GetConstructors()[0]);
        var bindings = new List<MemberAssignment>();
        var queryOrders = orders.AsQueryable();
        var orderBindings = new List<MemberAssignment>();
    
        //..more code was here, see question
    
        var orderParam = Expression.Parameter(typeof(OrderModel), "item");
        Expression orderPropertyExp = orderParam;
        var orderPropContact = typeof(OrderModel).GetProperty("Contact");
        orderPropertyExp = Expression.MakeMemberAccess(orderPropertyExp, orderPropContact);
        var orderPropContactId = orderPropContact.PropertyType.GetProperty("ContactId");
        orderPropertyExp = Expression.MakeMemberAccess(orderPropertyExp, orderPropContactId);
    
        var contactBody = Expression.New(typeof(ContactModel).GetConstructors()[0]);
        var contactMemerAssignment = Expression.Bind(orderPropContactId, propertyExp);
        orderBindings.Add(contactMemerAssignment);
        var contactMemberInit = Expression.MemberInit(Expression.New(contactBody, orderBindings);
    
        var orderContactMemberAssignment = Expression.Bind(orderPropContact, contactMemberInit);
    
        var orderMemberInit = Expression.MemberInit(Expression.New(typeof(OrderModel).GetConstructors()[0]), new List<MemberAssignment>() {orderContactMemberAssignment});
    
        //during debugging with the same model, I know TModel is OrderModel, so I can cast it
        //of course this is just a quick hack to verify it is working correctly in AgGrid, and it is!
        return (IQueryable<TModel>)queryOrders.Select(Expression.Lambda<Func<OrderModel, OrderModel>>(orderMemberInit, param));
    }
    
    然后,我可以修改的
    Select
    方法:

    GetHashCode()

    不幸的是,直到我开始在使用的每个模型中实现
    GetHasCode()
    Equals()
    之前,这仍然不起作用。在
    Count()
    或通过执行
    .ToList()
    执行查询期间,它将比较所有对象,以确保对象彼此相等(或不相等)。如果它们相等:同一组。但由于我们动态生成了这些模型,因此无法根据内存位置(默认情况下)正确比较这些对象

    幸运的是,您可以非常轻松地生成这两种方法:


    确保表中至少包含您将使用的所有属性(并且可以按进行分组)。

    如果要动态创建嵌套的
    MemberInit
    选择器,请
    public static IQueryable<TModel> GroupByExpression(List<string> propertyNames, IQueryable<TModel> sequence)
    {
        var param = Expression.Parameter(typeof(TModel), "item");
        var body = Expression.New(typeof(TModel).GetConstructors()[0]);
        var bindings = new List<MemberAssignment>();
        foreach (var property in propertyNames)
        {
            var fieldValue = typeof(TModel).GetProperty(property, BindingFlags.Public | BindingFlags.Static | BindingFlags.Instance | BindingFlags.IgnoreCase);
    
            var fieldValueOriginal = Expression.Property(param, fieldValue ?? throw new InvalidOperationException());
    
            var memberAssignment = Expression.Bind(fieldValue, fieldValueOriginal);
            bindings.Add(memberAssignment);
        }
        var result = sequence.Select(Expression.Lambda<Func<TModel, TModel>>(Expression.MemberInit(body, bindings), param));
        return result;
    }
    
    public static IQueryable<TModel> GroupByExpression(List<string> propertyNames, IQueryable<TModel> sequence)
    {
        var param = Expression.Parameter(typeof(TModel), "item");
        Expression propertyExp = param;
        var body = Expression.New(typeof(TModel).GetConstructors()[0]);
        var bindings = new List<MemberAssignment>();
        foreach (var property in propertyNames)
        {
            if (property.Contains("."))
            {
                //support nested, relation grouping
                string[] childProperties = property.Split('.');
                var prop = typeof(TModel).GetProperty(childProperties[0], BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.IgnoreCase);
                propertyExp = Expression.MakeMemberAccess(param, prop);
                //loop over the rest of the childs until we have reached the correct property
                for (int i = 1; i < childProperties.Length; i++)
                {
                    prop = prop.PropertyType.GetProperty(childProperties[i],
                        BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.IgnoreCase);
                    propertyExp = Expression.MakeMemberAccess(propertyExp, prop);
    
                    if (i == childProperties.Length - 1)//last item, this would be the grouping field item
                    {
                        var memberAssignment = Expression.Bind(prop, propertyExp);
                        bindings.Add(memberAssignment);
                    }
                }
            }
            else
            {
                var fieldValue = typeof(TModel).GetProperty(property, BindingFlags.Public | BindingFlags.Static | BindingFlags.Instance | BindingFlags.IgnoreCase);
    
                var fieldValueOriginal = Expression.Property(param, fieldValue ?? throw new InvalidOperationException());
    
                var memberAssignment = Expression.Bind(fieldValue, fieldValueOriginal);
                bindings.Add(memberAssignment);
            }
    
    
        }
        var memInitExpress = Expression.MemberInit(body, bindings);
        var result = sequence.Select(Expression.Lambda<Func<TModel, TModel>>(memInitExpress, param));
        return result;
    }
    
    public static IQueryable<TModel> GroupByExpression(List<string> propertyNames, IQueryable<TModel> sequence)
    {
        var param = Expression.Parameter(typeof(TModel), "item");
        Expression propertyExp = param;
        var body = Expression.New(typeof(TModel).GetConstructors()[0]);
        var bindings = new List<MemberAssignment>();
        var queryOrders = orders.AsQueryable();
        var orderBindings = new List<MemberAssignment>();
    
        //..more code was here, see question
    
        var orderParam = Expression.Parameter(typeof(OrderModel), "item");
        Expression orderPropertyExp = orderParam;
        var orderPropContact = typeof(OrderModel).GetProperty("Contact");
        orderPropertyExp = Expression.MakeMemberAccess(orderPropertyExp, orderPropContact);
        var orderPropContactId = orderPropContact.PropertyType.GetProperty("ContactId");
        orderPropertyExp = Expression.MakeMemberAccess(orderPropertyExp, orderPropContactId);
    
        var contactBody = Expression.New(typeof(ContactModel).GetConstructors()[0]);
        var contactMemerAssignment = Expression.Bind(orderPropContactId, propertyExp);
        orderBindings.Add(contactMemerAssignment);
        var contactMemberInit = Expression.MemberInit(Expression.New(contactBody, orderBindings);
    
        var orderContactMemberAssignment = Expression.Bind(orderPropContact, contactMemberInit);
    
        var orderMemberInit = Expression.MemberInit(Expression.New(typeof(OrderModel).GetConstructors()[0]), new List<MemberAssignment>() {orderContactMemberAssignment});
    
        //during debugging with the same model, I know TModel is OrderModel, so I can cast it
        //of course this is just a quick hack to verify it is working correctly in AgGrid, and it is!
        return (IQueryable<TModel>)queryOrders.Select(Expression.Lambda<Func<OrderModel, OrderModel>>(orderMemberInit, param));
    }
    
        /// <summary>
        /// Recursive get the MemberAssignment
        /// </summary>
        /// <param name="param">The initial paramter expression: var param =  Expression.Parameter(typeof(T), "item");</param>
        /// <param name="baseType">The type of the model that is being used</param>
        /// <param name="propEx">Can be equal to 'param' or when already started with the first property, use:  Expression.MakeMemberAccess(param, prop);</param>
        /// <param name="properties">The child properties, so not all the properties in the object, but the sub-properties of one property.</param>
        /// <param name="index">Index to start at</param>
        /// <returns></returns>
        public static MemberAssignment RecursiveSelectBindings(ParameterExpression param, Type baseType, Expression propEx, string[] properties, int index)
        {
            //Get the first property from the list.
            var prop = baseType.GetProperty(properties[index], BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.IgnoreCase);
            var leftProperty = prop;
            Expression selectPropEx = Expression.MakeMemberAccess(propEx, prop);
            //If this is the last property, then bind it and return that Member assignment
            if (properties.Length - 1 == index)
            {
                var memberAssignment = Expression.Bind(prop, selectPropEx);
                return memberAssignment;
            }
    
            //If we have more sub-properties, make sure the sub-properties are correctly generated.
            //Generate a "new Model() { }"
            NewExpression selectSubBody = Expression.New(leftProperty.PropertyType.GetConstructors()[0]);
            //Get the binding of the next property (recursive)
            var getBinding = RecursiveSelectBindings(param, prop.PropertyType, selectPropEx, properties, index + 1);
    
            MemberInitExpression selectSubMemberInit =
                Expression.MemberInit(selectSubBody, new List<MemberAssignment>() { getBinding });
    
            //Finish the binding by generating "new Model() { Property = item.Property.Property } 
            //During debugging the code, it will become clear what is what.
            MemberAssignment selectSubMemberAssignment = Expression.Bind(leftProperty, selectSubMemberInit);
    
            return selectSubMemberAssignment;
        }
    
        static Expression Select<T>(this IQueryable<T> source, string[] fields)
        {
            var itemType = typeof(T);
            var groupType = itemType; //itemType.Derive();
            var itemParam = Expression.Parameter(itemType, "x");
    
    
            List<MemberAssignment> bindings = new List<MemberAssignment>();
            foreach (var property in fields)
            {
                Expression propertyExp;
                if (property.Contains("."))
                {
                    string[] childProperties = property.Split('.');
                    var binding = RecursiveSelectBindings(itemParam, itemType, itemParam, childProperties, 0);
                    bindings.Add(binding);
                }
                else
                {
                    var fieldValue = groupType.GetProperty(property, BindingFlags.Public | BindingFlags.Static | BindingFlags.Instance | BindingFlags.IgnoreCase);
                    var fieldValueOriginal = Expression.Property(itemParam, fieldValue ?? throw new InvalidOperationException());
    
                    var memberAssignment = Expression.Bind(fieldValue, fieldValueOriginal);
                    bindings.Add(memberAssignment);
                }
            }
    
            var selector = Expression.MemberInit(Expression.New(groupType), bindings.ToArray());
            return Expression.Lambda(selector, itemParam);
        }
    
        static IQueryable<IGrouping<T, T>> GroupEntitiesBy<T>(this IQueryable<T> source, string[] fields)
        {
            var itemType = typeof(T);
            var method = typeof(Queryable).GetMethods()
                         .Where(m => m.Name == "GroupBy")
                         .Single(m => m.GetParameters().Length == 2)
                         .MakeGenericMethod(itemType, itemType);
    
            var result = method.Invoke(null, new object[] { source, source.Select(fields) });
            return (IQueryable<IGrouping<T, T>>)result;
        }
    
        public static IQueryable<TModel> GroupByExpression(List<string> propertyNames, IQueryable<TModel> sequence)
        {
           var grouping = sequence.GroupBy(propertyNames.ToArray());
    
            var selectParam = Expression.Parameter(grouping.ElementType, "item");
            Expression selectPropEx = selectParam;
            var selectBody = Expression.New(typeof(TModel).GetConstructors()[0]);
            var selectBindings = new List<MemberAssignment>();
            foreach (var property in propertyNames)
            {
                var keyProp = "Key." + property;
                //support nested, relation grouping
                string[] childProperties = keyProp.Split('.');
                var prop = grouping.ElementType.GetProperty(childProperties[0], BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.IgnoreCase);
                selectPropEx = Expression.MakeMemberAccess(selectParam, prop);
    
                var binding = PropertyGrouping.RecursiveSelectBindings(selectParam, prop.PropertyType, selectPropEx, childProperties, 1);
                selectBindings.Add(binding);
            }
    
            MemberInitExpression selectMemberInit = Expression.MemberInit(selectBody, selectBindings);
    
            var queryable = grouping
                .Select(Expression.Lambda<Func<IGrouping<TModel, TModel>, TModel>>(selectMemberInit, selectParam));
            return queryable;
    
        }
    
    public static class QueryableExtensions
    {
        public static IQueryable<T> SelectMembers<T>(this IQueryable<T> source, IEnumerable<string> memberPaths)
        {
            var parameter = Expression.Parameter(typeof(T), "item");
            var body = parameter.Select(memberPaths.Select(path => path.Split('.')));
            var selector = Expression.Lambda<Func<T, T>>(body, parameter);
            return source.Select(selector);
        }
    
        static Expression Select(this Expression source, IEnumerable<string[]> memberPaths, int depth = 0)
        {
            var bindings = memberPaths
                .Where(path => depth < path.Length)
                .GroupBy(path => path[depth], (name, items) =>
                {
                    var item = Expression.PropertyOrField(source, name);
                    return Expression.Bind(item.Member, item.Select(items, depth + 1));
                }).ToList();
            if (bindings.Count == 0) return source;
            return Expression.MemberInit(Expression.New(source.Type), bindings);
        }
    }