当需要 Expression 但只允许 Func 时,如何在 Include .Where() 中使用内置的 Expression 来应用 Include Filter?
How can I use a built Expression in an Include .Where() to apply an Include Filter when it expects Expression but only allows Func?
我在尝试在嵌套集合上构建表达式树时遇到一些错误。我有 类:
// not DB entity
public FilterTerm
{
public string ComparisonOperatorA { get; set; }
public decimal? ValueA { get; set; }
public string ComparisonOperatorB { get; set; }
public decimal? ValueB { get; set; }
public string ComparisonOperatorC { get; set; }
public decimal? ValueC { get; set; }
}
// not DB entity
public CourseFilter
{
public string Name { get; set; }
public string Type { get; set; }
public List<FilterTerm> FilterTerms { get; set; }
}
// DB entity
public Item
{
public int Id { get; set; }
public string Name { get; set; }
public decimal? SkillLevelA { get; set; }
public decimal? SkillLevelB { get; set; }
public decimal? SkillLevelC { get; set; }
[ForeignKey("Course")]
public int CourseId { get; set; }
public virtual Course Course { get; set; }
}
// DB entity
public Course
{
public int Id { get; set; }
public string Name { get; set; }
public string Type { get; set; }
public virtual ICollection<Item> Items { get; set; }
}
并且我正在尝试查询我的数据库(通过 EF 上下文)以收集包含基于用户提交的 CourseFilter
的项目的所有 Course
s:
[...]
CourseFilter searchParameters = GetSearchParameters();
var coursesQuery = context.GetAll<Course>(); // reutrns IQueryable<Course>
coursesQuery = coursesQuery.ApplyFilters(searchParameters);
var courses = await coursesQuery.ToListAsync();
[...]
private IQueryable<Course> ApplyFilters(IQueryable<Course> query, CourseFilter searchParameters)
{
query = query.Where(c => c.Name.Contains(searchParameters.Name));
query = query.Where(c => c.Type == searchParameters.Type);
Expression<Func<Item, bool>> superPredicate = PredicateBuilder.False<Item>()
foreach (var filterTerm in searchParameters.FilterTerms)
{
Expression<Func<Item, bool>> subPredicate = PredicateBuilder.True<Item>();
subPredicate = subPredicate.And<Item>(GetDynamicPredicate(i => i.SkillLevelA, filterTerm.ValueA, filterTerm.ComparisonOperatorA));
subPredicate = subPredicate.And<Item>(GetDynamicPredicate(i => i.SkillLevelB, filterTerm.ValueB, filterTerm.ComparisonOperatorB));
subPredicate = subPredicate.And<Item>(GetDynamicPredicate(i => i.SkillLevelC, filterTerm.ValueC, filterTerm.ComparisonOperatorC));
superPredicate = superPredicate.Or(subPredicate);
}
query = query.Include(c => c.Items.Where(superPredicate.Compile()))
.Where(c => c.Items.Count > 0);
return query;
}
private Expression<Func<Item, bool>> GetDynamicPredicate<TValue>(
Expression<Func<Item, decimal?>> property, decimal? value, string comparisonOperator)
{
Expression<Func<Item, bool>> predicate = null;
switch (comparisonOperator)
{
case "<":
predicate = Expression.Lambda<Func<Item, bool>>(
Expression.LessThan(property.Body, Expression.Constant(value, typeof(decimal?))),
property.Parameters[0]);
break;
[...]
}
return predicate;
}
我的问题是查询的包含过滤器需要 superPredicate.Compile()
或 pre-compile/compile 时间,Visual Studio 提供错误:
`Argument 2: cannot convert from 'System.Linq.Expressions.Expression<System.Func<DTO.Item, bool>>' to 'System.Func<DTO.Item, bool>'`
但是,在运行时使用 superPredicate.Compile()
时出现错误:
Expression of type 'System.Func`2[DTO.Item,System.Boolean]' cannot be used for parameter of type 'System.Linq.Expressions.Expression`1[System.Func`2[DTO.Item,System.Boolean]]' of method 'System.Linq.IQueryable`1[DTO.Item] Where[DTO.Item](System.Linq.IQueryable`1[DTO.Item], System.Linq.Expressions.Expression`1[System.Func`2[DTO.Item,System.Boolean]])' (Parameter 'arg1')
如果我尝试以这种方式过滤,同样的问题:
query = query
.Include(d => d.Items)
.Where(d => d.Items.Where(superPredicate.Compile()).Count() > 0);
是否可以解决此错误并能够使用包含过滤器?如果没有,有没有一种方法可以在单个查询中完成我想做的事情?
如果有帮助,这是将 SQL 服务器 SP 转换为 EF,其中 SP 本质上是:
@sql = 'SELECT *
FROM dbo.Course c
INNER JOIN dbo.Item i
on i.CourseId = c.Id
WHERE (c.Name LIKE @CourseName)
AND (c.Type = @CourseType)'
[below is added using a cursor over N filters and covers <,<=,>,>=,=
(represented as integers) for SkillLevelA,B,C on Item, and emulated
in the ApplyFilters and GetDynamicQuery methods]
@sql = @sql + 'AND ((' + @CO_A + ' = 1 AND (' + @ValueA' < i.SkillLevelA)) OR ([etc]))'
正如在评论中发现的那样,主要问题是您尝试使用的功能(“过滤后的包含”)是 introduced in EF5,而您 are/were 显然是在较早的版本上版本。
此功能允许您使用 .Where
子句过滤包含的关系。但是,即使在升级之后,您仍然需要确保一直使用表达式树以使 EF 满意。
所以你在这里打电话的这条线 Compile()
对你来说方向错了。
query = query
.Include(c => c.Items.Where(superPredicate.Compile()))
.Where(c => c.Items.Count > 0);
因为 Items
在编译时被视为 IEnumerable<T>
而不是 IQueryable<T>
,您自然会尝试通过将其强制转换为谓词来使 superPredicate
工作使用普通的 non-expression-tree lambda(Compile
将表达式转换为普通的 lambda)。但是,EF 不知道如何处理 lambda——它需要表达式树将其映射到 SQL.
所以你只需要走另一条路,通过调用 .AsQueryable
来使 c.Items
成为可查询的而不是可枚举的:
query.Include(c => c.Items.AsQueryable().Where(superPredicate))
这现在应该可以正常运行,并使用您的 superPredicate
.
定义的过滤后的项目列表填充您的 Items
属性
我在尝试在嵌套集合上构建表达式树时遇到一些错误。我有 类:
// not DB entity
public FilterTerm
{
public string ComparisonOperatorA { get; set; }
public decimal? ValueA { get; set; }
public string ComparisonOperatorB { get; set; }
public decimal? ValueB { get; set; }
public string ComparisonOperatorC { get; set; }
public decimal? ValueC { get; set; }
}
// not DB entity
public CourseFilter
{
public string Name { get; set; }
public string Type { get; set; }
public List<FilterTerm> FilterTerms { get; set; }
}
// DB entity
public Item
{
public int Id { get; set; }
public string Name { get; set; }
public decimal? SkillLevelA { get; set; }
public decimal? SkillLevelB { get; set; }
public decimal? SkillLevelC { get; set; }
[ForeignKey("Course")]
public int CourseId { get; set; }
public virtual Course Course { get; set; }
}
// DB entity
public Course
{
public int Id { get; set; }
public string Name { get; set; }
public string Type { get; set; }
public virtual ICollection<Item> Items { get; set; }
}
并且我正在尝试查询我的数据库(通过 EF 上下文)以收集包含基于用户提交的 CourseFilter
的项目的所有 Course
s:
[...]
CourseFilter searchParameters = GetSearchParameters();
var coursesQuery = context.GetAll<Course>(); // reutrns IQueryable<Course>
coursesQuery = coursesQuery.ApplyFilters(searchParameters);
var courses = await coursesQuery.ToListAsync();
[...]
private IQueryable<Course> ApplyFilters(IQueryable<Course> query, CourseFilter searchParameters)
{
query = query.Where(c => c.Name.Contains(searchParameters.Name));
query = query.Where(c => c.Type == searchParameters.Type);
Expression<Func<Item, bool>> superPredicate = PredicateBuilder.False<Item>()
foreach (var filterTerm in searchParameters.FilterTerms)
{
Expression<Func<Item, bool>> subPredicate = PredicateBuilder.True<Item>();
subPredicate = subPredicate.And<Item>(GetDynamicPredicate(i => i.SkillLevelA, filterTerm.ValueA, filterTerm.ComparisonOperatorA));
subPredicate = subPredicate.And<Item>(GetDynamicPredicate(i => i.SkillLevelB, filterTerm.ValueB, filterTerm.ComparisonOperatorB));
subPredicate = subPredicate.And<Item>(GetDynamicPredicate(i => i.SkillLevelC, filterTerm.ValueC, filterTerm.ComparisonOperatorC));
superPredicate = superPredicate.Or(subPredicate);
}
query = query.Include(c => c.Items.Where(superPredicate.Compile()))
.Where(c => c.Items.Count > 0);
return query;
}
private Expression<Func<Item, bool>> GetDynamicPredicate<TValue>(
Expression<Func<Item, decimal?>> property, decimal? value, string comparisonOperator)
{
Expression<Func<Item, bool>> predicate = null;
switch (comparisonOperator)
{
case "<":
predicate = Expression.Lambda<Func<Item, bool>>(
Expression.LessThan(property.Body, Expression.Constant(value, typeof(decimal?))),
property.Parameters[0]);
break;
[...]
}
return predicate;
}
我的问题是查询的包含过滤器需要 superPredicate.Compile()
或 pre-compile/compile 时间,Visual Studio 提供错误:
`Argument 2: cannot convert from 'System.Linq.Expressions.Expression<System.Func<DTO.Item, bool>>' to 'System.Func<DTO.Item, bool>'`
但是,在运行时使用 superPredicate.Compile()
时出现错误:
Expression of type 'System.Func`2[DTO.Item,System.Boolean]' cannot be used for parameter of type 'System.Linq.Expressions.Expression`1[System.Func`2[DTO.Item,System.Boolean]]' of method 'System.Linq.IQueryable`1[DTO.Item] Where[DTO.Item](System.Linq.IQueryable`1[DTO.Item], System.Linq.Expressions.Expression`1[System.Func`2[DTO.Item,System.Boolean]])' (Parameter 'arg1')
如果我尝试以这种方式过滤,同样的问题:
query = query
.Include(d => d.Items)
.Where(d => d.Items.Where(superPredicate.Compile()).Count() > 0);
是否可以解决此错误并能够使用包含过滤器?如果没有,有没有一种方法可以在单个查询中完成我想做的事情?
如果有帮助,这是将 SQL 服务器 SP 转换为 EF,其中 SP 本质上是:
@sql = 'SELECT *
FROM dbo.Course c
INNER JOIN dbo.Item i
on i.CourseId = c.Id
WHERE (c.Name LIKE @CourseName)
AND (c.Type = @CourseType)'
[below is added using a cursor over N filters and covers <,<=,>,>=,=
(represented as integers) for SkillLevelA,B,C on Item, and emulated
in the ApplyFilters and GetDynamicQuery methods]
@sql = @sql + 'AND ((' + @CO_A + ' = 1 AND (' + @ValueA' < i.SkillLevelA)) OR ([etc]))'
正如在评论中发现的那样,主要问题是您尝试使用的功能(“过滤后的包含”)是 introduced in EF5,而您 are/were 显然是在较早的版本上版本。
此功能允许您使用 .Where
子句过滤包含的关系。但是,即使在升级之后,您仍然需要确保一直使用表达式树以使 EF 满意。
所以你在这里打电话的这条线 Compile()
对你来说方向错了。
query = query
.Include(c => c.Items.Where(superPredicate.Compile()))
.Where(c => c.Items.Count > 0);
因为 Items
在编译时被视为 IEnumerable<T>
而不是 IQueryable<T>
,您自然会尝试通过将其强制转换为谓词来使 superPredicate
工作使用普通的 non-expression-tree lambda(Compile
将表达式转换为普通的 lambda)。但是,EF 不知道如何处理 lambda——它需要表达式树将其映射到 SQL.
所以你只需要走另一条路,通过调用 .AsQueryable
来使 c.Items
成为可查询的而不是可枚举的:
query.Include(c => c.Items.AsQueryable().Where(superPredicate))
这现在应该可以正常运行,并使用您的 superPredicate
.
Items
属性