Tolong jangan jangan buat DataTable
untuk memuat melalui BulkCopy. Itu adalah solusi yang baik untuk kumpulan data yang lebih kecil, tetapi sama sekali tidak ada alasan untuk memuat 10 juta baris ke dalam memori sebelum memanggil database.
Taruhan terbaik Anda (di luar BCP
/ BULK INSERT
/ OPENROWSET(BULK...)
) adalah mengalirkan konten dari file ke database melalui Parameter Bernilai Tabel (TVP). Dengan menggunakan TVP Anda dapat membuka file, membaca baris &mengirim baris hingga selesai, lalu menutup file. Metode ini memiliki jejak memori hanya satu baris. Saya menulis sebuah artikel, Streaming Data Ke SQL Server 2008 Dari Aplikasi, yang memiliki contoh skenario ini.
Gambaran sederhana dari struktur tersebut adalah sebagai berikut. Saya mengasumsikan tabel impor dan nama bidang yang sama seperti yang ditunjukkan pada pertanyaan di atas.
Objek database yang diperlukan:
-- First: You need a User-Defined Table Type
CREATE TYPE ImportStructure AS TABLE (Field VARCHAR(MAX));
GO
-- Second: Use the UDTT as an input param to an import proc.
-- Hence "Tabled-Valued Parameter" (TVP)
CREATE PROCEDURE dbo.ImportData (
@ImportTable dbo.ImportStructure READONLY
)
AS
SET NOCOUNT ON;
-- maybe clear out the table first?
TRUNCATE TABLE dbo.DATAs;
INSERT INTO dbo.DATAs (DatasField)
SELECT Field
FROM @ImportTable;
GO
Kode aplikasi C# untuk menggunakan objek SQL di atas ada di bawah. Perhatikan bagaimana daripada mengisi objek (misalnya DataTable) dan kemudian mengeksekusi Stored Procedure, dalam metode ini eksekusi Stored Procedure yang memulai pembacaan konten file. Parameter input dari Stored Proc bukan variabel; ini adalah nilai kembalian dari suatu metode, GetFileContents
. Metode itu dipanggil ketika SqlCommand
memanggil ExecuteNonQuery
, yang membuka file, membaca baris dan mengirimkan baris ke SQL Server melalui IEnumerable<SqlDataRecord>
dan yield return
konstruksi, dan kemudian menutup file. Prosedur Tersimpan hanya melihat Variabel Tabel, @ImportTable, yang dapat diakses segera setelah data mulai masuk (catatan:data bertahan untuk waktu yang singkat, meskipun bukan konten lengkap, dalam tempdb ).
using System.Collections;
using System.Data;
using System.Data.SqlClient;
using System.IO;
using Microsoft.SqlServer.Server;
private static IEnumerable<SqlDataRecord> GetFileContents()
{
SqlMetaData[] _TvpSchema = new SqlMetaData[] {
new SqlMetaData("Field", SqlDbType.VarChar, SqlMetaData.Max)
};
SqlDataRecord _DataRecord = new SqlDataRecord(_TvpSchema);
StreamReader _FileReader = null;
try
{
_FileReader = new StreamReader("{filePath}");
// read a row, send a row
while (!_FileReader.EndOfStream)
{
// 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 a string
// call ReadLine() into the string, do manipulation(s) / validation(s) on
// the string, then pass that string into SetString() or discard if invalid.
_DataRecord.SetString(0, _FileReader.ReadLine());
yield return _DataRecord;
}
}
finally
{
_FileReader.Close();
}
}
GetFileContents
metode di atas digunakan sebagai nilai parameter input untuk Prosedur Tersimpan seperti yang ditunjukkan di bawah ini:
public static void test()
{
SqlConnection _Connection = new SqlConnection("{connection string}");
SqlCommand _Command = new SqlCommand("ImportData", _Connection);
_Command.CommandType = CommandType.StoredProcedure;
SqlParameter _TVParam = new SqlParameter();
_TVParam.ParameterName = "@ImportTable";
_TVParam.TypeName = "dbo.ImportStructure";
_TVParam.SqlDbType = SqlDbType.Structured;
_TVParam.Value = GetFileContents(); // return value of the method is streamed data
_Command.Parameters.Add(_TVParam);
try
{
_Connection.Open();
_Command.ExecuteNonQuery();
}
finally
{
_Connection.Close();
}
return;
}
Catatan tambahan:
- Dengan beberapa modifikasi, kode C# di atas dapat disesuaikan untuk mengelompokkan data.
- Dengan sedikit modifikasi, kode C# di atas dapat disesuaikan untuk mengirim dalam beberapa bidang (contoh yang ditampilkan dalam artikel "Mengukus Data..." yang ditautkan di atas lolos dalam 2 bidang).
- Anda juga dapat memanipulasi nilai setiap record di
SELECT
pernyataan dalam proses. - Anda juga dapat memfilter baris dengan menggunakan kondisi WHERE dalam proc.
- Anda dapat mengakses Variabel Tabel TVP beberapa kali; ini HANYA BACA tetapi tidak "hanya untuk meneruskan".
- Kelebihan dari
SqlBulkCopy
:SqlBulkCopy
hanya INSERT sedangkan menggunakan TVP memungkinkan data digunakan dengan cara apa pun:Anda dapat memanggilMERGE
; Anda dapatDELETE
berdasarkan beberapa kondisi; Anda dapat membagi data menjadi beberapa tabel; dan seterusnya.- Karena TVP tidak hanya INSERT, Anda tidak memerlukan tabel staging terpisah untuk memasukkan data.
- Anda bisa mendapatkan kembali data dari database dengan memanggil
ExecuteReader
bukannyaExecuteNonQuery
. Misalnya, jika adaIDENTITY
bidang padaDATAs
impor tabel, Anda dapat menambahkanOUTPUT
klausa keINSERT
untuk mengirimkan kembaliINSERTED.[ID]
(dengan asumsiID
adalah namaIDENTITY
bidang). Atau Anda dapat meneruskan kembali hasil kueri yang sama sekali berbeda, atau keduanya karena beberapa set hasil dapat dikirim dan diakses melaluiReader.NextResult()
. Mendapatkan info kembali dari database tidak dimungkinkan saat menggunakanSqlBulkCopy
namun ada beberapa pertanyaan di sini di S.O. orang yang ingin melakukan hal itu (setidaknya berkaitan denganIDENTITY
yang baru dibuat nilai). - Untuk info lebih lanjut tentang mengapa terkadang lebih cepat untuk keseluruhan proses, meskipun sedikit lebih lambat dalam mendapatkan data dari disk ke SQL Server, silakan lihat buku putih ini dari Tim Penasihat Pelanggan SQL Server:Memaksimalkan Throughput dengan TVP