it-swarm.com.de

Übergeben von Tabellenwertparametern an gespeicherte Prozeduren aus .NET-Code

Ich habe eine MS SQL Server 2005-Datenbank. In einigen Prozeduren habe ich Tabellenparameter, die ich als nvarchar (durch Kommas getrennt) an einen gespeicherten proc übergebe und intern in einzelne Werte aufteile. Ich füge es der Liste der SQL-Befehlsparameter wie folgt hinzu:

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

Ich muss die Datenbank auf SQL Server 2008 migrieren. Ich weiß, dass es Tabellenwertparameter gibt, und ich weiß, wie ich sie in gespeicherten Prozeduren verwende. Ich weiß aber nicht, wie ich einen in einem SQL-Befehl an die Parameterliste übergeben soll. Kennt jemand die korrekte Syntax des Parameters.Add Verfahren? Oder gibt es eine andere Möglichkeit, diesen Parameter zu übergeben?

155
Marek Kwiendacz

DataTable, DbDataReader oder IEnumerable<SqlDataRecord> -Objekte können zum Auffüllen eines Tabellenwertparameters gemäß dem MSDN-Artikel verwendet werden Tabellenwertparameter in SQL Server 2008 (ADO.NET) .

Das folgende Beispiel zeigt die Verwendung eines DataTable oder eines IEnumerable<SqlDataRecord>:

SQL-Code :

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

C # -Code :

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

Zusätzlich zu Ryans Antwort müssen Sie auch die Eigenschaft DataColumn der Ordinal festlegen, wenn Sie es mit einem table-valued parameter mit mehrere Spalten mit den Ordnungszahlen nicht in alphabetischer Reihenfolge.

Wenn Sie beispielsweise den folgenden Tabellenwert haben, der in SQL als Parameter verwendet wird:

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

Sie müssten Ihre Spalten als solche in C # bestellen:

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

Wenn Sie dies nicht tun, wird ein Analysefehler angezeigt. Die Konvertierung von nvarchar in int ist fehlgeschlagen.

28
Scotty.NET

Generisch

   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

Die sauberste Art, damit zu arbeiten. Angenommen, Ihre Tabelle enthält eine Liste von Ganzzahlen mit dem Namen "dbo.tvp_Int" (Anpassen für Ihren eigenen Tabellentyp).

Erstellen Sie diese Erweiterungsmethode ...

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;
   }
}

Jetzt können Sie einen Tabellenwertparameter in einer Zeile an einer beliebigen Stelle hinzufügen, indem Sie dies tun:

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

Verwenden Sie diesen Code, um geeignete Parameter aus Ihrem Typ zu erstellen:

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