如何将此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);
}
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
可以是静态的、公共的,等等。它不一定是本地的