使用 List<T>.Contains 方法为 LINQ 构建表达式树
Build expression tree for LINQ using List<T>.Contains method
问题
我正在为我们的 Web 应用程序中的多个报告重构一些 LINQ
查询,并且我正在尝试将一些重复的查询谓词移动到它们自己的 IQueryable
扩展方法中,以便我们可以将它们重新用于这些报告和将来的报告。正如您可能推断的那样,我已经重构了组的谓词,但是代码的谓词给我带来了问题。这是我目前拥有的一种报告方法的示例:
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)
?
- 有更简单的方法吗?我觉得我错过了什么。
您可以创建自己的扩展方法,将其命名为 Where
,接受一个 IQueryable<T>
,return 一个 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 actually 看起来像:(j, codes) => codes.Contains(j.Code);
实际上明显不同。
参见#2 的前半部分。
问题
我正在为我们的 Web 应用程序中的多个报告重构一些 LINQ
查询,并且我正在尝试将一些重复的查询谓词移动到它们自己的 IQueryable
扩展方法中,以便我们可以将它们重新用于这些报告和将来的报告。正如您可能推断的那样,我已经重构了组的谓词,但是代码的谓词给我带来了问题。这是我目前拥有的一种报告方法的示例:
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)
? - 有更简单的方法吗?我觉得我错过了什么。
您可以创建自己的扩展方法,将其命名为
Where
,接受一个IQueryable<T>
,return 一个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 actually 看起来像:(j, codes) => codes.Contains(j.Code);
实际上明显不同。参见#2 的前半部分。