Передача целочисленных списков в SQL-запрос, лучшие практики - PullRequest
2 голосов
/ 12 мая 2010

В настоящее время я ищу способы передачи списков целых чисел в запросе 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, который представляет собой пару тысяч, если я правильно помню).

<ч />

Так что я хотел бы спросить ... Как вы думаете, что является лучшим из них, и какие другие методы и подходы я пропустил?

1 Ответ

2 голосов
/ 12 мая 2010

Вы можете использовать тип данных XML в качестве параметра

DECLARE @input xml
SET @input = '<Inputs><Input>1</Input><Input>2</Input></Inputs>'

            SELECT
                Inputs.Input.value('.','int') Input
            FROM
                @input.nodes('/Inputs/Input) as Inputs(Input)
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...