上传流到数据库

pge*_*hev 4 c# database entity-framework

我有一个FileForUploading应该上传到数据库的类。

public class FileForUploading
{
    public FileForUploading(string filename, Stream stream)
    {
        this.Filename = filename;
        this.Stream = stream;
    }

    public string Filename { get; private set; }

    public Stream Stream { get; private set; }
}
Run Code Online (Sandbox Code Playgroud)

我正在使用实体框架将其转换FileForUploadingEntity 为一个非常简单的类,但只包含该Filename属性。我不想将其存储Stream在内存中,而是将其直接上传到数据库。

将数据Stream直接“流式传输”到数据库的最佳方式是什么?

到目前为止,我想出了这个

private void UploadStream(string name, Stream stream)
    {
        var sqlQuery = @"UPDATE dbo.FilesForUpload SET Content =@content WHERE Name=@name;";

        var nameParameter = new SqlParameter()
        {
            ParameterName = "@name",
            Value = name
        };

        var contentParameter = new SqlParameter()
        {
            ParameterName = "@content",
            Value = ConvertStream(stream),
            SqlDbType = SqlDbType.Binary
        };

        // the database context used throughout the application.
        this.context.Database.ExecuteSqlCommand(sqlQuery, contentParameter, nameParameter);
    }
Run Code Online (Sandbox Code Playgroud)

这是 myConvertStream将 the 转换Stream为 a byte[]。(它作为 a 存储varbinary(MAX)在数据库中。

private static byte[] ConvertStream(Stream stream)
    {
        using (var memoryStream = new MemoryStream())
        {
            stream.CopyTo(memoryStream);
            return memoryStream.ToArray();
        }
    }
Run Code Online (Sandbox Code Playgroud)

上述解决方案是否足够好?如果Stream它很大,它会表现好吗?

Igo*_*gor 5

我不想将 Stream 存储在内存中,而是将其直接上传到数据库。

使用您提出的上述解决方案,您的应用程序中的内存中仍然有流的内容,您最初提到这是您试图避免的。

最好的办法是绕过 EF 并使用 async 函数上传流。以下示例取自MSDN 文章 SqlClient Streaming Support

// Application transferring a large BLOB to SQL Server in .Net 4.5
private static async Task StreamBLOBToServer() {

 using (SqlConnection conn = new SqlConnection(connectionString)) {
    await conn.OpenAsync();
    using (SqlCommand cmd = new SqlCommand("INSERT INTO [BinaryStreams] (bindata) VALUES (@bindata)", conn)) {
       using (FileStream file = File.Open("binarydata.bin", FileMode.Open)) {

          // Add a parameter which uses the FileStream we just opened
          // Size is set to -1 to indicate "MAX"
          cmd.Parameters.Add("@bindata", SqlDbType.Binary, -1).Value = file;

          // Send the data to the server asynchronously
          await cmd.ExecuteNonQueryAsync();
       }
    }
 }
}
Run Code Online (Sandbox Code Playgroud)

您可以将此示例转换为以下内容以使其适合您。请注意,您应该更改方法上的签名以使其异步,这样您就可以利用在长期数据库更新期间不会阻塞线程的优势。

// change your signature to async so the thread can be released during the database update/insert act
private async Task UploadStreamAsync(string name, Stream stream) {

    var conn = this.context.Database.Connection; // SqlConnection from your DbContext
    if(conn.State != ConnectionState.Open)
        await conn.OpenAsync();
    using (SqlCommand cmd = new SqlCommand("UPDATE dbo.FilesForUpload SET Content =@content WHERE Name=@name;", conn)) {
          cmd.Parameters.Add(new SqlParameter(){ParameterName = "@name",Value = name});
          // Size is set to -1 to indicate "MAX"
          cmd.Parameters.Add("@content", SqlDbType.Binary, -1).Value = stream;
          // Send the data to the server asynchronously
          await cmd.ExecuteNonQueryAsync();
    }
}
Run Code Online (Sandbox Code Playgroud)

再一记。如果您想保存大型非结构化数据集(即您上传的流),那么最好不要将它们保存在数据库中。有很多原因,但最重要的是,关系数据库的设计并没有真正考虑到这一点,处理数据很麻烦,而且它们可以真正快速地消耗数据库空间,使其他操作(即备份、恢复等)变得更加困难)。

有一种替代方法仍然允许您在本机中将指针保存在记录中,但将实际的非结构化数据驻留在磁盘上。您可以使用Sql Server FileStream执行此操作。在 ADO.NET 中,您将使用SqlFileStream。这是关于如何配置 Sql Server 和数据库以允许 Sql File Streams 的一个很好的演练。它还有一些关于如何使用SqlFileStream该类的Vb.net 示例。

SQL Server FileStream 简介


我确实假设您使用 Microsoft Sql Server 作为您的数据存储库。如果此假设不正确,请更新您的问题,并为您正在连接的正确数据库服务添加标签。