实体框架扩展方法ICollection/IQueryable

Era*_*cht 1 .net c# linq entity-framework 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()
Run Code Online (Sandbox Code Playgroud)

'&&'之后的部分将导致用户无法检索受限制的项目.

我想把这个检查抽象成一个函数.将来这些条件可能会改变,我不想替换所有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));
}
Run Code Online (Sandbox Code Playgroud)

现在我可以将LINQ查询更改为:

return _ctx.Projects.Where(p => p.CompanyId == companyId)
                    .IsVisibleForResearcher(userId).ToList()
Run Code Online (Sandbox Code Playgroud)

这会生成相同的SQL查询.现在我的问题开始时我想在另一个有项目的DbSet上使用这个扩展方法.

想象一下,公司有项目.我只想检索用户至少可以看到一个项目的公司.

return _ctx.Companies
       .Where(c => c.Projects.Where(p => 
            p.Type == Enums.ProjectType.Open || 
            p.Invites.Any(i => i.InviteeId == userId))
       .Any())
Run Code Online (Sandbox Code Playgroud)

在这里我也想使用扩展方法.

return _ctx.Companies
       .Where(c => c.Projects.AsQueryable().IsVisibleForCompanyAccount(userId).Any())
Run Code Online (Sandbox Code Playgroud)

这引发以下异常:

Remotion.Linq.dll中出现"System.NotSupportedException"类型的异常,但未在用户代码中处理

附加信息:无法解析表达式'c.Projects.AsQueryable()':目前不支持方法'System.Linq.Queryable.AsQueryable'的重载.

比我创建了以下扩展方法:

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));
}
Run Code Online (Sandbox Code Playgroud)

但这也不起作用.

有没有人有想法?或朝着正确的方向迈出一步.

顺便说一句,我在.NET Core上使用Entity Framework Core

更新:

使用a Expression<Func<>>导致了同样的异常:

目前不支持'System.Linq.Queryable.AsQueryable'.

更新2

Thx @ ivan-stoev提供解决方案.我还有一个问题.我还想检索"可见"项目的数量.

我通过这样做修复它:

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))     
                });
Run Code Online (Sandbox Code Playgroud)

但我找不到一种方法来c.Projects代替使用ctx.Projects.Where(p => p.CompanyId == c.Id)

生成的SQL是正确的,但我想避免这种不必要的检查.

真诚的,布莱希特

Iva*_*oev 5

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

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

首先,在方法中提取谓词的表达式部分.合理的地方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);
    }
}
Run Code Online (Sandbox Code Playgroud)

然后,创建一个这样的自定义扩展方法:

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));
    }
}
Run Code Online (Sandbox Code Playgroud)

使用此设计,不需要您当前的扩展方法,因为对于Projects查询,您可以使用:

var projects = _ctx.Projects
    .Where(p => p.CompanyId == companyId)
    .Where(Project.IsVisibleForResearcher(userId));
Run Code Online (Sandbox Code Playgroud)

并为Companies:

var companies = _ctx.Companies
    .WhereAny(c => c.Projects, Project.IsVisibleForResearcher(userId)); 
Run Code Online (Sandbox Code Playgroud)

更新:此解决方案非常有限,因此如果您有不同的用例(特别是在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))
    });
Run Code Online (Sandbox Code Playgroud)