如何在不同实体上重用相同的where子句?

huangapple go评论147阅读模式
英文:

How to reuse same where-clause on different entities?

问题

以下是您要求的代码部分的中文翻译:

在我的应用程序中,已登录的管理员用户可以根据某些权限查看“合作伙伴”:

public async Task<IList<Partner>> GetPartners(IList<Permission> permissions, Admin admin)
{
    bool all = permissions.Any(x => x.Key == "SeePartners" && x.Value == "All");
    bool sameArea = permissions.Any(x => x.Key == "SeePartners" && x.Value == "SameArea");

    var p = _dbContext.Partners.Where(x => all || (sameArea && x.AreaId == admin.AreaId));

    return await p.AsNoTracking().ToListAsync();
}

每个合作伙伴都与不同的项目相关联。管理员可以看到所有项目,但在访问时仍然只能看到基于权限的合作伙伴,如下所示:

public async Task<IList<ProgramPartner>> GetProgramPartners(IList<Permission> permissions, Admin admin, Guid programId)
{
    bool all = permissions.Any(x => x.Key == "SeePartners" && x.Value == "All");
    bool sameArea = permissions.Any(x => x.Key == "SeePartners" && x.Value == "SameArea");

    var p = _dbContext.ProgramPartners.Where(x => x.ProgramId == programId &&
        (all || (sameArea && x.Partner.AreaId == admin.AreaId))
    );

    return await p.AsNoTracking().ToListAsync();
}

这些只是非常基本的示例。权限处理要复杂得多,因此我想重用处理它的Where子句

我尝试过编写返回true/false的方法,但EF给我一个错误,无法翻译查询:

public bool FilterPartners(Partner partner, IList<Permission> permissions, Admin admin)
{
    bool all = permissions.Any(x => x.Key == "SeePartners" && x.Value == "All");
    bool ownArea = permissions.Any(x => x.Key == "SeePartners" && x.Value == "OwnArea");

    return all || (ownArea && partner.AreaId == admin.AreaId);
}

public async Task<IList<Partner>> GetPartners(IList<Permission> permissions, Admin admin)
{
    var p = _dbContext.Partners.Where(x => FilterPartners(x, permissions, admin));

    return await p.AsNoTracking().ToListAsync();
}

public async Task<IList<ProgramPartner>> GetProgramPartners(IList<Permission> permissions, Admin admin, Guid programId)
{
    var p = _dbContext.ProgramPartners.Where(x => x.ProgramId == programId).Where(x => FilterPartners(x.Partner, permissions, admin));

    return await p.AsNoTracking().ToListAsync();
}

我还尝试将IQueryable传递给方法然后传回,还查看了表达式函数,但根本不起作用。

这是否有可能?

请注意,上述翻译仅包括代码部分,不包括问题部分。如果您需要更多帮助或有其他问题,请随时提出。

英文:

In my app a logged in admin-user can see "partners" based on some permissions:

public async Task&lt;IList&lt;Partner&gt;&gt; GetPartners(IList&lt;Permission&gt; permissions, Admin admin)
{
	bool all = permissions.Any(x =&gt; x.Key == &quot;SeePartners&quot; &amp;&amp; x.Value == &quot;All&quot;);
	bool sameArea = permissions.Any(x =&gt; x.Key == &quot;SeePartners&quot; &amp;&amp; x.Value == &quot;SameArea&quot;);

    var p = _dbContext.Partners.Where(x =&gt; all || (sameArea &amp;&amp; x.AreaId == Admin.AreaId));

    return await p.AsNoTracking().ToListAsync();
}

Each partner is associated to different programs. The admin can see all programs, but when accessing it still only the partners based on the permissions. Like:

public async Task&lt;IList&lt;ProgramPartner&gt;&gt; GetProgramPartners(IList&lt;Permission&gt; permissions, Admin admin, Guid programId)
{
	bool all = permissions.Any(x =&gt; x.Key == &quot;SeePartners&quot; &amp;&amp; x.Value == &quot;All&quot;);
	bool sameArea = permissions.Any(x =&gt; x.Key == &quot;SeePartners&quot; &amp;&amp; x.Value == &quot;SameArea&quot;);

    var p = _dbContext.ProgramPartners.Where(x =&gt; x.ProgramId == programId &amp;&amp; 
        (all || (sameArea &amp;&amp; x.Partner.AreaId == Admin.AreaId))
    );

    return await p.AsNoTracking().ToListAsync()
}

These are very basic examples. The permission handling is much more complicated, so I'd like to reuse the Where-clause that handles it.

I've tried to make a method that returns true/false, but EF gives me an error, that the query couldn't be translated:

public bool FilterPartners(Partner partner, IList&lt;Permission&gt; permissions, Admin admin)
{
	bool all = permissions.Any(x =&gt; x.Key == &quot;SeePartners&quot; &amp;&amp; x.Value == &quot;All&quot;);
	bool ownArea = permissions.Any(x =&gt; x.Key == &quot;SeePartners&quot; &amp;&amp; x.Value == &quot;OwnArea&quot;);

	return all || (ownArea &amp;&amp; partner.AreaId == admin.AreaId);
}

public async Task&lt;IList&lt;Partner&gt;&gt; GetPartners(IList&lt;Permission&gt; permissions, Admin admin)
{
    var p = _dbContext.Partners.Where(x =&gt; FilterPartners(x, permissions, admin));

    return await p.AsNoTracking().ToListAsync();
}

public async Task&lt;IList&lt;ProgramPartner&gt;&gt; GetProgramPartners(IList&lt;Permission&gt; permissions, Admin admin, Guid programId)
{
    var p = _dbContext.Partners.Where(x =&gt; x.ProgramId == programId).Where(x =&gt; FilterPartners(x.Partner, permissions, admin));

    return await p.AsNoTracking().ToListAsync();
}

I've also tried passing an IQueryable to a method and back and also looked at Expressions functions, but that didn't work at all.

Is this possible at all?

答案1

得分: 1

以下是您要翻译的代码部分:

IQueryable<T> FilterPartners<T>(
    IQueryable<T> query,
    Func<T, Partner> getPartner,
    IList<Permission> permissions,
    Admin admin)
{
    return query.Where(item =>
        permissions.Any(x => x.Key == "SeePartners" && x.Value == "All")
        || (permissions.Any(x => x.Key == "SeePartners" && x.Value == "OwnArea") 
            && getPartner(item).AreaId == admin.AreaId));
}

async Task<IList<Partner>> GetPartners(IList<Permission> permissions, Admin admin)
{
    var p = _dbContext.Partners.Where(x => FilterPartners(x, permissions, admin));

    return await p.AsNoTracking().ToListAsync();
}

async Task<IList<ProgramPartner>> GetProgramPartners(
    IList<Permission> permissions, 
    Admin admin, 
    Guid programId)
{
    IQueryable<ProgramPartner> dbQuery = null; //_dbContext.Partners
    dbQuery = dbQuery
        .Where(x => x.ProgramId == programId);

    dbQuery = FilterPartners(dbQuery, x => x.Partner, permissions, admin);

    return await p.AsNoTracking().ToListAsync();
}

请注意,我已将HTML实体编码转换回原始文本,以便更好地理解代码。

英文:

It's long shot, but have you tried something among these lines:

IQueryable&lt;T&gt; FilterPartners&lt;T&gt;(
    IQueryable&lt;T&gt; query,
    Func&lt;T, Partner&gt; getPartner,
    IList&lt;Permission&gt; permissions,
    Admin admin)
{
    return query.Where(item =&gt;
        permissions.Any(x =&gt; x.Key == &quot;SeePartners&quot; &amp;&amp; x.Value == &quot;All&quot;)
        || (permissions.Any(x =&gt; x.Key == &quot;SeePartners&quot; &amp;&amp; x.Value == &quot;OwnArea&quot;) 
            &amp;&amp; getPartner(item).AreaId == admin.AreaId));
}

async Task&lt;IList&lt;Partner&gt;&gt; GetPartners(IList&lt;Permission&gt; permissions, Admin admin)
{
    var p = _dbContext.Partners.Where(x =&gt; FilterPartners(x, permissions, admin));

    return await p.AsNoTracking().ToListAsync();
}

async Task&lt;IList&lt;ProgramPartner&gt;&gt; GetProgramPartners(
    IList&lt;Permission&gt; permissions, 
    Admin admin, 
    Guid programId)
{
    IQueryable&lt;ProgramPartner&gt; dbQuery = null; //_dbContext.Partners
    dbQuery = dbQuery
        .Where(x =&gt; x.ProgramId == programId);

    dbQuery = FilterPartners(dbQuery, x =&gt; x.Partner, permissions, admin);

    return await p.AsNoTracking().ToListAsync();
}

答案2

得分: 0

"IQueryable" 在调用 ToListAsync 之前不会被实例化,对其应用的所有函数必须可转换,因此您不能在其 lambda 表达式中随意调用任何函数。

您需要使过滤函数接收并返回一个 IQueryable。然后该函数重写其中包含的查询并返回一个包含过滤器的新查询。

您可以将此函数作为扩展函数来执行,以便更容易阅读。

public static class DbContextExtensions
{
    (bool all, bool ownArea) CalculatePermissions(IList&lt;Permission&gt; permissions, Admin admin)
    {
        bool all = permissions.Any(x =&gt; x.Key == &quot;SeePartners&quot; &amp;&amp; x.Value == &quot;All&quot;);
        bool ownArea = permissions.Any(x =&gt; x.Key == &quot;SeePartners&quot; &amp;&amp; x.Value == &quot;OwnArea&quot;);
        return (all, ownArea);
    }

    public IQueryable&lt;Partner&gt; FilterPartners(this IQueryable&lt;Partner&gt; source,
        IList&lt;Permission&gt; permissions, Admin admin)
    {
        (var all, var ownArea) = CalculatePermissions(permissions, admin);
        return source.Where(p =&gt; all || (ownArea &amp;&amp; p.AreaId == admin.AreaId));
    }

    public IQueryable&lt;ProgramPartner&gt; FilterProgramPartners(this IQueryable&lt;ProgramPartner&gt; source,
        IList&lt;Permission&gt; permissions, Admin admin, Guid programId)
    {
        (var all, var ownArea) = CalculatePermissions(permissions, admin);
        return source.Where(pp =&gt;
            all ||
            (
               ownArea &amp;&amp;
               pp.Partner.Any(p =&gt; p.AreaId == admin.AreaId))
            );
    }
}
public async Task&lt;IList&lt;Partner&gt;&gt; GetPartners(IList&lt;Permission&gt; permissions, Admin admin)
{
    var p = _dbContext.Partners.FilterPartners(permissions, admin);
    return await p.AsNoTracking().ToListAsync();
}

public async Task&lt;IList&lt;ProgramPartner&gt;&gt; GetProgramPartners(IList&lt;Permission&gt; permissions, Admin admin, Guid programId)
{
    var p = _dbContext.ProgramPartners
        .Where(p =&gt; p.ProgramId == programId)
        .FilterProgramPartners(permissions, admin);

    return await p.AsNoTracking().ToListAsync();
}

请注意,根据您的设置,可能可以将 permissions 作为 IQueryable<Permission> 传递(即,您不将权限列表存储在内存中),然后您的扩展函数应如下所示:

public IQueryable&lt;Partner&gt; FilterPartners(this IQueryable&lt;Partner&gt; partnerSource,
    IQueryable&lt;Permission&gt; permissions, Admin admin)
{
    return partnerSource.Where(p =&gt;
        permissions.Any(x =&gt; x.Key == &quot;SeePartners&quot; &amp;&amp; x.Value == &quot;All&quot;)
        || (
           permissions.Any(x =&gt; x.Key == &quot;SeePartners&quot; &amp;&amp; x.Value == &quot;OwnArea&quot;) &amp;&amp;
           p.AreaId == admin.AreaId
          )
        );
}

换句话说,您不能单独使用 IQueryable,只能作为另一个查询的一部分使用,然后只需执行一次 .ToListAsync 即可将其从数据库中实例化。

英文:

An IQueryable is not materialized until ToListAsync is called, and all functions applied to it must be translatable, so you can't just call any function you like inside its lambdas.

You need the filter function to receive and return an IQueryable. Then the function rewrites the query contained in that and returns a new one with the filter added.

You can do this as an extension function to make it easier to read.

public static class DbContextExtensions
{
    (bool all, bool ownArea) CalculatePermissions(IList&lt;Permission&gt; permissions, Admin admin)
    {
        bool all = permissions.Any(x =&gt; x.Key == &quot;SeePartners&quot; &amp;&amp; x.Value == &quot;All&quot;);
        bool ownArea = permissions.Any(x =&gt; x.Key == &quot;SeePartners&quot; &amp;&amp; x.Value == &quot;OwnArea&quot;);
        return (all, ownArea);
    }

    public IQueryable&lt;Partner&gt; FilterPartners(this IQueryable&lt;Partner&gt; source,
        IList&lt;Permission&gt; permissions, Admin admin)
    {
        (var all, var ownArea) = CalculatePermissions(permissions, admin);
        return source.Where(p =&gt; all || (ownArea &amp;&amp; p.AreaId == admin.AreaId));
    }

    public IQueryable&lt;ProgramPartner&gt; FilterProgramPartners(this IQueryable&lt;ProgramPartner&gt; source,
        IList&lt;Permission&gt; permissions, Admin admin, Guid programId)
    {
        (var all, var ownArea) = CalculatePermissions(permissions, admin);
        return source.Where(pp =&gt;
            all ||
            (
               ownArea &amp;&amp;
               pp.Partner.Any(p =&gt; p.AreaId == admin.AreaId))
            );
    }
}
public async Task&lt;IList&lt;Partner&gt;&gt; GetPartners(IList&lt;Permission&gt; permissions, Admin admin)
{
    var p = _dbContext.Partners.FilterPartners(permissions, admin);
    return await p.AsNoTracking().ToListAsync();
}

public async Task&lt;IList&lt;ProgramPartner&gt;&gt; GetProgramPartners(IList&lt;Permission&gt; permissions, Admin admin, Guid programId)
{
    var p = _dbContext.ProgramPartners
        .Where(p =&gt; p.ProgramId == programId)
        .FilterProgramPartners(permissions, admin);

    return await p.AsNoTracking().ToListAsync();
}

Note that depending on your setup, it may be possible to pass through permissions as IQueryable&lt;Permission&gt; (ie you don't store the permission list in memory), and then your extension function should look like this

public IQueryable&lt;Partner&gt; FilterPartners(this IQueryable&lt;Partner&gt; partnerSource,
    IQueryable&lt;Permission&gt; permissions, Admin admin)
{
    return partnerSource.Where(p =&gt;
        permissions.Any(x =&gt; x.Key == &quot;SeePartners&quot; &amp;&amp; x.Value == &quot;All&quot;)
        || (
           permissions.Any(x =&gt; x.Key == &quot;SeePartners&quot; &amp;&amp; x.Value == &quot;OwnArea&quot;) &amp;&amp;
           p.AreaId == admin.AreaId
          )
        );
}

In other words, you mustn't use an IQueryable by itself, only as part of another query, so that you just do a single .ToListAsync as the end which materializes it from the database.

huangapple
  • 本文由 发表于 2023年6月26日 20:36:46
  • 转载请务必保留本文链接:https://go.coder-hub.com/76556745.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定