如何在不创建大型缓冲区的情况下将.NET对象的大型图序列化为SQL Server BLOB?

如何在不创建大型缓冲区的情况下将.NET对象的大型图序列化为SQL Server BLOB?,.net,sql-server,serialization,ado.net,memory-management,.net,Sql Server,Serialization,Ado.net,Memory Management,我们有如下代码: ms = New IO.MemoryStream bin = New System.Runtime.Serialization.Formatters.Binary.BinaryFormatter bin.Serialize(ms, largeGraphOfObjects) dataToSaveToDatabase = ms.ToArray() // put dataToSaveToDatabase in a Sql server BLOB 但是内存蒸汽从给我们带来问题的大内存

我们有如下代码:

ms = New IO.MemoryStream
bin = New System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
bin.Serialize(ms, largeGraphOfObjects)
dataToSaveToDatabase = ms.ToArray()
// put dataToSaveToDatabase in a Sql server BLOB

但是内存蒸汽从给我们带来问题的大内存堆中分配了一个大缓冲区。那么,我们如何在不需要足够的空闲内存来保存序列化对象的情况下对数据进行流处理呢

我正在寻找一种方法,从SQL server获取流,然后将其传递到bin.Serialize(),从而避免将所有数据保留在我的内存中

同样地,对于读回数据


更多背景。

这是一个复杂的数字处理系统的一部分,该系统近乎实时地处理数据,查找设备问题等。完成序列化是为了在数据源等的数据质量出现问题时允许重新启动(我们存储数据源,并可以在操作员编辑坏值后重新运行数据源)

因此,我们序列化对象的频率要比反序列化对象的频率高很多

我们正在序列化的对象包括非常大的数组大部分是双倍数组以及许多“更正常”的小对象。我们正在推动32位系统的内存限制,并使车库收集器工作非常努力。(系统中其他地方正在对此进行改进,例如重用大型阵列,而不是创建新阵列。)

通常,状态的序列化是导致内存不足异常的原因;我们的最高内存使用率是在进行序列化时

我认为当我们反序列化对象时会出现大内存池碎片,考虑到数组的大小,我认为大内存池碎片还存在其他问题。(这还没有被调查过,因为第一次看到这一点的人是数字处理专家,而不是内存管理专家。)

客户是否混合使用Sql Server 2000、2005和2008,如果可能的话,我们不希望每个版本的Sql Server都有不同的代码路径

我们一次可以有许多活动模型(在不同的过程中,跨多台机器),每个模型可以有许多保存的状态。因此,保存的状态存储在数据库blob中,而不是文件中

由于保存状态的扩展非常重要,我不希望将对象序列化为文件,然后将文件一次一个块地放入BLOB中

我问过的其他相关问题


为什么不实现您自己的system::io:流派生类?这将允许您通过直接将其附加到SQL列以进行写入

eg(伪码)

插入带有blob列的DB记录 “已初始化”(请参见上面的UpdateText 第条)
创建您的流类型/ 将数据库连接与 流
把溪水送到河边 序列化调用

它可以将对它的调用分块(我猜是8040字节的倍数),并在每个完整的缓冲区上以适当的偏移量传递给DB UpdateText调用

在流结束时,您将通过UpdateText清除未完全填满缓冲区的剩余内容

同样,您可以使用相同/相似的派生流来允许从DB列读取数据,并将其传递给反序列化

创建一个派生流并不是那么多的工作——我在C++/CLI中这样做是为了提供与IStream的互操作性——如果我能做到的话:)。。。(如果有帮助的话,我可以向您提供我作为示例编写的C++/CLI流代码)


如果将整个操作(插入初始行、通过流更新blob的调用)放入事务中,则在序列化步骤失败时,可以避免任何潜在的数据库不一致。

始终可以使用在线协议TDS(表格数据流)在较低级别写入SQL Server微软从第一天就开始使用。即使SQLAzure使用它,他们也不太可能在任何时候改变它

您可以从Mono项目和freetds项目中看到这是如何工作的源代码

查看
tds\u blob


图形是什么样子的

这里的一个问题是河流;SQL 2005需求是一个难题,否则您可以直接写入
SqlFileStream
,但是,我认为编写自己的
Stream
实现来缓冲8040(或多个)字节并增量写入不会太难。然而,我不确定它是否值得这么复杂——我很想用一个文件作为临时缓冲区,然后在插入/附加块的文件上循环(一旦序列化)。我不认为文件系统会损害您的整体性能,而且它可以避免您开始编写注定要失败的数据—也就是说,在您已经知道要编写什么数据之前,您不会与数据库通信。它还将帮助您尽可能缩短连接打开的时间

下一个问题是序列化本身。就我个人而言,我不建议使用
BinaryFormatter
来写入持久存储(仅用于传输),因为它在编码器本身和类型中都是特定于实现的(即,如果对数据类型进行无害的更改,那么它是脆弱的)

如果您的数据可以充分地表示为一棵树(而不是一个完整的图形),那么我很想尝试使用protocol buffers/protobuf net。这种编码(由谷歌设计)比
BinaryFormatter
输出更小,读写速度更快,并且是基于契约的,而不是基于字段的,因此您可以在以后可靠地重新对其进行水化(即使您完全切换平台)

默认选项意味着它必须在每个对象之前写入对象长度(在您的情况下这可能会很昂贵),但是如果您有大型(d)的嵌套列表
using System;
using System.Collections.Generic;
using System.IO;
using ProtoBuf;
[ProtoContract]
public class Foo {
    private readonly List<Bar> bars = new List<Bar>();
    [ProtoMember(1, DataFormat = DataFormat.Group)]
    public List<Bar> Bars { get { return bars;}}
}
[ProtoContract]
public class Bar {
    [ProtoMember(1)]
    public int Id { get; set; }
    [ProtoMember(2)]
    public string Name { get; set; }
}
static class Program {
    static void Main() {
        var obj = new Foo { Bars = {
            new Bar { Id = 123, Name = "abc"},
            new Bar { Id = 456, Name = "def"},
        } };
        // write it and show it
        using (MemoryStream ms = new MemoryStream()) {
            Serializer.Serialize(ms, obj);
            Console.WriteLine(BitConverter.ToString(ms.ToArray()));
        }
    }
}
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
 }
CREATE TABLE [dbo].[Uploads](
    [Id] [int] IDENTITY(1,1) NOT NULL,
    [FileName] [varchar](256) NULL,
    [ContentType] [varchar](256) NULL,
    [FileData] [varbinary](max) NULL)
[Serializable]
class HugeSerialized
{
    public byte[] theBigArray { get; set; }
}
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();
    }
}
CREATE TABLE BigFiles 
(
    [BigDataID] [int] IDENTITY(1,1) NOT NULL,
    [Data] VARBINARY(MAX) NULL
)
using (FileStream sourceStream = new FileStream(filePath, FileMode.Open))
{
    using (SqlCommand cmd = new SqlCommand(string.Format("UPDATE BigFiles SET Data=@Data WHERE BigDataID = @BigDataID"), _sqlConn))
    {
        cmd.Parameters.AddWithValue("@Data", sourceStream);
        cmd.Parameters.AddWithValue("@BigDataID", entryId);

        cmd.ExecuteNonQuery();
    }
}