Entity-Framework使用表达式构建全局和可重用的过滤器/查询规则

时间:2016-07-15 14:22:01

标签: c# entity-framework linq expression dry

给出以下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 */   
            })
    });

从我的主表开始,我希望将所有sub1与所有与sub1相关的sub2一起选中。 该查询按预期工作,生成一个将命中数据库的单个查询。

我的问题是关于内部Where - 部分,因此此过滤器将用于应用程序中的其他几个部分。所以我想在一个地方定义这个“可见规则”(DRY原则)。

在期待Func<SubTable2, bool>的地方,我写了以下属性

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

并将我的查询更改为

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

这引发了一个异常,说明Internal .NET Framework Data Provider error 1025.。 我已尝试使用相同的错误更改为.Where(VisibleOnlyExpression.Compile())

我知道这是因为EntityFramework正在尝试将其转换为SQL,而不是Where

我的问题是:如何在代码中的单个地点(DRY)定义我的“过滤规则”,但仍然可以在Select - ,IQueryable - 中使用,... -clauses可用于ICollection以及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) }); 用于内部(子)查询?

我希望能够写出类似的内容:

IsAwesome

IQueryable<MainTable> - 规则首先在ICollection<MainTable>上调用,以便仅获取真棒主条目,然后在子选择中的nptr上仅获取与之相关的真棒主条目特定的SubTable2条目。但是规则 - 将MainTable条目定义为真棒 - 将无论我在何处调用/过滤它都将是相同的。

我想解决方案需要使用表达式树以及它们如何被操作,因此它们可以翻译成纯SQL但我没有得到正确的想法或指向开始。

3 个答案:

答案 0 :(得分:1)

您可以使用LinqKit AsExpandableInvoke这样的扩展方法,与您要求的内容接近:

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

我说得很近,因为首先你需要将所有需要的表达式都拉到变量中,否则你将获得着名的EF&#34;方法不被支持&#34;例外。其次,调用并不像你的愿望那么简洁。但至少它允许你重用逻辑。

答案 1 :(得分:0)

AFAIK你想要做的事应该是完全可能的:

// 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 */   
        })

答案 2 :(得分:0)

我会准备如下的扩展方法:

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

然后你可以这样使用它:

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