Vui lòng không không tạo một DataTable
để tải qua BulkCopy. Đó là một giải pháp phù hợp cho các tập dữ liệu nhỏ hơn, nhưng hoàn toàn không có lý do gì để tải tất cả 10 triệu hàng vào bộ nhớ trước khi gọi cơ sở dữ liệu.
Đặt cược tốt nhất của bạn (ngoài BCP
/ BULK INSERT
/ OPENROWSET(BULK...)
) là truyền nội dung từ tệp vào cơ sở dữ liệu thông qua Tham số giá trị bảng (TVP). Bằng cách sử dụng TVP, bạn có thể mở tệp, đọc một hàng và gửi một hàng cho đến khi hoàn tất, sau đó đóng tệp. Phương thức này có vùng nhớ chỉ là một hàng. Tôi đã viết một bài báo, Truyền dữ liệu vào SQL Server 2008 từ một ứng dụng, trong đó có một ví dụ về tình huống này.
Tổng quan đơn giản về cấu trúc như sau. Tôi giả định rằng bảng nhập và tên trường giống như được hiển thị trong câu hỏi ở trên.
Các đối tượng cơ sở dữ liệu bắt buộc:
-- 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
Dưới đây là mã ứng dụng C # để sử dụng các đối tượng SQL ở trên. Lưu ý cách thay vì điền vào một đối tượng (ví dụ:DataTable) và sau đó thực hiện Thủ tục được lưu trữ, trong phương pháp này, việc thực thi Thủ tục được lưu trữ sẽ bắt đầu đọc nội dung tệp. Tham số đầu vào của Stored Proc không phải là một biến; nó là giá trị trả về của một phương thức, GetFileContents
. Phương thức đó được gọi khi SqlCommand
cuộc gọi ExecuteNonQuery
, mở tệp, đọc một hàng và gửi hàng đó đến SQL Server qua IEnumerable<SqlDataRecord>
và yield return
cấu trúc, và sau đó đóng tệp. Thủ tục đã lưu trữ chỉ thấy một Biến bảng, @ImportTable, có thể được truy cập ngay sau khi dữ liệu bắt đầu chuyển sang ( lưu ý:dữ liệu vẫn tồn tại trong một thời gian ngắn, ngay cả khi không phải là toàn bộ nội dung, trong 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
phương thức trên được sử dụng làm giá trị tham số đầu vào cho Thủ tục được lưu trữ như được hiển thị bên dưới:
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;
}
Ghi chú bổ sung:
- Với một số sửa đổi, mã C # ở trên có thể được điều chỉnh để phù hợp với dữ liệu.
- Với sửa đổi nhỏ, mã C # ở trên có thể được điều chỉnh để gửi trong nhiều trường (ví dụ được hiển thị trong bài viết "Dữ liệu hấp ..." được liên kết ở trên chuyển sang 2 trường).
- Bạn cũng có thể thao tác giá trị của từng bản ghi trong
SELECT
tuyên bố trong chương trình. - Bạn cũng có thể lọc ra các hàng bằng cách sử dụng điều kiện WHERE trong chương trình.
- Bạn có thể truy cập Biến Bảng TVP nhiều lần; nó là SN SÀNG nhưng không phải là "chỉ chuyển tiếp".
- Ưu điểm so với
SqlBulkCopy
:-
SqlBulkCopy
là chỉ INSERT trong khi sử dụng TVP cho phép dữ liệu được sử dụng theo bất kỳ cách nào:bạn có thể gọiMERGE
; bạn có thểDELETE
dựa trên một số điều kiện; bạn có thể chia dữ liệu thành nhiều bảng; vân vân. - Do TVP không chỉ ở chế độ CHÈN, bạn không cần một bảng dàn dựng riêng để kết xuất dữ liệu vào.
- Bạn có thể lấy lại dữ liệu từ cơ sở dữ liệu bằng cách gọi
ExecuteReader
thay vìExecuteNonQuery
. Ví dụ:nếu cóIDENTITY
trên trườngDATAs
nhập bảng, bạn có thể thêm mộtOUTPUT
mệnh đềINSERT
để trả lạiINSERTED.[ID]
(giả sửID
là tên củaIDENTITY
đồng ruộng). Hoặc bạn có thể trả lại kết quả của một truy vấn hoàn toàn khác hoặc cả hai vì nhiều bộ kết quả có thể được gửi và truy cập thông quaReader.NextResult()
. Không thể lấy lại thông tin từ cơ sở dữ liệu khi sử dụngSqlBulkCopy
Tuy nhiên, có một số câu hỏi ở đây trên S.O. những người muốn làm chính xác điều đó (ít nhất là liên quan đếnIDENTITY
mới được tạo giá trị). - Để biết thêm thông tin về lý do tại sao quá trình tổng thể đôi khi nhanh hơn, ngay cả khi tải dữ liệu từ đĩa vào SQL Server chậm hơn một chút, vui lòng xem báo cáo chính thức này từ Nhóm tư vấn khách hàng của SQL Server:Tối đa hóa thông lượng với TVP
-