我编写了许多方法(.WhereOr、.WhereAnd),它们基本上允许我“堆叠”一堆 lambda 查询,然后将它们应用到集合中。例如,数据集的用法有点像这样(尽管它通过使用泛型适用于任何类):
WITH LINQ TO DATASETS (Using the .NET DataSetExtensions)
DataTable Result;
List<Expression<Func<DataRow, bool>> Queries = new List<Expression<Func<DataRow, bool>>();
Queries.Add(dr=> dr.Field<string>("field1") == "somestring");
Queries.Add(dr=> dr.Field<string>("field2") == "somestring");
Queries.Add(dr=> dr.Field<string>("field3") == "somestring");
Result = GetSomeTable().AsEnumarable().WhereOr(Queries).CopyToDataTable();
现在说一下上面的示例集合中只有一行与“somestring”匹配,并且位于字段“field2”上。
这意味着 Result 的计数应该为 1。
现在,假设我将上面的代码稍微重写为:
DataTable Result;
List<Expression<Func<DataRow, bool>> Queries = new List<Expression<Func<DataRow, bool>>();
List<string> columns = new string[]{"field1","field2","field3"}.ToList();
string col;
foreach(string c in columns){
col = c;
Queries.Add(dr=> dr.Field<string>(col) == "somestring");
}
Result = GetSomeTable().AsEnumarable().WhereOr(Queries).CopyToDataTable();
现在,我不太理解表达式,但对我来说,上面的两个示例都在做完全相同的事情。
除了第一个示例中的“Result”的计数为 1,第二个示例中的“Result”的计数为 0 之外。
此外,在第二个示例的列表列中,如果将“field2”放在最后,而不是第二,那么“结果”的计数确实正确为 1。
所以,从这一切我得出了一种结论,但我真的不明白发生了什么,也不明白如何解决它..?我可以提前“评估”这些表达式……或者其中的一部分吗?
结论:
基本上,如果我将文字值(例如“field1”)发送到那里,它就会起作用。但是,如果我发送变量,例如“col”,它就不起作用,因为这些“表达式”只会在代码中稍后才被评估。
这也可以解释为什么当我将“field2”移动到最后一个位置时它会起作用。它之所以有效,是因为变量“col”最后被分配给“field2”,因此当表达式评估“col”等于“field2”时。
好吧,那么,有什么办法可以解决这个问题吗?
这是我的WhereOr 方法的代码(它是IEnumerable 的扩展方法):
public static IQueryable<T> WhereOr<T>(this IEnumerable<T> Source, List<Expression<Func<T, bool>>> Predicates) {
Expression<Func<T, bool>> FinalQuery;
FinalQuery = e => false;
foreach (Expression<Func<T, bool>> Predicate in Predicates) {
FinalQuery = FinalQuery.Or(Predicate);
}
return Source.AsQueryable<T>().Where(FinalQuery);
}
public static Expression<Func<T, bool>> Or<T>(this Expression<Func<T, bool>> Source, Expression<Func<T, bool>> Predicate) {
InvocationExpression invokedExpression = Expression.Invoke(Predicate, Source.Parameters.Cast<Expression>());
return Expression.Lambda<Func<T, bool>>(Expression.Or(Source.Body, invokedExpression), Source.Parameters);
}
I've written a number of methods (.WhereOr, .WhereAnd) which basically allow me to "stack up" a bunch of lambda queries, and then apply them to a collection. For example, the usage with datasets would be a little like this (although it works with any class by using generics):
WITH LINQ TO DATASETS (Using the .NET DataSetExtensions)
DataTable Result;
List<Expression<Func<DataRow, bool>> Queries = new List<Expression<Func<DataRow, bool>>();
Queries.Add(dr=> dr.Field<string>("field1") == "somestring");
Queries.Add(dr=> dr.Field<string>("field2") == "somestring");
Queries.Add(dr=> dr.Field<string>("field3") == "somestring");
Result = GetSomeTable().AsEnumarable().WhereOr(Queries).CopyToDataTable();
Now say that in the example above only one row in the collection matches "somestring", and it's on field "field2".
That means that the count for Result should be 1.
Now, say I re-write the code above slightly to this:
DataTable Result;
List<Expression<Func<DataRow, bool>> Queries = new List<Expression<Func<DataRow, bool>>();
List<string> columns = new string[]{"field1","field2","field3"}.ToList();
string col;
foreach(string c in columns){
col = c;
Queries.Add(dr=> dr.Field<string>(col) == "somestring");
}
Result = GetSomeTable().AsEnumarable().WhereOr(Queries).CopyToDataTable();
Now, I don't really understand expressions, but to me both examples above are doing exactly the same thing.
Except that "Result" in the first example has a count of 1, and "Result" in the second example has a count of 0.
Also, in the List columns in the second example, if you put "field2" last, instead of second, then "Result" does correctly have a count of 1.
So, from all this I've come to a kind of conclusion, but I don't really understand what's happening, nor how to fix it..? Can I "evaluate" those expressions earlier...or part of them?
CONCLUSION:
Basically, it seems like, if I send literal values into there, like "field1", it works. But if I send in variables, like "col", it doesn't work, because those "expressions" are only getting evaluated much later in the code.
that would also explain why it works when I move "field2" to the last position. it works because the variable "col" was assigned to "field2" lastly, thus by the time the expressions evaluate "col" equals "field2".
Ok, so, is there any way around this??
Here's the code for my WhereOr method (it's an extension method to IENumerable):
public static IQueryable<T> WhereOr<T>(this IEnumerable<T> Source, List<Expression<Func<T, bool>>> Predicates) {
Expression<Func<T, bool>> FinalQuery;
FinalQuery = e => false;
foreach (Expression<Func<T, bool>> Predicate in Predicates) {
FinalQuery = FinalQuery.Or(Predicate);
}
return Source.AsQueryable<T>().Where(FinalQuery);
}
public static Expression<Func<T, bool>> Or<T>(this Expression<Func<T, bool>> Source, Expression<Func<T, bool>> Predicate) {
InvocationExpression invokedExpression = Expression.Invoke(Predicate, Source.Parameters.Cast<Expression>());
return Expression.Lambda<Func<T, bool>>(Expression.Or(Source.Body, invokedExpression), Source.Parameters);
}
发布评论
评论(4)
“如何解决”的答案。将其更改
为:
享受。布莱恩给出了“是什么和为什么”的答案。
The "how to fix" answer. Change this:
to this:
Enjoy. The "what & why" answer was given by Brian.
我什至不再费心阅读问题正文,我只是阅读标题,然后说
See
http://lorgonblog.spaces.live.com/blog/cns!701679AD17B6D310!689.entry
和
http://blogs.msdn.com/ericlippert/archive/2009/11/12/ opening-循环变量考虑有害.aspx
I don't even bother reading the question bodies any more, I just read the title and then say
See
http://lorgonblog.spaces.live.com/blog/cns!701679AD17B6D310!689.entry
and
http://blogs.msdn.com/ericlippert/archive/2009/11/12/closing-over-the-loop-variable-considered-harmful.aspx
哦天哪,我发表评论后就看到了这个问题。您在循环的每次迭代中都使用相同的变量“col”。当您构建 lambda 表达式时,它不会绑定到变量的值,而是引用变量本身。当您执行查询时,“Col”将设置为它的最后一个值。尝试在循环内创建一个临时字符串,设置其值并使用它。
Oh geez, after I commented I saw the issue. You are using the same variable, "col", in every iteration of the loop. When you build the lambda expression, it doesn't bind to the value of the variable, it references the variable itself. By the time you execute the query, "Col" is set to whatever it's last value is. Try creating a temporary string inside the loop, setting its value, and using that.
我找到了一篇很棒的文章来满足您的要求。
该链接提供了
And
和Or
扩展方法来实现它。http://blogs .msdn.com/b/meek/archive/2008/05/02/linq-to-entities-combining-predicates.aspx
I found great article to fulfill your requirements.
The link provides
And<T>
andOr<T>
extension method to make it.http://blogs.msdn.com/b/meek/archive/2008/05/02/linq-to-entities-combining-predicates.aspx