如何将此LINQ转换为其等效表达式树并使其性能良好

本文关键字:表达式 性能 LINQ 转换 | 更新日期: 2023-09-27 18:05:26

更新:你可以跳过这个问题的第二部分,因为George已经帮助回答了第一部分。

第一部分:我正在尝试转换下面的LINQ

childItems.Where(x => x.ParentID == parentItem.ID).ToList().ForEach(y => parentItem.Children.Add(y));

到它的等效表达式,但是我不能让步骤6产生结果。我也不认为我在使用表达。常数在步骤2 - 6作为适当的,但它似乎是有效的,至少现在。

任何帮助在修复这将是非常感激!


    public class Parent
    {
        public Guid ID { get; set; }
        public string Name { get; set; }
        public ICollection<Child> Children { get; set; }
    }
    public class Child
    {
        public Guid ParentID { get; set; }
        public Guid ID { get; set; }
        public string Name { get; set; }
    }
    public void SO_Question()
    {
        Parent parentItem       = new Parent() { ID = Guid.NewGuid(), Name = "Parent" };
        parentItem.Children     = new List<Child>();
        List<Child> childItems  = new List<Child>() {   new Child() { ParentID = parentItem.ID, ID = Guid.NewGuid(), Name = "Child 1" }, 
                                                        new Child() { ParentID = parentItem.ID, ID = Guid.NewGuid(), Name = "Child 2" },
                                                        new Child() { ParentID = Guid.NewGuid(),ID = Guid.NewGuid(), Name = "Child 3" } 
                                                    };

        // Linq query that I am trying to write using Expressions
        childItems.Where(x => x.ParentID == parentItem.ID).ToList().ForEach(y => parentItem.Children.Add(y));
        System.Diagnostics.Debug.WriteLine("Children count from LINQ: " + parentItem.Children.Count);   
        parentItem.Children.Clear();
        Type parentEntityType   = parentItem.GetType();
        Type childEntityCollType= childItems.GetType();
        Type childEntityType    = childEntityCollType.GetGenericArguments()[0];

        //1. (x => x.ParentID == parentItem.ID)
        ParameterExpression predParam   = Expression.Parameter(childEntityType, "x");
        Expression left                 = Expression.Property(predParam, childEntityType.GetProperty("ParentID"));
        Expression right                = Expression.Property(Expression.Constant(parentItem), "ID");
        Expression equality             = Expression.Equal(left, right);
        LambdaExpression le             = Expression.Lambda(equality, new ParameterExpression[] { predParam });

        //2. childItems.Where(x => x.ParentID == parentItem.ID)
        Expression targetConstant       = Expression.Constant(childItems, childEntityCollType);
        Expression whereBody            = Expression.Call(typeof(Enumerable), "Where", new Type[] { childEntityType }, targetConstant, le);
        Func<IEnumerable> whereLambda   = Expression.Lambda<Func<IEnumerable>>(whereBody).Compile();
        object whereResult              = whereLambda.Invoke();

        //3. childItems.Where(x => x.ParentID == parentItem.ID).ToList()
        Expression toListConstant   = Expression.Constant(whereResult, whereResult.GetType());
        Expression toListBody       = Expression.Call(typeof(Enumerable), "ToList", new Type[] { childEntityType }, toListConstant);
        Func<IEnumerable> listLambda= Expression.Lambda<Func<IEnumerable>>(toListBody).Compile();
        object toListResult         = listLambda.Invoke();

        //5. (y => parentItem.Children.Add(y))
        ParameterExpression feParam = Expression.Parameter(childEntityType, "y");
        Expression addConst         = Expression.Constant(parentItem, parentEntityType);
        Expression childAccessor    = Expression.Property(addConst, parentEntityType.GetProperty("Children"));
        Expression body             = Expression.Call(childAccessor, "Add", null, feParam);
        LambdaExpression exp2       = Expression.Lambda(body, new ParameterExpression[] { feParam });

        //6. childItems.Where(x => x.ParentID == parentItem.ID).ToList().ForEach(y => parentItem.Children.Add(y));
        Expression targetConst2 = Expression.Constant(toListResult, toListResult.GetType());
        Expression whereBody2   = Expression.Call(targetConst2, toListResult.GetType().GetMethod("ForEach"), exp2);
        Delegate whereLambda2   = Expression.Lambda(whereBody2, feParam).Compile();
        whereLambda.Invoke();
        System.Diagnostics.Debug.WriteLine("Children count from Expressions: " + parentItem.Children.Count);    
    }

第二部分:在@George修复了我的问题后,我遇到了一个性能问题。我需要在一个循环中运行这两个lambdas数千次,目前它非常慢,可能是因为每次都生成表达式树。我怎么才能避开这个问题呢?我对代码进行了重构,并对最初的问题进行了一些简化。
在下面的代码中,parentiitem是在闭包中捕获的。我怎样才能重写这个,这样我就可以使用从1,2和3的表达式,只提供parentItem(在2和3)作为变量的lambda在每次运行?


public class Parent
{
    public Guid ID { get; set; }
    public string Name { get; set; }
    public ICollection<Child> Children { get; set; }
    public SingleChild SingleChild { get; set; }
}
public class Child
{
    public Guid ParentID { get; set; }
    public Guid ID { get; set; }
    public string Name { get; set; }
}
public class SingleChild
{
    public Guid ParentID { get; set; }
    public Guid ID { get; set; }
    public string Name { get; set; }
}
public static void SO_Question2()
{
    Parent newParentItem1 = new Parent() { ID = Guid.NewGuid(), Name = "Parent1" };
    Parent newParentItem2 = new Parent() { ID = Guid.NewGuid(), Name = "Parent2" };
    Parent newParentItem3 = new Parent() { ID = Guid.NewGuid(), Name = "Parent3" };
    newParentItem1.Children = new List<Child>();
    newParentItem2.Children = new List<Child>();
    newParentItem3.Children = new List<Child>();
    List<Child> childItems = new List<Child>() {   
        new Child() { ParentID = newParentItem1.ID, ID = Guid.NewGuid(), Name = "Child 1" }, 
        new Child() { ParentID = newParentItem1.ID, ID = Guid.NewGuid(), Name = "Child 2" },
        new Child() { ParentID = newParentItem2.ID, ID = Guid.NewGuid(), Name = "Child 3" },
        new Child() { ParentID = newParentItem2.ID, ID = Guid.NewGuid(), Name = "Child 4" },
        new Child() { ParentID = newParentItem2.ID, ID = Guid.NewGuid(), Name = "Child 5" },
        new Child() { ParentID = newParentItem2.ID, ID = Guid.NewGuid(), Name = "Child 6" },
        new Child() { ParentID = newParentItem2.ID, ID = Guid.NewGuid(), Name = "Child 7" } 
    };
    List<Parent> parentCollection = new List<Parent>() { newParentItem1, newParentItem2, newParentItem3 }; // In reality this can be a collection of over 2000 items

    // Linq query that I am trying to write using Expressions
    foreach (Parent parentItem in parentCollection)
    {
        parentItem.Children.Clear();
        childItems.Where(x => x.ParentID == parentItem.ID).ToList().ForEach(y => parentItem.Children.Add(y));
    }
    System.Diagnostics.Debug.WriteLine("Children count from LINQ: " + newParentItem1.Children.Count);
    newParentItem1.Children.Clear();
    newParentItem2.Children.Clear();
    newParentItem3.Children.Clear();

    Type parentEntityType = parentCollection.GetType().GetGenericArguments()[0];
    Type childEntityCollType = childItems.GetType();
    Type childEntityType = childEntityCollType.GetGenericArguments()[0];
    Parent parentVariable = parentCollection.First();
    // 1. parentItem.Children.Clear()
    var childCollection = Expression.Property(Expression.Constant(parentVariable), "Children");
    Expression clearBody = Expression.Call(childCollection, typeof(ICollection<Child>).GetMethod("Clear"));
    Expression<System.Action> bodyLambda = Expression.Lambda<System.Action>(clearBody);
    System.Action compiledClear = bodyLambda.Compile();
    // How can I change 1 and 2 so that they are not recreated with every iteration?
    // My problem is that parentItem changes but is captured in the closure
    //2. (x => x.ParentID == parentItem.ID)
    ParameterExpression predParam = Expression.Parameter(childEntityType, "x");
    Expression left = Expression.Property(predParam, childEntityType.GetProperty("ParentID"));
    Expression right = Expression.Property(Expression.Constant(parentVariable), "ID");
    Expression equality = Expression.Equal(left, right);
    Expression<Func<Child, bool>> le = Expression.Lambda<Func<Child, bool>>(equality, new ParameterExpression[] { predParam });
    Func<Child, bool> compileLambda = le.Compile();

    //3. (y => parentItem.Children.Add(y))
    ParameterExpression feParam = Expression.Parameter(childEntityType, "y");
    Expression addConst = Expression.Constant(parentVariable, parentEntityType);
    Expression childAccessor = Expression.Property(addConst, parentEntityType.GetProperty("Children"));
    Expression body = Expression.Call(childAccessor, "Add", null, feParam);
    Expression<Action<Child>> exp2 = Expression.Lambda<Action<Child>>(body, new ParameterExpression[] { feParam });
    Action<Child> compileExp2 = exp2.Compile();
    foreach (Parent parentItem in parentCollection)
    {
        parentVariable = parentItem;
        compiledClear();
        childItems.Where(compileLambda).ToList().ForEach(compileExp2);
    }
    System.Diagnostics.Debug.WriteLine("Children count from Expressions: " + parentCollection.First().Children.Count);
}

如何将此LINQ转换为其等效表达式树并使其性能良好

public static void SO_Question()
    {
        Parent parentItem = new Parent() { ID = Guid.NewGuid(), Name = "Parent" };
        parentItem.Children = new List<Child>();
        List<Child> childItems = new List<Child>() {   
            new Child() { ParentID = parentItem.ID, ID = Guid.NewGuid(), Name = "Child 1" }, 
            new Child() { ParentID = parentItem.ID, ID = Guid.NewGuid(), Name = "Child 2" },
            new Child() { ParentID = Guid.NewGuid(),ID = Guid.NewGuid(), Name = "Child 3" } 
        };

        // Linq query that I am trying to write using Expressions
        childItems.Where(x => x.ParentID == parentItem.ID).ToList().ForEach(y => parentItem.Children.Add(y));
        System.Diagnostics.Debug.WriteLine("Children count from LINQ: " + parentItem.Children.Count);
        parentItem.Children.Clear();
        Type parentEntityType = parentItem.GetType();
        Type childEntityCollType = childItems.GetType();
        Type childEntityType = childEntityCollType.GetGenericArguments()[0];

        //1. (x => x.ParentID == parentItem.ID)
        ParameterExpression predParam = Expression.Parameter(childEntityType, "x");
        Expression left = Expression.Property(predParam, childEntityType.GetProperty("ParentID"));
        Expression right = Expression.Property(Expression.Constant(parentItem), "ID");
        Expression equality = Expression.Equal(left, right);
        LambdaExpression le = Expression.Lambda(equality, new ParameterExpression[] { predParam });

        //2. childItems.Where(x => x.ParentID == parentItem.ID)
        Expression targetConstant = Expression.Constant(childItems, childEntityCollType);
        Expression whereBody = Expression.Call(typeof(Enumerable), "Where", new Type[] { childEntityType }, targetConstant, le);
        Func<IEnumerable> whereLambda = Expression.Lambda<Func<IEnumerable>>(whereBody).Compile();
        object whereResult = whereLambda.Invoke();

        //3. childItems.Where(x => x.ParentID == parentItem.ID).ToList()
        Expression toListConstant = Expression.Constant(whereResult, whereResult.GetType());
        Expression toListBody = Expression.Call(typeof(Enumerable), "ToList", new Type[] { childEntityType }, toListConstant);
        Func<IEnumerable> listLambda = Expression.Lambda<Func<IEnumerable>>(toListBody).Compile();
        object toListResult = listLambda.Invoke();

        //5. (y => parentItem.Children.Add(y))
        ParameterExpression feParam = Expression.Parameter(childEntityType, "y");
        Expression addConst = Expression.Constant(parentItem, parentEntityType);
        Expression childAccessor = Expression.Property(addConst, parentEntityType.GetProperty("Children"));
        Expression body = Expression.Call(childAccessor, "Add", null, feParam);
        LambdaExpression exp2 = Expression.Lambda(body, new ParameterExpression[] { feParam });

        //6. childItems.Where(x => x.ParentID == parentItem.ID).ToList().ForEach(y => parentItem.Children.Add(y));
        Expression targetConst2 = Expression.Constant(toListResult, toListResult.GetType());
        Expression whereBody2 = Expression.Call(targetConst2, toListResult.GetType().GetMethod("ForEach"), exp2);
        Delegate d = Expression.Lambda(whereBody2).Compile();
        d.DynamicInvoke();
        System.Diagnostics.Debug.WriteLine("Children count from Expressions: " + parentItem.Children.Count);
    }

仅限第二题:
你可以让父变量成为lambda的变量。但是,这不会镜像您的LINQ代码。

另一种方法是创建一个虚拟变量,针对虚拟变量构建表达式,然后循环将设置虚拟变量。我注释掉了第一个lambda,因为它没有编译,似乎与这个问题无关:

public static void SO_Question2()
{
    Parent newParentItem = new Parent() { ID = Guid.NewGuid(), Name = "Parent" };
    newParentItem.Children = new List<Child>();
    List<Child> childItems = new List<Child>() {   
        new Child() { ParentID = newParentItem.ID, ID = Guid.NewGuid(), Name = "Child 1" }, 
        new Child() { ParentID = newParentItem.ID, ID = Guid.NewGuid(), Name = "Child 2" },
        new Child() { ParentID = Guid.NewGuid(),ID = Guid.NewGuid(), Name = "Child 3" } 
    };
    List<Parent> parentCollection = new List<Parent>() { newParentItem }; // In reality this can be a collection of over 2000 items
    // Linq query that I am trying to write using Expressions
    foreach (Parent parentItem in parentCollection)
    {
        childItems.Where(x => x.ParentID == parentItem.ID).ToList().ForEach(y => parentItem.Children.Add(y));
    }
    System.Diagnostics.Debug.WriteLine("Children count from LINQ: " + newParentItem.Children.Count);
    newParentItem.Children.Clear();

    Type parentEntityType = parentCollection.GetType().GetGenericArguments()[0];
    Type childEntityCollType = childItems.GetType();
    Type childEntityType = childEntityCollType.GetGenericArguments()[0];
    Parent parentVariable = parentCollection.First();
    // 1. parentItem.Children.Clear()
    //var childCollection = Expression.Property(Expression.Constant(parentVariable), "Children");
    //Expression clearBody = Expression.Call(childCollection, typeof(List<Child>).GetMethod("Clear"));
    //Delegate bodyLambda = Expression.Lambda(clearBody).Compile();
    //bodyLambda.DynamicInvoke();

    // How can I change 2 and 3 so that they are not recreated with every iteration?
    // My problem is that parentItem changes but is captured in the closure
    //2. (x => x.ParentID == parentItem.ID)
    ParameterExpression predParam = Expression.Parameter(childEntityType, "x");
    Expression left = Expression.Property(predParam, childEntityType.GetProperty("ParentID"));
    Expression right = Expression.Property(Expression.Constant(parentVariable), "ID");
    Expression equality = Expression.Equal(left, right);
    Expression<Func<Child, bool>> le = Expression.Lambda<Func<Child, bool>>(equality, new ParameterExpression[] { predParam });
    Func<Child, bool> compileLambda = le.Compile();

    //3. (y => parentItem.Children.Add(y))
    ParameterExpression feParam = Expression.Parameter(childEntityType, "y");
    Expression addConst = Expression.Constant(parentVariable, parentEntityType);
    Expression childAccessor = Expression.Property(addConst, parentEntityType.GetProperty("Children"));
    Expression body = Expression.Call(childAccessor, "Add", null, feParam);
    Expression<Action<Child>> exp2 = Expression.Lambda<Action<Child>>(body, new ParameterExpression[] { feParam });
    Action<Child> compileExp2 = exp2.Compile();
    foreach (Parent parentItem in parentCollection)
    {
        parentVariable = parentItem;
        childItems.Where(compileLambda).ToList().ForEach(compileExp2);
    }
    System.Diagnostics.Debug.WriteLine("Children count from Expressions: " + parentCollection.First().Children.Count);
}

注意编译(开销较大的部分)只运行一次。执行运行多次。这是有效的,因为Expression.Constant(parentVariable)每次都会检查该引用位置中的任何内容。改变了引用,就改变了变量。还要注意,parentVariable可以是静态的、公共的,等等。它不一定是本地的