Update: You can skip the second part of this question as George already helped answer the first part.
Part I: I am trying to convert the LINQ below
childItems.Where(x => x.ParentID == parentItem.ID).ToList().ForEach(y => parentItem.Children.Add(y));
to its equivalent Expression but I can't get Step 6 to produce a result. I also don't think I am using Expression.Constant in Steps 2 - 6 as appropriate but it seems to be working, for now at least.
Any help in fixing this would be much appreciated!
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);
}
Part II: After @George fixed my issue I have hit a performance problem. I need to run the two lambdas thousands of times within a loop and currently it is very slow, probably because the expression tree is generated every time. How can I get around this?
I've restructured the code and simplified it a bit from my initial question.
In the code below parentItem is captured in the closure. How can i rewrite this so that I can use the expression from 1, 2 and 3 and only supply parentItem (on 2 and 3) as a variable to the lambda on every run?
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);
}
答案 0 :(得分:1)
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);
}
答案 1 :(得分:0)
仅针对问题2:
您可以将父变量设为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
可以是静态的,公共的,等等。它不一定是本地人。