Подтвердить что ты не робот

В чем разница между объединением двух разных контекстов БД с использованием ToList() и .AsQueryable()?

Случай 1: Я вхожу в два разных контекста БД методом ToList() в обоих контекстах.

Случай 2: А также попытался объединить первый контекст Db с ToList() и второй с AsQueryable().

Оба работали для меня. Все, что я хочу знать, - это разница между этими объединениями в отношении производительности и функциональности. Какая из них лучше?

var users = (from usr in dbContext.User.AsNoTracking()
                  select new
                  {
                     usr.UserId,
                     usr.UserName
                  }).ToList();

 var logInfo= (from log in dbContext1.LogInfo.AsNoTracking()
               select new
               {
                   log.UserId,
                   log.LogInformation
               }).AsQueryable();

 var finalQuery= (from usr in users
                  join log in logInfo on usr.UserId equals log.UserId
                  select new
                  {
                     usr.UserName,
                     log.LogInformation
                  }.ToList();
4b9b3361

Ответ 1

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

Во-первых, это соединение не может быть выполнено в базе данных, потому что вы соединяете объект в памяти (users) с отложенным запросом (logInfo). Исходя из этого, невозможно создать запрос, который можно отправить в базу данных. Это означает, что перед выполнением фактического соединения выполняется отложенный запрос, и все журналы извлекаются из базы данных. Подводя итог, в этом сценарии в базе данных выполняется 2 запроса, и соединение происходит в памяти. Не важно, используете ли вы ToList + AsQueryable или ToList + ToList.

Во-вторых, в вашем сценарии это объединение может выполняться ТОЛЬКО в памяти. Даже если вы используете AsQueryable с первым контекстом и со вторым контекстом, это не сработает. Вы получите исключение System.NotSupportedException с сообщением:

Указанное выражение LINQ содержит ссылки на запросы, связанные с различными контекстами.

Интересно, почему вы используете 2 контекста БД. Это действительно необходимо? Как я объяснил из-за этого, вы потеряли возможность полностью использовать отложенные запросы (ленивые оценочные функции).

Если вам действительно нужно использовать 2 контекста БД, я рассмотрю вопрос о добавлении некоторых фильтров (условий WHERE) к запросам, ответственным за чтение пользователей и журналов из БД. Зачем? Для небольшого количества записей нет проблем. Однако для большого объема данных неэффективно выполнять объединения в памяти. Для этого были созданы базы данных.

Ответ 2

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

Если вы меняете оба списка...

var finalQuery= (from log in logInfo
                 join usr in users on log.UserId equals usr.UserId
                 ...

EF будет бросать

Невозможно создать постоянное значение типа "Пользователь". В этом контексте поддерживаются только примитивные типы или типы перечислений.

Итак, почему ваш код работает?

Это станет ясно, если мы преобразуем ваш оператор в синтаксис метода (который выполняется во время выполнения под капотом):

users.Join(logInfo, usr => usr.UserId, log => log.UserId
            (usr,log) => new
                        {
                            usr.UserName,
                            log.LogInformation
                        }

Так как users является IEnumerable, метод расширения Enumerable.Join разрешен как соответствующий метод. Этот метод принимает IEnumerable как второй список для объединения. Поэтому logInfo неявно приводится к IEnumerable, поэтому он запускается как отдельный оператор SQL, прежде чем он присоединяется к соединению.

В версии from log in logInfo join usr ... используется Queryable.Join. Теперь usr преобразуется в IQueryable. Это превращает весь оператор в одно выражение, которое EF безуспешно пытается перевести в один оператор SQL.

Теперь несколько слов о

Какой из них лучше?

Лучшим вариантом является тот, который делает достаточно, чтобы заставить его работать. Это означает, что

  • Вы можете удалить AsQueryable(), потому что logInfo уже есть IQueryable, и он все равно добавляется к IEnumerable.
  • Вы можете заменить ToList() на AsEnumerable(), потому что ToList() создает избыточный промежуточный результат, а AsEnumerable() изменяет только тип выполнения users, не запуская его выполнение еще.

Ответ 3

ToList()

  • Выполнить запрос немедленно
  • Вы получите все элементы, готовые в памяти

AsQueryable()

  • lazy (выполнить запрос позже)
  • Параметр: Expression<Func<TSource, bool>>
  • Преобразовать выражение в T-SQL (с конкретным поставщиком), запросить удаленно и загрузить результат в вашу память приложения.
  • Вот почему DbSet (в Entity Framework) также наследует IQueryable для получения эффективного запроса.
  • Он не загружает каждую запись. Например. если Take (5), он будет генерировать select top 5 * SQL в фоновом режиме.