#c# #entity-framework #sqlite #linq #entity-framework-core
#c# #entity-framework #sqlite #linq #сущность-фреймворк-ядро
Вопрос:
У меня есть необработанный SQL-запрос, который я хотел бы преобразовать в LINQ и выполнить с помощью моей базы данных SQLite, но у меня возникли серьезные проблемы: предполагается, что запрос вернет мне только 2 строки.
Код SQL:
WITH XXXX("A", "B", "C", "D", "E", "F")
AS
(
SELECT "A", "B", "C", "D", "E", "F" FROM "TABLEXYZ"
WHERE ("A" <> 0) AND ("B" = 32)
ORDER BY "C" DESC
)
SELECT *
FROM ( SELECT * FROM XXXX LIMIT 1)
UNION
SELECT *
FROM ( SELECT * FROM XXXX WHERE ("F" = 0) LIMIT 1)
Это то, что, как я думал, сработало бы:
var query = dataStore.XXXXTable.Where(x => x.A != 0 amp;amp; x.B == 32).OrderByDescending(x => x.C).AsCte();
XXXXData? lastData = query.FirstOrDefault();
XXXXData? lastCorrectData = query.FirstOrDefault(data => data.F == 0);
Но это выдает ошибку:
Выражение LINQ ‘…’ не удалось перевести. Либо перепишите запрос в форме, которую можно преобразовать, либо явно переключитесь на оценку клиента, вставив вызов ‘AsEnumerable’, ‘AsAsyncEnumerable’, ‘ToList’ или ‘ToListAsync’.
Я не хочу, чтобы это оценивалось клиентом.
Приведенные ниже, похоже, работают, но запрос выполняется дважды, или, по крайней мере, это то, что указывает LINQPad:
1: подзапрос вызывается дважды
var query = dataStore.XXXXTable.Where(x => x.A != 0 amp;amp; x.B == 32).OrderByDescending(x => x.C);
XXXXData? lastData = query.FirstOrDefault();
XXXXData? lastCorrectData = query.FirstOrDefault(data => data.F == 0);
2: подзапрос вызывается дважды
Expression<Func<DbSet<XXXXTable>, IOrderedQueryable<XXXXTable>>>? query = (DbSet<XXXXTable> table) =>
table.Where(x => x.A != 0 amp;amp; x.B == 32)
.OrderByDescending(x => x.C);
var func = query.Compile();
// Yet another attempt - query executed twice
XXXXTable? lastData = func(dataStore.XXXXTable).FirstOrDefault();
XXXXTable? lastCorrectData = func(dataStore.XXXXTable).FirstOrDefault(data => data.F == 0);
3: подзапрос вызывается дважды
Expression<Func<DbSet<XXXXTable>, IOrderedQueryable<XXXXTable>>>? query = (DbSet<XXXXTable> table) =>
table.Where(x => x.A != 0 amp;amp; x.B == 32)
.OrderByDescending(x => x.C);
var func = query.Compile();
dataStore.XXXXTable.Select(x =>
new
{
LastData = func(dataStore.XXXXTable).FirstOrDefault(),
LastCorrectData = func(dataStore.XXXXTable).FirstOrDefault(data => data.F == 0)
}
).FirstOrDefault();
Возможно ли, чтобы этот подзапрос XXXX вызывался только один раз, я знаю, что мог бы обойти эту проблему, используя LINQ aggregate, но, похоже, я не могу принудительно использовать EF Core LINQ для использования с предложением WITH без исключений во время выполнения.
Комментарии:
1. Да, вложенный запрос будет вызван дважды — почему это проблема?
2. Во время загрузки я вызываю этот метод несколько сотен раз с разными аргументами «B». Я пытаюсь уменьшить количество запросов, отправляемых в базу данных. Я полагаю, что в лучшем случае было бы просто отправить запрос с массивом различных аргументов «B», что в конечном итоге привело бы к доступу только к 1 БД.
3. Если вы хотите, чтобы я помог решить проблему, вы должны дать полную спецификацию проблемы. Я предполагаю, что вам нужна другая таблица с аргументами «B», а затем вы хотите выполнить объединение — это, вероятно, лучший способ быстрого получения результатов для системы SQL.
4. У меня есть несколько тысяч датчиков, подключенных к моему серверу, каждый датчик выполняет измерение каждые n минут и отправляет его на сервер. Сервер имеет экземпляр класса каждого датчика, поэтому, когда сервер получает данные, он устанавливает свойства датчика LastMeasurement и LastCorrectMeasurement (если измерение не содержит ошибок). Сам сервер выполняет много вычислений на основе последнего измерения датчика и LastCorrectMeasurement.
5. Проблема во времени запуска. При запуске сервера он должен загрузить LastMeasurement и LastCorrectMeasurement всех датчиков из базы данных, и это занимает довольно много времени.
Ответ №1:
для расширения ядра linq2db EF требуется хотя бы один ToLinqToDB()
вызов для переключения поставщика данных. Также ваш запрос нуждается в настройке.
var cteQuery = dataStore.XXXXTable
.Where(x => x.A != 0 amp;amp; x.B == 32)
.OrderByDescending(x => x.C)
.AsCte();
var lastData = cteQuery.Take(1);
var lastCorrectData = cteQuery.Where(data => data.F == 0).Take(1);
var query = lastData.Concat(lastCorrectData);
// switch to alternative translator
query = query.ToLinqToDB();
var result = query.ToList();
Ответ №2:
Таким образом, SQL-запрос, который вы хотите, вероятно, является ROW_NUMBER с разделом в идентификаторе датчика и order by в метке времени desc — затем вы выбираете all с номером строки = 1 и получаете набор данных со всеми необходимыми вам значениями. Вы можете создать представление для хранения этого запроса и выбрать десять из представления
вот так:
CREATE VIEW RECENT_SENSOR_DATA AS
(
SELECT *
FROM (
SELECT *,
ROW_NUMBER() OVER (PARTITION BY SENSOR_ID ORDER BY LAST_UPDATE DESC) AS RN
FROM SENSOR_DATA_TABLE
) X
WHERE RN = 1
)
Примечание — я использую *, потому что я не знаю имен полей в вашей таблице — вы должны использовать фактические имена полей в окончательном представлении, чтобы избежать всевозможных проблем с обслуживанием при использовании *