В чем разница между объединением двух разных контекстов БД с использованием 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();
Ответы
Ответ 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 в фоновом режиме.