Преобразование SQL в LINQ, подзапрос вызывается дважды

#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
)
 

Примечание — я использую *, потому что я не знаю имен полей в вашей таблице — вы должны использовать фактические имена полей в окончательном представлении, чтобы избежать всевозможных проблем с обслуживанием при использовании *