#c# #sql #linq-to-sql #query-optimization
#c# #sql #linq-to-sql #оптимизация запросов
Вопрос:
Я ищу кучу int32 в базе данных SQL (Compact edition), используя LINQ2SQL.
Моя основная проблема заключается в том, что у меня есть большой список (тысячи) int32, и я хочу, чтобы все записи в БД, где поле id в БД соответствует любому из моих int32. В настоящее время я выбираю по одной строке за раз, эффективно выполняя поиск по индексу тысячи раз.
Как я могу это оптимизировать? Временная таблица?
Комментарии:
1. Покажите нам запрос LINQ, который вы используете в данный момент.
2. Откуда вы берете этот список int32?
3. Я получаю список int32 из сложной и совершенно секретной вещи. Имеет ли это значение? 😉 Это результат алгоритма, ищущего определенные ключевые точки на изображениях.
Ответ №1:
Звучит так, как будто вы могли бы использовать запрос Contains:
int[] intArray = ...;
var matches = from item in context.SomeTable
where intArray.Contains(item.id)
select item;
Комментарии:
1. Это не будет работать для многих значений, превышающих 2000 — вы получаете серьезную ошибку в журналах SQl.
2. Под рукой нет SQL, но есть что-то, связанное с выполнением запроса с более чем определенным количеством параметров.
3. С помощью Google: bit.ly/mgahHX и вопрос SO по этому поводу: bit.ly/j68myx
4. Интересно, я не знал об этом ограничении
5. @ck возникнет ли такое же ограничение, если создать динамический оператор where из выражений OR?
Ответ №2:
Для serarching для тысяч значений вашими вариантами являются:
- Отправьте XML-блок в хранимую процедуру (сложную, но выполнимую)
- Создайте временную таблицу, массово загрузите данные, затем присоединитесь к ней (может вызвать проблемы с параллелизмом)
- Выполните несколько запросов (т. Е. разбейте вашу группу идентификаторов на фрагменты по тысяче или около того и используйте решение BrokenGlass)
Я не уверен, что вы можете сделать с Compact Edition.
Ответ №3:
Вставьте свои целые числа в таблицу SQL, затем выполните :
var items = from row in table
join intRow in intTable on row.TheIntColumn equals intRow.IntColumn
select row;
Редактировать 1 и 2: изменил ответ, чтобы он объединял 2 таблицы, без коллекций.
Комментарии:
1. Вы не можете присоединиться к списку в Linq2SQL
2. Что ж, тогда я снова отредактирую свой ответ, чтобы сказать, что он должен сделать то, что я уже написал в своей правке, если он хочет использовать это решение 😉
3. Пробовал другие советы на этой странице. Этот вариант был самым быстрым на сегодняшний день.
4. Вам следует заглянуть в ‘LINQPad’. Одна из функций, которая мне больше всего нравится в программе, заключается в том, что вы можете видеть результирующий SQL-запрос. Таким образом, вы можете немного подкорректировать свой код, чтобы позволить «Сверх-оптимизированным» серверам SQL обрабатывать нагрузку.
Ответ №4:
Я бы предпочел написать хранимую процедуру для поиска. Если у вас есть индекс в поле, которое вы ищете, это значительно упростит вам жизнь в будущем, когда количество обрабатываемых строк увеличится.
Сложность, с которой вы столкнетесь, заключается в написании инструкции select, которая может выполнять предложение IN из входного параметра. Что вам нужно, так это иметь табличную функцию для преобразования строки (идентификаторов) в столбец и использования этого столбца в предложении IN. Нравится:
Select *
From SomeTable So
Where So.ID In (Select Column1 From dbo.StringToTable(InputIds))
Ответ №5:
Я придумал это решение linq после того, как устал писать пакетный код вручную. Это не идеально (т. Е. пакеты не совсем идеальны), но это решает проблему. Очень полезно, когда вам не разрешено писать хранимые процедуры или функции sql. Работает практически с каждым выражением linq.
Наслаждайтесь:
public static IQueryable<TResultElement> RunQueryWithBatching<TBatchElement, TResultElement>(this IList<TBatchElement> listToBatch, int batchSize, Func<List<TBatchElement>, IQueryable<TResultElement>> initialQuery)
{
return RunQueryWithBatching(listToBatch, initialQuery, batchSize);
}
public static IQueryable<TResultElement> RunQueryWithBatching<TBatchElement, TResultElement>(this IList<TBatchElement> listToBatch, Func<List<TBatchElement>, IQueryable<TResultElement>> initialQuery)
{
return RunQueryWithBatching(listToBatch, initialQuery, 0);
}
public static IQueryable<TResultElement> RunQueryWithBatching<TBatchElement, TResultElement>(this IList<TBatchElement> listToBatch, Func<List<TBatchElement>, IQueryable<TResultElement>> initialQuery, int batchSize)
{
if (listToBatch == null)
throw new ArgumentNullException("listToBatch");
if (initialQuery == null)
throw new ArgumentNullException("initialQuery");
if (batchSize <= 0)
batchSize = 1000;
int batchCount = (listToBatch.Count / batchSize) 1;
var batchGroup = listToBatch.AsQueryable().Select((elem, index) => new { GroupKey = index % batchCount, BatchElement = elem }); // Enumerable.Range(0, listToBatch.Count).Zip(listToBatch, (first, second) => new { GroupKey = first, BatchElement = second });
var keysBatchGroup = from obj in batchGroup
group obj by obj.GroupKey into grouped
select grouped;
var groupedBatches = keysBatchGroup.Select(key => key.Select((group) => group.BatchElement));
var map = from employeekeysBatchGroup in groupedBatches
let batchResult = initialQuery(employeekeysBatchGroup.ToList()).ToList() // force to memory because of stupid translation error in linq2sql
from br in batchResult
select br;
return map;
}
использование:
using (var context = new SourceDataContext())
{
// some code
var myBatchResult = intArray.RunQueryWithBatching(batch => from v1 in context.Table where batch.Contains(v1.IntProperty) select v1, 2000);
// some other code that makes use of myBatchResult
}
затем либо используйте result, либо разверните до list, либо что угодно, что вам нужно. Просто убедитесь, что вы не потеряли ссылку DataContext.