В настоящее время я ищу способы передачи списков целых чисел в запросе SQL и пытаюсь решить, какой из них лучше всего подходит в какой ситуации, каковы преимущества каждого и каковы подводные камни, чего следует избегать :)
Сейчас я знаю 3 способа, которыми мы сейчас пользуемся в нашем приложении.
1) Табличный параметр:
Создайте новый табличный параметр на сервере sql:
CREATE TYPE [dbo].[TVP_INT] AS TABLE(
[ID] [int] NOT NULL
)
Затем выполните запрос к нему:
using (var conn = new SqlConnection(DataContext.GetDefaultConnectionString))
{
var comm = conn.CreateCommand();
comm.CommandType = CommandType.Text;
comm.CommandText = @"
UPDATE DA
SET [tsLastImportAttempt] = CURRENT_TIMESTAMP
FROM [Account] DA
JOIN @values IDs ON DA.ID = IDs.ID";
comm.Parameters.Add(new SqlParameter("values", downloadResults.Select(d => d.ID).ToDataTable()) { TypeName = "TVP_INT" });
conn.Open();
comm.ExecuteScalar();
}
Основными недостатками этого метода является тот факт, что Linq не поддерживает табличные параметры (если вы создадите SP с параметром TVP, linq не сможет его запустить): (
2) Преобразуйте список в двоичный и используйте его в Linq!
Это немного лучше .. Создайте SP, и вы можете запустить его в linq:)
Для этого у SP будет параметр IMAGE, и мы будем использовать пользовательскую функцию (udf) для преобразования этого в таблицу. В настоящее время у нас есть реализации этой функции, написанные на C ++ и на ассемблере, оба имеют практически одинаковую производительность :)
По существу, каждое целое число представлено 4 байтами и передается в SP. В .NET у нас есть метод расширения, который обращается к IEnumerable в байтовый массив
Метод расширения:
public static Byte [] ToBinary (это IEnumerable intList)
{
return ToBinaryEnum (intList) .ToArray ();
} * * Тысяча двадцать-один
private static IEnumerable<Byte> ToBinaryEnum(IEnumerable<Int32> intList)
{
IEnumerator<Int32> marker = intList.GetEnumerator();
while (marker.MoveNext())
{
Byte[] result = BitConverter.GetBytes(marker.Current);
Array.Reverse(result);
foreach (byte b in result)
yield return b;
}
}
ИП:
CREATE PROCEDURE [Accounts-UpdateImportAttempts]
@values IMAGE
AS
BEGIN
UPDATE DA
SET [tsLastImportAttempt] = CURRENT_TIMESTAMP
FROM [Account] DA
JOIN dbo.udfIntegerArray(@values, 4) IDs ON DA.ID = IDs.Value4
END
И мы можем использовать его, запустив SP напрямую или в любом запросе linq, который нам нужен
using (var db = new DataContext())
{
db.Accounts_UpdateImportAttempts(downloadResults.Select(d => d.ID).ToBinary());
// or
var accounts = db.Accounts
.Where(a => db.udfIntegerArray(downloadResults.Select(d => d.ID).ToBinary(), 4)
.Select(i => i.Value4)
.Contains(a.ID));
}
Преимущество этого метода заключается в использовании скомпилированных запросов в linq (который будет иметь то же определение sql и план запроса, поэтому также будет кэшироваться), и его также можно использовать в SP.
Оба эти метода теоретически не ограничены, поэтому вы можете передавать миллионы целых за раз:)
3) Простое linq .Contains ()
Это более простой подход и идеально подходит для простых сценариев. Но это, конечно, ограничено.
using (var db = new DataContext())
{
var accounts = db.Accounts
.Where(a => downloadResults.Select(d => d.ID).Contains(a.ID));
}
Самым большим недостатком этого метода является то, что каждое целое число в переменной downloadResults будет передаваться как отдельное целое число. В этом случае запрос ограничивается sql (максимально допустимые параметры в запросе sql, который представляет собой пару тысяч, если я правильно помню).
<ч />
Так что я хотел бы спросить ... Как вы думаете, что является лучшим из них, и какие другие методы и подходы я пропустил?