Как я могу составить запрос Entity Framework из небольших, повторно используемых запросов? - PullRequest
6 голосов
/ 25 апреля 2011

У меня есть несколько (довольно избыточных) запросов в моем приложении, которые выглядят примерно так:

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 неизвестна.

Я пробовал несколько разных методов экспрессионной гимнастики, но безрезультатно.

Что мне нужно сделать, чтобы составлять повторно используемые запросы Entity Framework?

1 Ответ

2 голосов
/ 28 апреля 2011

Я немного поиграл с вашей проблемой, но без окончательного удовлетворительного результата.Я перечислю только несколько моментов, которые смог найти и понять.

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<Func<Country, T>> (T в данном случае int).(Я не уверен, что «тело» - это правильный терминус, но вы понимаете, что я имею в виду: часть справа от лямбда-стрелки =>).Когда весь запрос переводится в дерево выражений, это тело транслируется как вызов метода для функции FindSeverity.(Это можно увидеть в отладчике, когда вы наблюдаете, что свойство Expression query: FindSeverity является непосредственно узлом в дереве выражений, а не телом этого метода.) Это приводит к сбою при выполнении, поскольку LINQ to Entitiesне знает этот метод.В теле такого лямбда-выражения вы можете использовать только известные функции, например, канонические функции из статического System.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;
    }
}

Затем вы можете писать запросы, такие как:

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
}

... подпадает под категорию "Ссылка на нескалярную переменную" внутри запроса, который не поддерживается в LINQ to Entities.(В LINQ to Objects это работает.) Нескалярная переменная в запросе выше - Users.Если таблица Building не пуста, ожидается исключение: "Невозможно создать постоянное значение типа EntityType. В этом контексте поддерживаются только примитивные типы (такие как Int32, String и Guid ')."

Похоже, у вас есть отношение один-ко-многим между User и Building в базе данных, но эта связь не полностью смоделирована в ваших сущностях: 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
}

Это не создаст упомянутое исключение ссылки на нескалярную переменную.Но, возможно, я неправильно понял вашу модель.

...