Pull to refresh

IEnumerable<T> и IQueryable<T>, в чем разница?

.NET *
Уважаемые Хабровчане, решил поделиться одним не очевидным моментом использования LinqToSql, с которым столкнулся некоторое время назад. А именно, про особенности использования каскадных Linq запросов:

Работая над очередным ASP.NET MVC проектом, и проектируя уровень доступа к БД мне потребовалось проверить качество скриптов, генерируемых Framework-ом L2C.

Что имеем (упрощенный вариант модели):


class User {
  public long Id { get; set; }
  public string Name { get; set; }
  public IEnumerable<Parameter> Parameters { get; set; }
}

class Parameter {
  public long UserId { get; set; }
  public string Name { get; set; }
  public string Value { get; set; }
}

Для получения данных написан класс UserRepository:

class UserRepository: MyProjectDataContext {
  public IEnumerable GetUsers() {
    return this.Users.ToModelUsers();
  }
}

static class RepositoryHelper {
  public static IEnumerable<Model.User> ToModelUsers(this IEnumerable<DataAccess.User> users) {
    return users.Select(u => new Model.User { Id = u.Id, Name = u.Name, Parameters = u.Parameters.ToModelParameters() });
  }

  public static IEnumerable<Model.Parameter> ToModelParameters(this IEnumerable<DataAccess.Parameter> parameters) {
    return parameters.Select(u => new Model.Parameter {… });
  }
}

пишем
var users = userRepository.GetUsers().ToList();

смотрим профайлер, и с удивлением обнаруживаем что для загрузки 10 пользователей и их параметров было выполнено целых 11 запросов.

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

public IEnumerable<User> GetUsers() {
  return this.Users.ToModelUsers().Select(u => new Model.User {
    Id = u.Id, Name = u.Name, Parameters = u.Parameters.Select(p => new Model.Parameter {… })
  });
}

Картина меняется кардинально. Всего 1 запрос, в котором грузятся как user-ы так и Parameter-ы

Где собака зарыта?


Все очень просто, все дело в том в первоначальном варианте после вызова метода GetUsers мы получали запрос содержащий 2 разных выражения: Linq2Sql и Linq2Object, а во втором только Linq2Sql.

А теперь выводы


Как оказалось, Linq Extension-ы ведут себя совершенно по разному в случае использования переменной типа IEnumerable и IQueryable, и это не случайно. Дело в том, что метод user.Select(...) есть у обоих классов System.Linq.Enumerable и System.Linq.Queryable соответственно, но реализация конечно же отличается (в чем легко убедиться с помощью Reflector-а).

Что делать?


Есть 2 пути, либо указывать тип IQueryable<T> для возвращаемого значения

static class RepositoryHelper {
  public static IQueryable<Model.User> ToModelUsers(this IQueryable<DataAccess.User> users) {
    return users.Select(u => new Model.User { Id = u.Id, Name = u.Name, Parameters = u.Parameters.ToModelParameters() });
  }
}

либо явно приводить IEnumerable<T> к IQueryable<T> вызовом метода .AsQueryable<T>()

А самое важное — оставаться внимательным и не забывать, что скрывается за ситаксическим сахаром фич .Net 3.5 / 4.0
Tags:
Hubs:
Total votes 64: ↑43 and ↓21 +22
Views 44K
Comments Comments 35