Sqlserver
 sql >> Datenbank >  >> RDS >> Sqlserver

Übergeben Sie Dictionary an Stored Procedure T-SQL

Die akzeptierte Antwort, ein TVP zu verwenden, ist im Allgemeinen richtig, bedarf jedoch einer Klärung basierend auf der Menge der übergebenen Daten. Die Verwendung einer DataTable ist für kleinere Datensätze in Ordnung (ganz zu schweigen von der Schnelligkeit und Einfachheit), für größere Datensätze jedoch schon nicht skalieren, da das Dataset dupliziert wird, indem es einfach in die DataTable eingefügt wird, um es an SQL Server zu übergeben. Für größere Datensätze gibt es also die Möglichkeit, den Inhalt jeder benutzerdefinierten Sammlung zu streamen. Die einzige wirkliche Anforderung ist, dass Sie die Struktur in Bezug auf SqlDb-Typen definieren und die Sammlung durchlaufen müssen, was beides ziemlich triviale Schritte sind.

Eine vereinfachte Übersicht über die minimale Struktur ist unten gezeigt, die eine Anpassung der Antwort ist, die ich auf Wie kann ich 10 Millionen Datensätze in kürzester Zeit einfügen? gepostet habe, die sich mit dem Importieren von Daten aus einer Datei befasst und daher etwas anders ist als die Daten sind derzeit nicht im Speicher. Wie Sie dem unten stehenden Code entnehmen können, ist dieses Setup nicht allzu kompliziert, aber hochflexibel sowie effizient und skalierbar.

SQL-Objekt Nr. 1:Struktur definieren

-- First: You need a User-Defined Table Type
CREATE TYPE dbo.IDsAndOrderNumbers AS TABLE
(
   ID NVARCHAR(4000) NOT NULL,
   SortOrderNumber INT NOT NULL
);
GO

SQL-Objekt Nr. 2:Verwenden Sie die Struktur

-- Second: Use the UDTT as an input param to an import proc.
--         Hence "Tabled-Valued Parameter" (TVP)
CREATE PROCEDURE dbo.ImportData (
   @ImportTable    dbo.IDsAndOrderNumbers READONLY
)
AS
SET NOCOUNT ON;

-- maybe clear out the table first?
TRUNCATE TABLE SchemaName.TableName;

INSERT INTO SchemaName.TableName (ID, SortOrderNumber)
    SELECT  tmp.ID,
            tmp.SortOrderNumber
    FROM    @ImportTable tmp;

-- OR --

some other T-SQL

-- optional return data
SELECT @NumUpdates AS [RowsUpdated],
       @NumInserts AS [RowsInserted];
GO

C#-Code, Teil 1:Iterator/Sender definieren

using System.Collections;
using System.Data;
using System.Data.SqlClient;
using System.IO;
using Microsoft.SqlServer.Server;

private static IEnumerable<SqlDataRecord> SendRows(Dictionary<string,int> RowData)
{
   SqlMetaData[] _TvpSchema = new SqlMetaData[] {
      new SqlMetaData("ID", SqlDbType.NVarChar, 4000),
      new SqlMetaData("SortOrderNumber", SqlDbType.Int)
   };
   SqlDataRecord _DataRecord = new SqlDataRecord(_TvpSchema);
   StreamReader _FileReader = null;

      // read a row, send a row
      foreach (KeyValuePair<string,int> _CurrentRow in RowData)
      {
         // You shouldn't need to call "_DataRecord = new SqlDataRecord" as
         // SQL Server already received the row when "yield return" was called.
         // Unlike BCP and BULK INSERT, you have the option here to create an
         // object, do manipulation(s) / validation(s) on the object, then pass
         // the object to the DB or discard via "continue" if invalid.
         _DataRecord.SetString(0, _CurrentRow.ID);
         _DataRecord.SetInt32(1, _CurrentRow.sortOrderNumber);

         yield return _DataRecord;
      }
}

C#-Code, Teil 2:Iterator/Sender verwenden

public static void LoadData(Dictionary<string,int> MyCollection)
{
   SqlConnection _Connection = new SqlConnection("{connection string}");
   SqlCommand _Command = new SqlCommand("ImportData", _Connection);
   SqlDataReader _Reader = null; // only needed if getting data back from proc call

   SqlParameter _TVParam = new SqlParameter();
   _TVParam.ParameterName = "@ImportTable";
// _TVParam.TypeName = "IDsAndOrderNumbers"; //optional for CommandType.StoredProcedure
   _TVParam.SqlDbType = SqlDbType.Structured;
   _TVParam.Value = SendRows(MyCollection); // method return value is streamed data
   _Command.Parameters.Add(_TVParam);
   _Command.CommandType = CommandType.StoredProcedure;

   try
   {
      _Connection.Open();

      // Either send the data and move on with life:
      _Command.ExecuteNonQuery();
      // OR, to get data back from a SELECT or OUTPUT clause:
      SqlDataReader _Reader = _Command.ExecuteReader();
      {
       Do something with _Reader: If using INSERT or MERGE in the Stored Proc, use an
       OUTPUT clause to return INSERTED.[RowNum], INSERTED.[ID] (where [RowNum] is an
       IDENTITY), then fill a new Dictionary<string, int>(ID, RowNumber) from
       _Reader.GetString(0) and _Reader.GetInt32(1). Return that instead of void.
      }
   }
   finally
   {
      _Reader.Dispose(); // optional; needed if getting data back from proc call
      _Command.Dispose();
      _Connection.Dispose();
   }
}