我需要多次检查相同的特定条件(where
子句):
return _ctx.Projects.Where(p => p.CompanyId == companyId &&
(p.Type == Enums.ProjectType.Open ||
p.Invites.Any(i => i.InviteeId == userId))).ToList()
'&&'之后的部分。将导致用户无法检索受限制的项目。
我想将此检查抽象为函数。将来这些条件可能会发生变化,我不想替换所有LINQ查询。
我使用以下扩展方法执行此操作:
public static IQueryable<Project> IsVisibleForResearcher(this IQueryable<Project> projects, string userId)
{
return projects.Where(p => p.Type == Enums.ProjectType.Open ||
p.Invites.Any(i => i.InviteeId == userId));
}
现在我可以将LINQ查询更改为:
return _ctx.Projects.Where(p => p.CompanyId == companyId)
.IsVisibleForResearcher(userId).ToList()
这会生成相同的SQL查询。现在我的问题开始于我想在另一个有项目的DbSet上使用这个扩展方法。
想象一下,公司有项目。我只想检索用户至少可以看到一个项目的公司。
return _ctx.Companies
.Where(c => c.Projects.Where(p =>
p.Type == Enums.ProjectType.Open ||
p.Invites.Any(i => i.InviteeId == userId))
.Any())
这里我也想使用扩展方法。
return _ctx.Companies
.Where(c => c.Projects.AsQueryable().IsVisibleForCompanyAccount(userId).Any())
这引发以下异常:
类型&#39; System.NotSupportedException&#39;的例外情况发生在 Remotion.Linq.dll但未在用户代码中处理
附加信息:无法解析表达式&c; .Projects.AsQueryable()&#39;:方法的重载&#39; System.Linq.Queryable.AsQueryable&#39;目前不支持。
比我创建了以下扩展方法:
public static IEnumerable<Project> IsVisibleForResearcher(this ICollection<Project> projects, string userId)
{
return projects.Where(p => p.Type == Enums.ProjectType.Open ||
p.Invites.Any(i => i.InviteeId == userId));
}
但这也不起作用。
有没有人有想法? 或者是朝着正确方向迈出的一步。
顺便说一下,我在.NET Core上使用Entity Framework Core
更新:
使用Expression<Func<>>
会导致相同的异常:
&#39; System.Linq.Queryable.AsQueryable&#39;目前不支持。
更新2
Thx @ ivan-stoev提供解决方案。 我还有一个问题。我还想检索“可见&#39;项目
我通过这样做来修复它:
var companies = _ctx.Companies
.WhereAny(c => c.Projects, Project.IsProjectVisibleForResearcher(userId))
.Select(c => new CompanyListDto
{
Id = c.Id,
Name = c.Name,
LogoId = c.LogoId,
ProjectCount = _ctx.Projects.Where(p => p.CompanyId == c.Id)
.Count(Project.IsProjectVisibleForResearcher(userId))
});
但我找不到使用c.Projects
代替ctx.Projects.Where(p => p.CompanyId == c.Id)
生成的SQL是正确的,但我想避免这种不必要的检查。
此致 布莱希特
答案 0 :(得分:5)
在IQueryable<T>
查询表达式中使用表达式/自定义方法一直存在问题,需要一些表达式树后处理。例如,LinqKit为此提供了AsExpandable
,Invoke
和Expand
自定义扩展方法。
虽然不是那么一般,但这里是一个使用第三方软件包的示例用例的解决方案。
首先,在方法中提取谓词的表达式部分。逻辑位置IMO是Project
类:
public class Project
{
// ...
public static Expression<Func<Project, bool>> IsVisibleForResearcher(string userId)
{
return p => p.Type == Enums.ProjectType.Open ||
p.Invites.Any(i => i.InviteeId == userId);
}
}
然后,创建一个这样的自定义扩展方法:
public static class QueryableExtensions
{
public static IQueryable<T> WhereAny<T, E>(this IQueryable<T> source, Expression<Func<T, IEnumerable<E>>> elements, Expression<Func<E, bool>> predicate)
{
var body = Expression.Call(
typeof(Enumerable), "Any", new Type[] { typeof(E) },
elements.Body, predicate);
return source.Where(Expression.Lambda<Func<T, bool>>(body, elements.Parameters));
}
}
使用此设计,您无需使用当前的扩展方法,因为对于Projects
查询,您可以使用:
var projects = _ctx.Projects
.Where(p => p.CompanyId == companyId)
.Where(Project.IsVisibleForResearcher(userId));
和Companies
:
var companies = _ctx.Companies
.WhereAny(c => c.Projects, Project.IsVisibleForResearcher(userId));
更新:此解决方案非常有限,因此如果您有不同的用例(特别是在第二次更新中的Select
表达式内),您最好采用第三次更新派对套餐。例如,这是LinqKit解决方案:
// LInqKit requires expressions to be put into variables
var projects = Linq.Expr((Company c) => c.Projects);
var projectFilter = Project.IsVisibleForResearcher(userId);
var companies = db.Companies.AsExpandable()
.Where(c => projects.Invoke(c).Any(p => projectFilter.Invoke(p)))
.Select(c => new CompanyListDto
{
Id = c.Id,
Name = c.Name,
LogoId = c.LogoId,
ProjectCount = projects.Invoke(c).Count(p => projectFilter.Invoke(p))
});