Как заставить анонимный метод работать в LINQ to Entities? - PullRequest
2 голосов
/ 21 июня 2011

Я пытаюсь создать универсальный метод, который EF4.1 будет искать как в базе данных, так и в локальной памяти для определенной строки в таблице, которая соответствует определенным критериям.

Пока что эточто у меня такое.

Это звонящий.

dbEntities.MyTables.LocalAndDb(delegate(MyTable s)
                { return s.Description.Contains("test"); });

Это LocalAndDb

public static object LocalAndDb<T>(this DbSet<T> myTable, Func<T, bool> function) where T : class
{   
    // look in local 
    var item = myTable.Local.Where(o => function((T)o)).FirstOrDefault()
    // if not exist, look in the database
    if (item == null)
    {
        Expression<Func<T, bool>> predicate = (u) => function(u);
        item = myTable.Where(predicate).FirstOrDefault();
    }
    return item;
}

Проблема с этой строкой.

    item = myTable.Where(predicate).FirstOrDefault();

Когда он вызывает базу данных, он выдает эту ошибку.

"Тип узла выражения LINQ 'Invoke' не поддерживается в LINQ to Entities."

Я думаю, это потому, что яЯ передаю анонимный метод, и он не знает, как превратить это в SQL.Я думал, что преобразование его в объект Expression поможет, но он все еще не работает для меня.

Что мне нужно сделать, чтобы анонимный метод стал тем, что LINQ может превратить в SQL?

1 Ответ

4 голосов
/ 21 июня 2011

Чтобы сделать это, вам нужно передать лямбда-выражение в LocalAndDb в виде дерева выражений (чтобы LINQ to Entities мог анализировать код и переводить его в SQL):

public static object LocalAndDb<T>(this DbSet<T> myTable, 
    Expression<Func<T, bool>> expr) where T : class {
    // ...
    if (item == null) {
        item = myTable.Where(expr).FirstOrDefault();
    }
    return item;
}

Тогда, конечно, проблема в том, что вы не можете выполнить дерево выражений при проверке данных в памяти. Одним из способов решения этой проблемы является использование Compile метода Expression<T>, но это будет немного неэффективно (в зависимости от вашего сценария).

Другой вариант - передать условие как дерево функций и выражений:

public static object LocalAndDb<T>(this DbSet<T> myTable, 
    Func<T, boo> function, Expression<Func<T, bool>> expr) where T : class {
    var item = myTable.Local.Where(o => function((T)o)).FirstOrDefault();
    if (item == null) {
        item = myTable.Where(expr).FirstOrDefault();
    }
    return item;
}

table.LocalAndDb(t => t.Foo > 10, t => t.Foo > 10);

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

class Precompiled<T1, T2> {
  public Precompiled(Expression<Func<T1, T2>> expr) {
    this.Expression = expr;
    this.Function = expr.Compile();
  }
  public Expression<Func<T1,T2>> Expression { get; private set; }
  public Func<T1,T2> Function { get; private set; }
}
...