如何从较小的可重复使用的查询中编写实体框架查询?

Joh*_*zen 6 .net c# linq-to-entities entity-framework composition

我的应用程序中有一些(相当多余的)查询,如下所示:

var last30Days = DateTime.Today.AddDays(-30);

from b in Building
let issueSeverity = (from u in Users
                     where u.Building == b
                     from i in u.Issues
                     where i.Date > last30Days
                     select i.Severity).Max()
select new
{
    Building = b,
    IssueSeverity = issueSeverity
}
Run Code Online (Sandbox Code Playgroud)

和:

var last30Days = DateTime.Today.AddDays(-30);

from c in Countries
let issueSeverity = (from u in Users
                     where u.Building.Country == c
                     from i in u.Issues
                     where i.Date > last30Days
                     select i.Severity).Max()
select new
{
    Country = c,
    IssueSeverity = issueSeverity
}
Run Code Online (Sandbox Code Playgroud)

当然,这是一个简化的例子.但是,要点是我需要捕获日期并在其上过滤子查询.我还需要根据父对象以不同方式过滤子查询.

我尝试(基本上)创建以下函数:

public IQueryable<int?> FindSeverity(Expression<Func<User, bool>> predicate)
{
    var last30Days = DateTime.Today.AddDays(-30);

    return from u in Users.Where(predicate)
           from i in u.Issues
           where i.Date > last30Days
           select i.Severity;
}
Run Code Online (Sandbox Code Playgroud)

使用方法如下:

from c in Countries
let issueSeverity = FindSeverity(u => u.Building.Country == c).Max()
select new
{
    Country = c,
    IssueSeverity = issueSeverity
}
Run Code Online (Sandbox Code Playgroud)

这会编译,但在运行时不起作用.实体框架抱怨FindSeverity功能未知.

我尝试了几种不同的表现体操方法,但无济于事.

构建可重用的Entity Framework查询需要做什么?

Sla*_*uma 2

我已经解决了你的问题,但没有最终令人满意的结果。我只列出我能找到并理解的几点。

1)

我重写了你的最后一个代码片段(以简化的形式,没有投影到匿名类型)...

var query = from c in Countries
            select FindSeverity(u => u.Building.Country == c).Max();
Run Code Online (Sandbox Code Playgroud)

...然后在扩展方法语法中:

var query = Countries
            .Select(c => FindSeverity(u => u.Building.Country == c).Max());
Run Code Online (Sandbox Code Playgroud)

现在我们可以更好地看到(在本例中是FindSeverity(u => u.Building.Country == c).Max())的主体。(我不确定“body”是否是正确的技术终点,但你知道我的意思:Lambda 箭头右侧的部分=>)。当整个查询被转换为表达式树时,该主体被转换为对函数的方法调用。(当您观察:的属性直接是表达式树中的一个节点,而不是此方法的主体时,您可以在调试器中看到这一点。)执行时会失败,因为 LINQ to Entities 不知道此方法。在此类 lambda 表达式的主体中,您只能使用已知函数,例如静态类中的规范函数。Expression<Func<Country, T>>TintFindSeverityExpressionqueryFindSeveritySystem.Data.Objects.EntityFunctions

2)

构建查询的可重用部分的一种可能的通用方法是编写 的自定义扩展方法IQueryable<T>,例如:

public static class MyExtensions
{
    public static IQueryable<int?> FindSeverity(this IQueryable<User> query,
                                       Expression<Func<User, bool>> predicate)
    {
        var last30Days = DateTime.Today.AddDays(-30);

        return from u in query.Where(predicate)
               from i in u.Issues
               where i.Date > last30Days
               select i.Severity;
    }
}
Run Code Online (Sandbox Code Playgroud)

然后你可以编写如下查询:

var max1 = Users.FindSeverity(u => u.Building.ID == 1).Max();
var max2 = Users.FindSeverity(u => u.Building.Country == "Wonderland").Max();
Run Code Online (Sandbox Code Playgroud)

正如您所看到的,您被迫使用扩展方法语法编写查询。我没有看到在查询语法中使用此类自定义查询扩展方法的方法。

上面的示例只是创建可重用查询片段的通用模式,但它对于您问题中的特定查询并没有真正的帮助。至少我不知道如何重新制定你的FindSeverity方法以使其适合这种模式。

3)

我相信您的原始查询无法在 LINQ to Entities 中运行。像这样的查询...

from b in Building
let issueSeverity = (from u in Users
                     where u.Building == b
                     from i in u.Issues
                     where i.Date > last30Days
                     select i.Severity).Max()
select new
{
    Building = b,
    IssueSeverity = issueSeverity
}
Run Code Online (Sandbox Code Playgroud)

...属于查询内的“引用非标量变量”类别,LINQ to Entities 不支持该类别。(在 LINQ to Objects 中它可以工作。)上面查询中的非标量变量是Users。如果Building表不为空,则会出现异常:“无法创建 EntityType 类型的常量值。此上下文中仅支持原始类型(‘例如 Int32、String 和 Guid’)。”

看起来您在数据库中User和 之间具有一对多关系,但此关联并未在您的实体中完全建模:具有导航属性,但没有. 在这种情况下,我希望查询中有一个,如下所示:BuildingUserBuildingBuildingUsersJoin

from b in Building
join u in Users
  on u.Building.ID equals b.ID
let issueSeverity = (i in u.Issues
                     where i.Date > last30Days
                     select i.Severity).Max()
select new
{
    Building = b,
    IssueSeverity = issueSeverity
}
Run Code Online (Sandbox Code Playgroud)

这不会创建提到的引用非标量变量的异常。但也许我误解了你的模型。