Как мне добавить фильтр даты в сгенерированный SQL-запрос без доступа к контексту базы данных?
EF выполняет очень дорогие запросы. Я не передаю и не использую результаты как IEnumerable, поэтому я ожидаю, что EF сделает эффективный запрос. Я ожидал, что он будет использовать время начала и окончания для фильтрации результатов в SQL.
EF переводит следующее
context.Channels
.First(ch => ch.Channel_ID == id)
.ChannelValues
.Where(cv => start < cv.ValueTime && cv.ValueTime <= stop);
в
SELECT TOP (1)
[Extent1].[Channel_ID] AS [Channel_ID],
[Extent1].[ChannelType_ID] AS [ChannelType_ID],
[Extent1].[Name] AS [Name],
FROM [dbo].[Channel] AS [Extent1]
WHERE [Extent1].[Channel_ID] = @p__linq__0
p__linq__0: '1' (Type = Int32, IsNullable = false)
SELECT
[Extent1].[Channel_ID] AS [Channel_ID],
[Extent1].[ValueTime] AS [ValueTime],
[Extent1].[Value] AS [Value]
FROM [dbo].[ChannelValue] AS [Extent1]
WHERE [Extent1].[Channel_ID] = @EntityKeyValue1
EntityKeyValue1: '1' (Type = Int32, IsNullable = false)
Первый SQL-запрос меня устраивает, но второй должен создать запрос, аналогичный тому, что делает следующий
context.ChannelValues
.Where(cv => cv.Channel_ID == id && start < cv.ValueTime && cv.ValueTime <= stop);
это приводит к
SELECT
[Extent1].[Channel_ID] AS [Channel_ID],
[Extent1].[ValueTime] AS [ValueTime],
[Extent1].[Value] AS [Value]
FROM [dbo].[ChannelValue] AS [Extent1]
WHERE ([Extent1].[Channel_ID] = @p__linq__0) AND (@p__linq__1 < [Extent1].[ValueTime]) AND ([Extent1].[ValueTime] <= @p__linq__2)
p__linq__0: '1' (Type = Int32, IsNullable = false)
p__linq__1: '7/1/2018 12:00:00 AM' (Type = DateTime2, IsNullable = false)
p__linq__2: '7/23/2018 11:45:00 AM' (Type = DateTime2, IsNullable = false
Там, где мне это действительно нужно, у меня нет доступа к DatabaseContext.
@ sprinter252 Спасибо, но у меня нет доступа к контексту базы данных. У меня есть только сущность канала. Я пробовал включить, это делает запрос более сложным, но не добавляет предложения datetime where
Итак .. Какой у вас вопрос?
@MennoB Как мне добавить фильтр даты в запрос SQL без доступа к контексту базы данных? Отредактирую свой вопрос.
Что вы имеете в виду, когда говорите «У меня нет доступа к DatabaseContext».? Я не понимаю, почему вы просто не используете второй подход для начала. Что дает вам первый подход, чего нет во втором? Вы включили ленивую загрузку?
@Flater Я полагаю, я мог бы повторно фактор, чтобы передать контекст базы данных во все мои функции ... Но это кажется ненужным, когда у EF есть вся информация для начала. Насколько я понимаю, по умолчанию используется ленивая загрузка.
@Badger: Я не понимаю, почему у вас нет доступа к контексту базы данных, но каким-то образом все еще есть доступ к самому EF. Я думаю, что это скорее причина проблемы, не могли бы вы подробнее рассказать об этой части кода?
Если вы хотите, чтобы ваш запрос выполнял некоторые действия с вашей базой данных, EF нужен ваш DatabaseContext. Вы можете использовать те же лямбды в Коллекциях для управления данными, но в конце концов всегда будет что-то вроде DatabaseContext.SomeMethod () для управления данными в базе данных.
@Flater Итак, я передаю канал context.Channels.First(ch => ch.Channel_ID == id) другой функции, затем в этой функции я хочу получить доступ к значениям, связанным с этим каналом channel.ChannelValues .Where(cv => start < cv.ValueTime && cv.ValueTime <= stop);.
@MennoB Я знаю, что переменная канала, которую я использую, находится за прокси-объектом, который имеет доступ к ObjectContext (не может найти контекст базы данных) и использует его для запроса БД, но я не могу получить доступ к этому ObjectContext без большого размышления .
@Badger: Тебе действительно не следует этого делать. Фактически вы все еще передаете свой контекст, поскольку он является частью «ленивой загрузки» в сущности. Однако вы больше не можете напрямую получить доступ к своему контексту. Здесь есть много подводных камней, например если ваш первый метод использовал using() для вашего контекста, возврат из метода удаляет контекст, даже если вы все еще не загрузили свои данные лениво. Вы должны вернуть все данные, которые вам нужны. перед вы закрываете свой контекст, а передача лениво загруженного объекта от метода к методу значительно усложнит вашу жизнь как разработчика.
@Badger: Я до сих пор не понимаю, почему нельзя просто использовать второй вариант.
@Flater using(var context = new DatabaseContext) { var channel = context.Channels.First(ch => ch.Channel_ID == id); var total = Totalize(channel); return total; }
@Flater Предыдущий комментарий - это просто пример, поэтому я не сохраняю объект после того, как контекст удален. Второй вариант не используется, потому что он включает в себя несколько функций, я мог бы провести рефакторинг, но это похоже на то, что EF должен делать, иначе свойства навигации действительно полезны только для отношений от 1: 1 до 1: 100





«EF делает очень дорогие запросы» ммм, у вас есть контроль над тем, как вы хотели бы использовать фреймворк с его ограничением *, если вы знаете, что что-то плохо, но все же делаете это так, это не ошибка EF.
Вы могли бы сделать это именно так, как вы.
т.е. если бы вы сделали это с необработанным sql, как бы вы это сделали.
Затем просто сделайте то же самое в EF.
var channel = context.Channels.First(x=> x.Channel_ID == id)
channel.ChannelValues = context.ChannelValues.Where(x => x.Channel_ID == id
&& start < x.ValueTime
&& x.ValueTime <= stop
).ToList();
Фактический ответ, который вы даете, не отличается от второго предложения OP (объединенный Where без первого). Другая часть (сохранение подмножества как части channel.ChannelValues) на самом деле не является частью вопроса.
Приведенный пример кода является моим вторым примером, однако у меня нет доступа к контексту базы данных в функции, которая требует значений. SQL в моем вопросе генерируется EF.
@Badger извините, но я действительно не понимаю, о чем вы спрашиваете. «Как мне добавить фильтр даты в сгенерированный запрос SQL без доступа к контексту базы данных?» Пожалуйста, просто укажите sql, который вы хотите сгенерировать .... тогда мы можем преобразовать его в linq
@Flater, извините, я просто пытаюсь понять, о чем спрашивают. см. ответ на OP. выше.
Я не думаю, что вторая часть запроса вообще передается в SQL.
Это говорит SQL вернуть все ChannelValues с Channel_ID, равным id.
context.Channels
.First(ch => ch.Channel_ID == id)
.ChannelValues
Затем в памяти выполняется второе предложение where.
.Where(cv => start < cv.ValueTime && cv.ValueTime <= stop);
Чтобы выполнить все это в базе данных, вам нужно будет сделать что-то вроде этого:
context.Channels
.Where(ch => ch.Channel_ID == id)
.SelectMany(x => x.ChannelValues)
.Where(cv => start < cv.ValueTime && cv.ValueTime <= stop)
Хотя я не знаю, отвечает ли это на ваш вопрос, как это сделать без доступа к контексту базы данных.
Если ленивая загрузка включена, .ChannelValues еще не будет создавать экземпляры данных (поскольку они еще не были перечислены). Последующий .Where() не будет операцией в памяти. Если ленивая загрузка отключена, то в итоге будет получен NullReferenceException, поскольку ChannelValues имеет значение null, потому что не было явного Include.
SelectMany() здесь практически ничего не решает. First() по своей сути создает экземпляр объекта канала, независимо от того, используете ли вы SelectMany() после этого (или нет).
На тесте был загружен эквивалент .ChannelValues, а не ноль. Заменили First() на Where()
Но почему это может быть лучше, чем второе предложение OP? Конечный результат такой же.
context.Channels.First() извлекает Channel из базы данных, затем его ChannelValues загружаются ленивой загрузкой (все они!), И, наконец, эти значения фильтруются в памяти. Действительно, возможное решение - использовать SelectMany.
Я принимаю это, потому что это самый близкий ответ. Оказывается, ленивая загрузка EntityFramework не такая «ленивая», как хотелось бы. Я мог бы написать свой собственный метод расширения для First, который возвращает мой собственный прокси-объект, чтобы делать то, что я хочу, чтобы EF делал. Спасибо всем.
context.Channels.Include(ch => ch.ChannelValues).Where(ch => ch.Channel_Id = id && ch => start < ch.ChannelValue.ValueTime && ch.ChannelValue.ValueTime <= stop)меняет его в лучшую сторону? Это не идеальный вопрос для случая. Я просто хотел намекнуть вам использовать include.