Entity-Framework using expressions to build global and reusable filter/query-rules

白昼怎懂夜的黑 提交于 2019-12-12 03:18:51

问题


Given the following linq-query:

var query1 = dbContext.MainTable.Where(m => m.MainId == _mainId).SelectMany(sub => sub.SubTable1)
    .Select(sub1 => new
    {
        sub1.CategoryName,
        VisibleDivisions = sub1.SubTable2
            .Where(sub2 => sub2.Status == "Visible")
            .Select(sub2 => new 
            { 
                /* select only what needed */   
            })
    });

Starting from my main-table, I want to get all sub1's selected together with all the sub2's related to the sub1. The query works as expected, generating a single query which will hit the database.

My question is regarding the inner Where-part, as of this filter will be used at several other parts in the application. So I would like to have this "visible-rule" defined at a single place (DRY-principle).

As of the Where is expecting an Func<SubTable2, bool> I have written the following property

public static Expression<Func<SubTable2, bool>> VisibleOnlyExpression => sub2 => sub2.Status == "Visible";

and changed my query to

var query1 = dbContext.MainTable.Where(m => m.MainId == _mainId).SelectMany(sub => sub.SubTable1)
    .Select(sub1 => new
    {
        sub1.CategoryName,
        VisibleDivisions = sub1.SubTable2
            .Where(VisibleOnlyExpression.Compile())
            .Select(sub2 => new 
            { 
                /* select only what needed */   
            })
    });

This throws me an exception, stating Internal .NET Framework Data Provider error 1025.. I already tried changing to .Where(VisibleOnlyExpression.Compile()) with the same error.

I know that this is because EntityFramework is trying to transalte this into SQL which it can not.

My question is: How can I have my "filter-rules" defined at a single place (DRY) in code but have the still usable in Where-, Select-, ... -clauses which can be used on IQueryable as well as on ICollection for inner (sub-)queries?

I would love to be able to write something like:

var query = dbContext.MainTable
    .Where(IsAwesome)
    .SelectMany(s => s.SubTable1.Where(IsAlsoAwesome))
    .Select(sub => new 
    { 
        Sub1sub2s = sub.SubTable2.Where(IsVisible),
        Sub2Mains = sub.MainTable.Where(IsAwesome)
    });

whereas the IsAwesome-rule is called first on IQueryable<MainTable> to get only awesome main-entries and later on ICollection<MainTable> in the sub-select to fetch only awesome main-entries related to a specific SubTable2-entry. But the rule - defining a MainTable-entry as awesome - will be the same, no matter where I call/filter for it.

I guess the solution will need the use of expression-trees and how they can be manipulated, so they will be translatable to plain SQL but I don't get the right idea or point to start with.


回答1:


You can get something close to what are you asking for using the LinqKit AsExpandable and Invoke extension methods like this:

var isAvesome = IsAwesome;
var isAlsoAwesome = IsAlsoAwesome;
var isVisible = IsVisible;

var query = dbContext.MainTable
    .AsExpandable()
    .Where(mt => isAwesome.Invoke(mt))
    .SelectMany(s => s.SubTable1.Where(st1 => isAlsoAwesome.Invoke(st1)))
    .Select(sub => new 
    { 
        Sub1sub2s = sub.SubTable2.Where(st2 => isVisible.Invoke(st2)),
        Sub2Mains = sub.MainTable.Where(mt => isAwesome.Invoke(mt))
    });

I'm saying close because first you need to pull all the expressions needed into variables, otherwise you'll get the famous EF "Method not supported" exception. And second, the invocation is not so concise as in your wish. But at least it allows you to reuse the logic.




回答2:


AFAIK what you are trying to do should be perfectly possible:

// You forgot to access ".Status" in your code.
// Also you don't have to use "=>" to initialize "IsVisible". Use the regular "=".
public static Expression<Func<SubTable2, bool>> IsVisible = sub2 =>
    sub2.Status == "Visible";

...

VisibleDivisions = sub1
    .SubTable2
    // Don't call "Compile()" on your predicate expression. EF will do that.
    .Where(IsVisibleOnly)
    .Select(sub2 => new 
        { 
            /* select only what needed */   
        })



回答3:


I would prepare extension method like below:

public static IQueryable<SubTable2> VisibleOnly(this IQueryable<SubTable2> source)
{
    return source.Where(s => s.Status == "Visible");
}

An then you can use it in that way:

var query = dbContext.Table.VisibleOnly().Select(...)


来源:https://stackoverflow.com/questions/38398507/entity-framework-using-expressions-to-build-global-and-reusable-filter-query-rul

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!