web-dev-qa-db-fra.com

Comment passer des paramètres de valeur de table à une procédure stockée à partir de code .net

J'ai une base de données MS SQL Server 2005. Dans quelques procédures, j'ai des paramètres de table que je passe à un proc stocké en tant que nvarchar (séparés par des virgules) et que je divise en interne en valeurs uniques. Je l'ajoute à la liste des paramètres de commande SQL comme ceci:

cmd.Parameters.Add("@Logins", SqlDbType.NVarchar).Value = "jim18,jenny1975,cosmo";

Je dois migrer la base de données vers SQL Server 2008. Je sais qu'il existe des paramètres de valeur de table et je sais comment les utiliser dans des procédures stockées. Mais je ne sais pas comment passer à la liste des paramètres dans une commande SQL. Est-ce que quelqu'un connaît la syntaxe correcte de la procédure Parameters.Add? Ou existe-t-il un autre moyen de passer ce paramètre?

155
Marek Kwiendacz

Les objets DataTable, DbDataReader ou IEnumerable<SqlDataRecord> peuvent être utilisés pour renseigner un paramètre table comme indiqué dans l'article MSDN paramètres table dans SQL Server 2008 (ADO.NET) .

L'exemple suivant illustre l'utilisation de DataTable ou de IEnumerable<SqlDataRecord>:

Code SQL :

CREATE TABLE dbo.PageView
(
    PageViewID BIGINT NOT NULL CONSTRAINT pkPageView PRIMARY KEY CLUSTERED,
    PageViewCount BIGINT NOT NULL
);
CREATE TYPE dbo.PageViewTableType AS TABLE
(
    PageViewID BIGINT NOT NULL
);
CREATE PROCEDURE dbo.procMergePageView
    @Display dbo.PageViewTableType READONLY
AS
BEGIN
    MERGE INTO dbo.PageView AS T
    USING @Display AS S
    ON T.PageViewID = S.PageViewID
    WHEN MATCHED THEN UPDATE SET T.PageViewCount = T.PageViewCount + 1
    WHEN NOT MATCHED THEN INSERT VALUES(S.PageViewID, 1);
END

Code C # :

private static void ExecuteProcedure(bool useDataTable, 
                                     string connectionString, 
                                     IEnumerable<long> ids) 
{
    using (SqlConnection connection = new SqlConnection(connectionString)) 
    {
        connection.Open();
        using (SqlCommand command = connection.CreateCommand()) 
        {
            command.CommandText = "dbo.procMergePageView";
            command.CommandType = CommandType.StoredProcedure;

            SqlParameter parameter;
            if (useDataTable) {
                parameter = command.Parameters
                              .AddWithValue("@Display", CreateDataTable(ids));
            }
            else 
            {
                parameter = command.Parameters
                              .AddWithValue("@Display", CreateSqlDataRecords(ids));
            }
            parameter.SqlDbType = SqlDbType.Structured;
            parameter.TypeName = "dbo.PageViewTableType";

            command.ExecuteNonQuery();
        }
    }
}

private static DataTable CreateDataTable(IEnumerable<long> ids) 
{
    DataTable table = new DataTable();
    table.Columns.Add("ID", typeof(long));
    foreach (long id in ids) 
    {
        table.Rows.Add(id);
    }
    return table;
}

private static IEnumerable<SqlDataRecord> CreateSqlDataRecords(IEnumerable<long> ids) 
{
    SqlMetaData[] metaData = new SqlMetaData[1];
    metaData[0] = new SqlMetaData("ID", SqlDbType.BigInt);
    SqlDataRecord record = new SqlDataRecord(metaData);
    foreach (long id in ids) 
    {
        record.SetInt64(0, id);
        yield return record;
    }
}
256
Ryan Prechel

Suite à la réponse de Ryan, vous devrez également définir la propriété DataColumn de Ordinal si vous traitez avec un table-valued parameter avec multiple colonnes dont les ordinaux sont pas dans l'ordre alphabétique.

Par exemple, si vous avez la valeur de table suivante utilisée comme paramètre dans SQL:

CREATE TYPE NodeFilter AS TABLE (
  ID int not null
  Code nvarchar(10) not null,
);

Vous devez commander vos colonnes comme telles en C #:

table.Columns["ID"].SetOrdinal(0);
// this also bumps Code to ordinal of 1
// if you have more than 2 cols then you would need to set more ordinals

Si vous échouez, vous obtiendrez une erreur d'analyse, impossible de convertir nvarchar en int.

28
Scotty.NET

Générique

   public static DataTable ToTableValuedParameter<T, TProperty>(this IEnumerable<T> list, Func<T, TProperty> selector)
    {
        var tbl = new DataTable();
        tbl.Columns.Add("Id", typeof(T));

        foreach (var item in list)
        {
            tbl.Rows.Add(selector.Invoke(item));

        }

        return tbl;

    }
14
Martea

La façon la plus propre de travailler avec elle. En supposant que votre table est une liste d’entiers appelée "dbo.tvp_Int" (à personnaliser pour votre propre type de table)

Créer cette méthode d'extension ...

public static void AddWithValue_Tvp_Int(this SqlParameterCollection paramCollection, string parameterName, List<int> data)
{
   if(paramCollection != null)
   {
       var p = paramCollection.Add(parameterName, SqlDbType.Structured);
       p.TypeName = "dbo.tvp_Int";
       DataTable _dt = new DataTable() {Columns = {"Value"}};
       data.ForEach(value => _dt.Rows.Add(value));
       p.Value = _dt;
   }
}

Maintenant, vous pouvez ajouter un paramètre de valeur table dans une ligne n'importe où simplement en procédant comme suit:

cmd.Parameters.AddWithValueFor_Tvp_Int("@IDValues", listOfIds);
5
Shahzad Qureshi

Utilisez ce code pour créer un paramètre approprié à partir de votre type:

private SqlParameter GenerateTypedParameter(string name, object typedParameter)
{
    DataTable dt = new DataTable();

    var properties = typedParameter.GetType().GetProperties().ToList();
    properties.ForEach(p =>
    {
        dt.Columns.Add(p.Name, Nullable.GetUnderlyingType(p.PropertyType) ?? p.PropertyType);
    });
    var row = dt.NewRow();
    properties.ForEach(p => { row[p.Name] = (p.GetValue(typedParameter) ?? DBNull.Value); });
    dt.Rows.Add(row);

    return new SqlParameter
    {
        Direction = ParameterDirection.Input,
        ParameterName = name,
        Value = dt,
        SqlDbType = SqlDbType.Structured
    };
}
0
bside