Entity Framework использует CompiledQuery, но разрешает значения фильтра во время выполнения

#entity-framework-4.1 #linq-expressions #compiled-query

#entity-framework-4.1 #linq-выражения #скомпилированный запрос

Вопрос:

Я пытаюсь реорганизовать громоздкий уровень данных LINQ-to-SQL с помощью entity framework. Схема базы данных, лежащая в основе модели, большая, и типичный запрос может содержать от 20 до 30 включений. EF генерирует массивные инструкции SQL для таких запросов, самым большим на данный момент было 4k строк, но они по-прежнему выполняются своевременно, так что это не проблема.

Проблема в том, что EF занимает много времени, до 4 или 5 секунд, чтобы сгенерировать запрос. Чтобы преодолеть это, я использовал CompileQuery. Тогда проблема в том, что существующий уровень данных L2S содержит множество фильтров, которые могут быть применены к запросу в зависимости от пользовательского ввода. Единственное значение в этих фильтрах должно быть установлено во время выполнения.

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

 public static class DataLayer
{
    static Func<MyEntities, int, IQueryable<Prescription>> compiledQuery;

    static int? FilterHpID;
    static Expression<Func<Prescription, bool>> filter1 = x => (FilterHpID == null || x.Prescriber.HPID == FilterHpID);

    static DateTime? FilterDateTime;
    static Expression<Func<Prescription, bool>> filter2 = x => (FilterDateTime == null || x.DateTimeDispensed > FilterDateTime);

    public static List<Prescription> Get(int patientID, int? hpID, DateTime? dispensed)
    {
        FilterHpID = hpID;
        FilterDateTime = dispensed;

        if (compiledQuery == null)
        {
            compiledQuery = System.Data.Objects.CompiledQuery.Compile((MyEntities entities, int id) =>
                        (from pre in entities.Prescription
                         where pre.PatientID == id
                         select pre)
                         .Where(filter1)
                         .Where(filter2));
        }

        using (MyEntities entities = new MyEntities())
        {
            return compiledQuery(entities, patientID).ToList();
        }
    }
}
  

Могу ли я каким-либо образом включить мои выражения фильтра в скомпилированный запрос и иметь возможность устанавливать значения для выражений фильтра при выполнении запроса?

Ответ №1:

Фильтры должны быть частью скомпилированного запроса, и после этого вы можете установить их при вызове запроса. Я думаю, вы можете использовать что-то вроде:

 public static IQueryable<Prescription> Filter1(this IQueryale<Prescription> query, 
    DateTime? param)
{
    return query.Where(x => (param == null || x.Prescriber.HPID == param));
}
  

Тогда вы должны быть в состоянии определить свой скомпилированный запрос как:

  compiledQuery = System.Data.Objects.CompiledQuery
                       .Compile((MyEntities entities, int id, DateTime? param) =>
                           (from pre in entities.Prescription
                            where pre.PatientID == id
                            select pre)
                           .Filter1(param));
  

Это работает с обычными запросами, но я никогда не пробовал это в скомпилированных запросах. Если это не работает, вы должны поместить выражение фильтра непосредственно в скомпилированный запрос:

  compiledQuery = System.Data.Objects.CompiledQuery
                       .Compile((MyEntities entities, int id, DateTime? param) =>
                           (from pre in entities.Prescription
                            where pre.PatientID == id
                            select pre)
                           .Where(x => (param == null || x.Prescriber.HPID == param));
  

Комментарии:

1. LINQ to Entities это не нравится :(. «…не распознает метод Filter1, и этот метод не может быть преобразован в выражение хранилища.».

2. Это было бы идеальным способом, но, к сожалению, в этом случае фильтры уже определены в отдельном классе, и существует множество из них, которые необходимо повторно использовать в разных запросах. Подход сработал с L2S, но не так хорошо с EF, поскольку теперь запросы должны быть скомпилированы, чтобы сделать производительность приемлемой.

3. Можете ли вы просто проверить, работает ли метод расширения, если вы не компилируете запрос?

4. Да, метод расширения отлично работает, если запрос не скомпилирован.

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

Ответ №2:

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

Цель приведенного ниже кода невозможна ни в какой форме или способом.

 static Expression<Func<Prescription, bool>> filter1 = x => (FilterHpID == null || x.Prescriber.HPID == 1);

compiledQuery = System.Data.Objects.CompiledQuery.Compile((MyEntities entities, int id) =>
                    (from pre in entities.Prescription
                     where pre.PatientID == id
                     select pre)
                     .Where(filter1));
  

Мы собираемся продолжить и рассмотреть использование представлений базы данных как способ обойти необходимость использования скомпилированных запросов в первую очередь. Если запросы L2E не обязательно компилировать, чтобы избежать задержки почти в 2 секунды, то можно добавить фильтры выражений многократного использования.