实体框架扩展方法ICollection / IQueryable

时间:2016-10-29 18:42:25

标签: c# .net entity-framework linq entity-framework-core

我需要多次检查相同的特定条件(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是正确的,但我想避免这种不必要的检查。

此致 布莱希特

1 个答案:

答案 0 :(得分:5)

IQueryable<T>查询表达式中使用表达式/自定义方法一直存在问题,需要一些表达式树后处理。例如,LinqKit为此提供了AsExpandableInvokeExpand自定义扩展方法。

虽然不是那么一般,但这里是一个使用第三方软件包的示例用例的解决方案。

首先,在方法中提取谓词的表达式部分。逻辑位置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))
    });