Entity Framework拡張メソッド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でこの拡張メソッドを使用したいときに始まります。

企業にプロジェクトがあるとします。そして私は、ユーザーが少なくとも1つのプロジェクトを見ることができる会社を検索したいだけです。

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())

これは以下の例外をスローします:

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

しかしこれもうまくいかなかった。

誰にもアイデアはありますか?または正しい方向への一歩。

Btw私はEntity Frameworkコア.NETコアを使用しています

更新

Expression<Func<>>を使用すると、同じ例外が発生しました。

'System.Linq.Queryable.AsQueryable'は現在サポートされていません。

更新2

ソリューションを提供するためのThx @ ivan-stoev私はまだ1つの問題があります。私はまた、「目に見える」プロジェクトの数を取得したい。

私はこれを行うことでそれを修正した:

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は合法ですか? はい、理由を学ぶ