TVP を使用するという受け入れられた答えは一般的に正しいですが、渡されるデータの量に基づいて明確化する必要があります。 DataTable を使用することは、小さなデータ セットの場合は問題ありません (迅速かつ簡単であることは言うまでもありません) が、大きなセットの場合は問題ありません。データセットを SQL Server に渡すためだけに DataTable に配置することでデータセットを複製するため、スケールしません。そのため、より大きなデータ セットの場合は、任意のカスタム コレクションのコンテンツをストリーミングするオプションがあります。唯一の実際の要件は、SqlDb 型の観点から構造を定義し、コレクションを反復処理する必要があることです。どちらも非常に簡単な手順です。
最小限の構造の単純化された概要を以下に示します。これは、可能な限り最短時間で 1000 万レコードを挿入するにはどうすればよいですか?に投稿した回答を適応したものです。、ファイルからのデータのインポートを処理するため、データが現在メモリにないため、わずかに異なります。以下のコードからわかるように、このセットアップは過度に複雑ではありませんが、柔軟性が高く、効率的でスケーラブルです。
SQL オブジェクト # 1: 構造を定義する
-- 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 オブジェクト # 2: 構造体を使用する
-- 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# コード、パート 1: イテレーター/送信者を定義する
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# コード、パート 2: イテレーター/センダーを使用する
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();
}
}