How can I compose an Entity Framework query from smaller, resusable queries?

北城以北 提交于 2019-12-03 14:09:07

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.

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