我正在为我们的Web应用程序中的几个报告重构一些LINQ
个查询,并且我正在尝试将一些重复的查询谓词移动到他们自己的IQueryable
exension方法中,以便我们可以重用它们这些报告和未来的报告。正如您可能推断的那样,我已经为组重构了谓词,但是代码的谓词给了我一些问题。这是我到目前为止报告方法之一的一个例子:
DAL方法:
public List<Entities.QueryView> GetQueryView(Filter filter)
{
using (var context = CreateObjectContext())
{
return (from o in context.QueryViews
where (!filter.FromDate.HasValue || o.RepairDate >= EntityFunctions.TruncateTime(filter.FromDate))
&& (!filter.ToDate.HasValue || o.RepairDate <= EntityFunctions.TruncateTime(filter.ToDate))
select o)
.WithCode(filter)
.InGroup(filter)
.ToList();
}
}
IQueryable
扩展名:
public static IQueryable<T> WithCode<T>(this IQueryable<T> query, Filter filter)
{
List<string> codes = DAL.GetCodesByCategory(filter.CodeCategories);
if (codes.Count > 0)
return query.Where(Predicates.FilterByCode<T>(codes));
return query;
}
谓词:
public static Expression<Func<T, List<string>, bool>> FilterByCode<T>(List<string> codes)
{
// Method info for List<string>.Contains(code).
var methodInfo = typeof(List<string>).GetMethod("Contains", new Type[] { typeof(string) });
// List of codes to call .Contains() against.
var instance = Expression.Variable(typeof(List<string>), "codes");
var param = Expression.Parameter(typeof(T), "j");
var left = Expression.Property(param, "Code");
var expr = Expression.Call(instance, methodInfo, Expression.Property(param, "Code"));
// j => codes.Contains(j.Code)
return Expression.Lambda<Func<T, List<string>, bool>>(expr, new ParameterExpression[] { param, instance });
}
我遇到的问题是Queryable.Where
不接受某种Expression<Func<T, List<string>, bool>
。我能想到动态创建这个谓词的唯一方法是使用两个参数,这是真正让我感到难过的部分。
我不理解的是以下方法有效。我可以传递我想要动态创建的确切lambda表达式,并且它正确地过滤了我的数据。
public List<Entities.QueryView> GetQueryView(Filter filter)
{
// Get the codes here.
List<string> codes = DAL.GetCodesByCategory(filter.CodeCategories);
using (var context = CreateObjectContext())
{
return (from o in context.QueryViews
where (!filter.FromDate.HasValue || o.RepairDate >= EntityFunctions.TruncateTime(filter.FromDate))
&& (!filter.ToDate.HasValue || o.RepairDate <= EntityFunctions.TruncateTime(filter.ToDate))
select o)
.Where(p => codes.Contains(p.Code)) // This works fine.
//.WithCode(filter)
.InGroup(filter)
.ToList();
}
}
Queryable.Where
重载吗?如果是这样,它甚至可行吗? p => codes.Contains(p.Code)
而不使用两个参数?答案 0 :(得分:15)
您可以创建自己的扩展方法,将其命名为Where
,接受IQueryable<T>
,返回IQueryable<T>
,然后使其模拟LINQ方法的形式。它不会是一个LINQ方法,但它看起来像一个。我不鼓励你写这样的方法只是因为它可能会混淆他人;即使您想要创建一个新的扩展方法,也可以使用LINQ中未使用的名称来避免混淆。简而言之,做你现在正在做的事情,创建新的扩展而不实际命名它们Where
。如果你真的想给一个Where
命名,虽然没有什么可以阻止你。
当然,只需使用lambda:
public static Expression<Func<T, bool>> FilterByCode<T>(List<string> codes)
where T : ICoded //some interface with a `Code` field
{
return p => codes.Contains(p.Code);
}
如果你真的不能让你的实体实现一个接口(提示:你几乎可以肯定),那么代码看起来与你拥有的代码相同,但是使用你作为常量而不是新传递的列表参数:
public static Expression<Func<T, bool>> FilterByCode<T>(List<string> codes)
{
var methodInfo = typeof(List<string>).GetMethod("Contains",
new Type[] { typeof(string) });
var list = Expression.Constant(codes);
var param = Expression.Parameter(typeof(T), "j");
var value = Expression.Property(param, "Code");
var body = Expression.Call(list, methodInfo, value);
// j => codes.Contains(j.Code)
return Expression.Lambda<Func<T, bool>>(body, param);
}
我强烈鼓励使用前一种方法;这种方法失去了静态类型的安全性,并且更加复杂,因此难以维护。
另一个注意事项,您在代码中的评论:// j => codes.Contains(j.Code)
并不准确。 lambda 实际的外观如下:(j, codes) => codes.Contains(j.Code);
实际上明显不同。
见#2的前半部分。