如何从较小的、可重复使用的查询组成实体框架查询?

发布于 2024-11-03 10:07:59 字数 1526 浏览 0 评论 0 原文

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

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
}

并且:

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
}

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

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

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

按如下方式使用它:

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

这可以编译,但在运行时不起作用。实体框架抱怨 FindSeverity 函数未知。

我尝试过几种不同的表达体操方法,但都没有效果。

我需要做什么来编写可重用的实体框架查询?

I have a few (fairly redundant) queries in my app that are something like this:

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
}

And:

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
}

This is a simplified example, of course. However, the gist is that I need to capture a date and filter a subquery on it. I also need to filter the subquery differently based on the parent object.

I tried (essentially) creating the following function:

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

Using it as follows:

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

This compiles, but does not work at runtime. Entity frameworks complains about the FindSeverity function being unknown.

I've tried a few different methods of Expression gymnastics, but to no avail.

What do I need to do to compose reusable Entity Framework queries?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

七秒鱼° 2024-11-10 10:07:59

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

1)

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

var query = from c in Countries
            select FindSeverity(u => u.Building.Country == c).Max();

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

var query = Countries
            .Select(c => FindSeverity(u => u.Building.Country == c).Max());

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

2)

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

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

然后您可以编写如下查询

var max1 = Users.FindSeverity(u => u.Building.ID == 1).Max();
var max2 = Users.FindSeverity(u => u.Building.Country == "Wonderland").Max();

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

上面的示例只是创建可重用查询片段的通用模式,但它对于您问题中的特定查询并没有真正的帮助。至少我不知道如何重新编写您的 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
}

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

看起来数据库中的 UserBuilding 之间存在一对多关系,但这种关联并未完全建模在您的实体中:User 具有导航属性 Building,但 Building 没有 Users 的集合。在这种情况下,我希望查询中出现 Join ,如下所示:

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
}

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

I've played around a bit with your problem but without a final satisfying result. I will only list the few points I could find and understand.

1)

I rewrite your last code snippet (in simplified form without the projection to an anonymous type)...

var query = from c in Countries
            select FindSeverity(u => u.Building.Country == c).Max();

...and then in extension method syntax:

var query = Countries
            .Select(c => FindSeverity(u => u.Building.Country == c).Max());

Now we see better that FindSeverity(u => u.Building.Country == c).Max() is the body of an Expression<Func<Country, T>> (T is int in this case). (I'm not sure if "body" is the correct terminus technicus, but you know what I mean: the part right from the Lambda arrow =>). When the whole query is translated into an expression tree this body is translated as a method call to the function FindSeverity. (You can see this in the debugger when you watch the Expression property of query: FindSeverity is directly a node in the expression tree, and not the body of this method.) This fails on execution because LINQ to Entities doesn't know this method. In the body of such a lambda expression you can only use known functions, for instance the canonical functions from the static System.Data.Objects.EntityFunctions class.

2)

A possible general way to build reusable parts of a query is to write custom extension methods of IQueryable<T>, for example:

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

Then you can write queries like:

var max1 = Users.FindSeverity(u => u.Building.ID == 1).Max();
var max2 = Users.FindSeverity(u => u.Building.Country == "Wonderland").Max();

As you can see, you are forced to write your queries in extension method syntax. I don't see a way to use such custom query extension methods in query syntax.

The example above is only a general pattern to create reusable query fragments but it doesn't really help for the specific queries in your question. At least I don't know how to reformulate your FindSeverity method so that it fits into this pattern.

3)

I believe that your original queries cannot work in LINQ to Entities. A query like this...

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
}

...falls under the category "Referencing a non-scalar variable" inside of a query which is not supported in LINQ to Entities. (In LINQ to Objects it works.) The non-scalar variable in the query above is Users. If the Building table is not empty an exception is expected: "Unable to create a constant value of type EntityType. Only primitive types ('such as Int32, String, and Guid') are supported in this context."

It looks that you have a one-to-many relationship between User and Building in the database but this association isn't completely modelled in your Entities: User has a navigation property Building but Building doesn't have a collection of Users. In this case I would expect a Join in the query, something like:

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
}

This wouldn't create the mentioned exception of referencing a non-scalar variable. But perhaps I misunderstood your model.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文