實體框架擴展方法ICollection / IQueryable

.net c# entity-framework entity-framework-core linq

我需要多次檢查相同的特定條件( 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())

這引發以下異常:

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));
}

但這也不起作用。

有沒有人有想法?或朝著正確的方向邁出一步。

順便說一句,我在.NET Core上使用Entity Framework Core

更新

使用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))     
                });

但我找不到一種方法來使用c.Projects而不是ctx.Projects.Where(p => p.CompanyId == c.Id)

生成的SQL是正確的,但我想避免這種不必要的檢查。

真誠的,布萊希特

一般承認的答案

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))
    });


Related

許可下: CC-BY-SA with attribution
不隸屬於 Stack Overflow
這個KB合法嗎? 是的,了解原因
許可下: CC-BY-SA with attribution
不隸屬於 Stack Overflow
這個KB合法嗎? 是的,了解原因