Странное поведение с расширением IEnumerable / IQueryable (без отложенной загрузки?) - PullRequest
2 голосов
/ 27 июля 2011

У меня странное поведение, которое я не могу понять.

Два метода ниже, как я понимаю (что, по-видимому, неправильно), должны вести себя так же, как и IQueryable, но они этого не делают.

Если я вызываю первый с помощью IQueryable (объект представляет собой DbSet из каркаса сущностей, который явно используется как IQueryable), то, похоже, он не использует отложенную загрузку (он выполняет сканирование базы данных). Когда я вызываю второй метод с тем же объектом, он, кажется, работает так, как я хочу (он выполняет поиск в базе данных).

Итак, два вопроса:

  • Почему это происходит?

  • Могу ли я (и как) заставить самый общий метод (с IEnumerable) работать "правильно"? (Поскольку у меня больше расширений, и я не хочу дублировать код, я бы хотел избежать перегрузки и просто вставить тело метода, как показано ниже)

Я использую EF 4.1 для работы с базой данных SQL Server Express 2008

public static TEntity GetByID<TEntity>(this IEnumerable<TEntity> list, long id) where TEntity : Identifiable
{
   return list.SingleOrDefault(e => e.ID == id);
}

public static TEntity GetByID<TEntity>(this IQueryable<TEntity> list, long id) where TEntity : Identifiable
{
    return list.SingleOrDefault(e => e.ID == id);
}

Ответы [ 4 ]

5 голосов
/ 27 июля 2011

IEnumerable<T> не передает выражение запроса поставщику EF LINQ, а вместо этого выполняет SingleOrDefault() в памяти. Это требует полной загрузки вашей таблицы в память, после чего следует SingleOrDefault(). Используя версию IQueryable<T>, провайдер получает правильное дерево выражений, которое он переводит в нужный вам SQL. Отсюда и разница.

2 голосов
/ 27 июля 2011

Поскольку list вводится как IEnumerable, в вашем первом случае поставщик Entity Framework Query воспринимает это как подсказку для выполнения над ним метода SingleOrDefault() в памяти (он будет использовать Linqметоды для Enumerable вместо Queryable) - поэтому вы видите сканирование базы данных, чтобы материализовать полный список.

Также см. метод AsEnumerable() и этот пост Джона Скита: «Переопределение LINQ для объектов: часть 36 - AsEnumerable»

0 голосов
/ 13 августа 2013

Для тех, кто приходит сюда, еще одна вещь, которую стоит отметить:

Вы должны быть абсолютно уверены, что вы предоставляете выражение методу SingleOrDefault(), в противном случае возможно следующее:

public static TEntity GetByID<TEntity>(this IEnumerable<TEntity> list, Func<TEntity,bool> selector) where TEntity : Identifiable
{
   return list.SingleOrDefault(selector);
}

public static TEntity GetByID<TEntity>(this IQueryable<TEntity> list, Func<TEntity,bool> selector) where TEntity : Identifiable
{
    return list.SingleOrDefault(selector);
}

Эти методы будут работать точно так же. ЗАЧЕМ? Поскольку вы не предоставляете выражение для списка IQueryable<>, фактически получается, что список будет автоматически преобразован в IEnumerable<>, а затем селектор будет запущен на IEnumerable<>. Таким образом, фактически вы извлекаете всю таблицу / список из памяти в БД, а затем запускаете селектор в списке в памяти.

Я только что сделал это, и я думал, что схожу с ума.

Если вы передадите Expression<Func<TEntity,bool>> в IQueryable<>, это будет выполнено на стороне БД.

0 голосов
/ 27 июля 2011

FirstOrDefault определяется отдельно для IEnumerable и IQueryable System.Linq.Queryable.FirstOrDefault () вызывается во втором случае.

Если вы хотите объединить оба метода в один, вы можете проверить, является ли list списком IQueryable, и использовать вместо него метод расширения Queryable.

public static TEntity GetByID<TEntity>(this IEnumerable<TEntity> list, long id) where TEntity : Identifiable
{
    var query = list as IQueryable<TEntity>;
    if (query != null)
        return query.SingleOrDefault(e => e.ID == id);
    return list.SingleOrDefault(e => e.ID == id);
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...