Скомпилированный запрос LinQ с выражениями в функциях - PullRequest
4 голосов
/ 23 сентября 2010

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

ObjectContext.Employees.Where(EmployeePredicates.CustomerPredicate)

EmployeePredicates - это статический класс со свойством CustomerPredicate, которое выглядит следующим образом:

public static Expression<Func<Employee, bool>> CustomerPredicate
    {
        get
        {
            return t => t.CustomerId == 1;
        }
    }

Это работает, как и ожидалось.Однако в большинстве случаев вы бы хотели передать параметр Expression.Чтобы достичь этого, я должен изменить свойство на статическую функцию:

 public static Expression<Func<Employee, bool>> CustomerPredicate(int id)
    {
         return t => t.CustomerId == id;
    }

И я могу использовать это так:

ObjectContext.Employees.Where(EmployeePredicates.CustomerPredicate(id))

Это работает, но теперь приходит сложная часть.Я хотел бы скомпилировать этот запрос ... Visual Studio не выдает никаких ошибок компиляции, но когда я запускаю этот пример, во время выполнения выдается следующее исключение:

Internal .NET Framework Data Provider error 1025

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

var _compiledQuery = CompiledQuery.Compile<AdventureWorksEntities, int, IQueryable<Employee>>(
                    (ctx, id) =>
                            (ctx.Employee.Where(EmployeePredicates.CustomerPredicate(id))
                        ));

Кто-нибудь знает, почему выбрасывается это исключение?Я взял этот способ работы с http://www.albahari.com/nutshell/linqkit.aspx. Любая помощь будет высоко ценится.


Спасибо Джон,

Проблема с подходом, который вы описываете, заключается в том, чтосцепление предикатов вместе станет очень трудным.Что если мне нужно объединить этот предикат с другим предикатом, который фильтрует сотрудников с определенным именем?Затем вы получите множество вариантов выбора для передачи параметров.

    (ctx, id, name) => 
(ctx.Employee.Select(emp => new {emp, id})
.Where(EmployeePredicates.CustomerPredicate(id))
.Select(emp => new {emp, name})
.Where(EmployeePredicates.NamePredicate(name))

Это становится еще хуже, когда вы работаете над объединенными таблицами.

 (ctx, id, name) => 
    (ctx.Employee
     .Join(ctx.Contact, e=> e.ContactId, c => c.Id), (emp, cont) => new Container<Employee, Customer> {Employee = emp, Contact = cont})
    .Where(EmployeePredicates.CustomerPredicate(id))
    .Where(EmployeePredicates.NamePredicate(name))
    .Select(t => new EmployeeDTO {Name = t.cont.Name, Customer = e.emp.Customer})

Потому что каждый Где () работает с чем-то типа T и возвращает что-то типа T, WherePredicates в приведенном выше коде должны работать с типом Container.Это делает очень трудным повторное использование Предикатов.И повторное использование было первоначальной целью этого подхода ...

1 Ответ

2 голосов
/ 23 сентября 2010

Проблема в том, что Entity Framework пытается исследовать дерево выражений, представленное

 (ctx, id) => (ctx.Employee.Where(EmployeePredicates.CustomerPredicate(id))

Это не может быть сделано, потому что он не знает, что делает EmployeePredicates.CustomerPredicate.

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

I подозреваю лучшее решение будет включать что-то вродеэто:

public static Expression<Func<Employee, int, bool>> CustomerPredicate()
{
     return (t, id) => t.CustomerId == id;
}

... так как это повышает абстракцию на один уровень;он дает вам дерево выражений, которое использует id как ParameterExpression, что вам нужно для построения соответствующего дерева выражений для вызова CompileQuery.К сожалению, об этом трудно думать: (

...