Không có chức năng ADO.Net tích hợp sẵn để xử lý điều này một cách thực sự duyên dáng đối với dữ liệu lớn. Vấn đề là hai vấn đề:
- không có API nào để 'ghi' vào (các) lệnh SQL hoặc các tham số dưới dạng một luồng. Các loại tham số chấp nhận một luồng (như
FileStream
) chấp nhận luồng để ĐỌC từ nó, không đồng ý với ngữ nghĩa tuần tự hóa của write thành một dòng. Bất kể bạn chuyển điều này theo cách nào, bạn sẽ nhận được một bản sao trong bộ nhớ của toàn bộ đối tượng được tuần tự hóa, thật tệ. - ngay cả khi điểm ở trên sẽ được giải quyết (và không thể), giao thức TDS và cách SQL Server chấp nhận các tham số không hoạt động tốt với các tham số lớn vì toàn bộ yêu cầu phải được nhận lần đầu trước khi nó được đưa vào thực thi và điều này sẽ tạo ra các bản sao bổ sung của đối tượng bên trong SQL Server.
Vì vậy, bạn thực sự phải tiếp cận vấn đề này từ một góc độ khác. May mắn thay, có một giải pháp khá dễ dàng. Mẹo là sử dụng UPDATE .WRITE
hiệu quả cao cú pháp và truyền từng phần dữ liệu một, trong một chuỗi các câu lệnh T-SQL. Đây là cách được khuyến nghị MSDN, hãy xem phần Sửa đổi dữ liệu có giá trị lớn (tối đa) trong ADO.NET. Điều này trông có vẻ phức tạp, nhưng thực sự rất đơn giản để thực hiện và cắm vào một lớp Stream.
Lớp BlobStream
Đây là bánh mì và bơ của dung dịch. Một lớp dẫn xuất Luồng thực hiện phương thức Viết dưới dạng lời gọi tới cú pháp T-SQL BLOB WRITE. Nói thẳng ra, điều thú vị duy nhất về nó là nó phải theo dõi bản cập nhật đầu tiên vì UPDATE ... SET blob.WRITE(...)
cú pháp sẽ không thành công trên trường NULL:
class BlobStream: Stream
{
private SqlCommand cmdAppendChunk;
private SqlCommand cmdFirstChunk;
private SqlConnection connection;
private SqlTransaction transaction;
private SqlParameter paramChunk;
private SqlParameter paramLength;
private long offset;
public BlobStream(
SqlConnection connection,
SqlTransaction transaction,
string schemaName,
string tableName,
string blobColumn,
string keyColumn,
object keyValue)
{
this.transaction = transaction;
this.connection = connection;
cmdFirstChunk = new SqlCommand(String.Format(@"
UPDATE [{0}].[{1}]
SET [{2}] = @firstChunk
WHERE [{3}] = @key"
,schemaName, tableName, blobColumn, keyColumn)
, connection, transaction);
cmdFirstChunk.Parameters.AddWithValue("@key", keyValue);
cmdAppendChunk = new SqlCommand(String.Format(@"
UPDATE [{0}].[{1}]
SET [{2}].WRITE(@chunk, NULL, NULL)
WHERE [{3}] = @key"
, schemaName, tableName, blobColumn, keyColumn)
, connection, transaction);
cmdAppendChunk.Parameters.AddWithValue("@key", keyValue);
paramChunk = new SqlParameter("@chunk", SqlDbType.VarBinary, -1);
cmdAppendChunk.Parameters.Add(paramChunk);
}
public override void Write(byte[] buffer, int index, int count)
{
byte[] bytesToWrite = buffer;
if (index != 0 || count != buffer.Length)
{
bytesToWrite = new MemoryStream(buffer, index, count).ToArray();
}
if (offset == 0)
{
cmdFirstChunk.Parameters.AddWithValue("@firstChunk", bytesToWrite);
cmdFirstChunk.ExecuteNonQuery();
offset = count;
}
else
{
paramChunk.Value = bytesToWrite;
cmdAppendChunk.ExecuteNonQuery();
offset += count;
}
}
// Rest of the abstract Stream implementation
}
Sử dụng BlobStream
Để sử dụng lớp luồng blog mới tạo này, bạn cắm vào BufferedStream
. Lớp có một thiết kế tầm thường chỉ xử lý việc ghi luồng vào một cột của bảng. Tôi sẽ sử dụng lại một bảng từ một ví dụ khác:
CREATE TABLE [dbo].[Uploads](
[Id] [int] IDENTITY(1,1) NOT NULL,
[FileName] [varchar](256) NULL,
[ContentType] [varchar](256) NULL,
[FileData] [varbinary](max) NULL)
Tôi sẽ thêm một đối tượng giả để được tuần tự hóa:
[Serializable]
class HugeSerialized
{
public byte[] theBigArray { get; set; }
}
Cuối cùng là tuần tự hóa thực tế. Trước tiên, chúng tôi sẽ chèn một bản ghi mới vào Uploads
bảng, sau đó tạo một BlobStream
trên Id mới được chèn và gọi tuần tự hóa thẳng vào luồng này:
using (SqlConnection conn = new SqlConnection(Settings.Default.connString))
{
conn.Open();
using (SqlTransaction trn = conn.BeginTransaction())
{
SqlCommand cmdInsert = new SqlCommand(
@"INSERT INTO dbo.Uploads (FileName, ContentType)
VALUES (@fileName, @contentType);
SET @id = SCOPE_IDENTITY();", conn, trn);
cmdInsert.Parameters.AddWithValue("@fileName", "Demo");
cmdInsert.Parameters.AddWithValue("@contentType", "application/octet-stream");
SqlParameter paramId = new SqlParameter("@id", SqlDbType.Int);
paramId.Direction = ParameterDirection.Output;
cmdInsert.Parameters.Add(paramId);
cmdInsert.ExecuteNonQuery();
BlobStream blob = new BlobStream(
conn, trn, "dbo", "Uploads", "FileData", "Id", paramId.Value);
BufferedStream bufferedBlob = new BufferedStream(blob, 8040);
HugeSerialized big = new HugeSerialized { theBigArray = new byte[1024 * 1024] };
BinaryFormatter bf = new BinaryFormatter();
bf.Serialize(bufferedBlob, big);
trn.Commit();
}
}
Nếu bạn theo dõi quá trình thực thi của mẫu đơn giản này, bạn sẽ thấy rằng không có nơi nào tạo ra một luồng tuần tự hóa lớn. Mẫu sẽ phân bổ mảng [1024 * 1024] nhưng đó là dành cho mục đích demo để có thứ gì đó để tuần tự hóa. Mã này tuần tự hóa theo cách có bộ đệm, phân đoạn từng đoạn, sử dụng kích thước cập nhật được đề xuất BLOB của SQL Server là 8040 byte tại một thời điểm.