Het geaccepteerde antwoord op het gebruik van een TVP is over het algemeen correct, maar behoeft enige verduidelijking op basis van de hoeveelheid gegevens die wordt doorgegeven. Het gebruik van een DataTable is prima (om nog maar te zwijgen van snel en gemakkelijk) voor kleinere gegevenssets, maar voor grotere sets wel niet geschaald, aangezien het de dataset dupliceert door deze in de DataTable te plaatsen, simpelweg om deze door te geven aan SQL Server. Voor grotere gegevenssets is er dus een optie om de inhoud van een aangepaste verzameling te streamen. De enige echte vereiste is dat je de structuur moet definiëren in termen van SqlDb-typen en de verzameling moet doorlopen, wat beide vrij triviale stappen zijn.
Een simplistisch overzicht van de minimale structuur wordt hieronder weergegeven, wat een aanpassing is van het antwoord dat ik heb gepost op Hoe kan ik 10 miljoen records in de kortst mogelijke tijd invoegen?, dat handelt over het importeren van gegevens uit een bestand en daarom iets anders is als de gegevens bevinden zich momenteel niet in het geheugen. Zoals je kunt zien aan de hand van de onderstaande code, is deze setup niet al te ingewikkeld, maar toch zeer flexibel, maar ook efficiënt en schaalbaar.
SQL-object #1:Definieer de structuur
-- 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-object #2:Gebruik de structuur
-- 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, deel 1:definieer de iterator/afzender
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, deel 2:gebruik de iterator/afzender
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();
}
}