【问题标题】:Layered application: Store file in filestream in the database分层应用程序:将文件存储在数据库中的文件流中
【发布时间】:2012-02-16 16:53:03
【问题描述】:

对于一个 asp.Net MVC 项目,我需要处理大文件(主要是 200-300Mo,有时是 1Go)。

我会将它们存储在数据库中(出于备份原因/一致性原因)。

我担心性能问题,所以我想尽可能避免在程序中的任何地方都有一个字节数组,目标是在每个地方都使用流。

我有一个分层应用程序,这主要意味着我有几个“DataStore”,它们负责连接和检索/插入/更新数据库中的数据。

由于 EF 目前不支持 Filestream,我通过简单的 Sql 请求处理“文件部分”。我在这里阅读了一篇关于文件流使用的好文章:http://blog.tallan.com/2011/08/22/using-sqlfilestream-with-c-to-access-sql-server-filestream-data/

我还有一些其他问题,希望您能帮助我/指出好的方向:

  • 由于我有一个分层应用程序,一旦我实例化了我的 SQLFileStream 对象,我可以处置 SqlCommand/Sql Connection/Transaction 范围吗?
  • 如果没有,我应该如何关闭它们?
  • 在上一个链接中,有一个示例展示了如何将它与 ASP 一起使用。但是由于我使用的是 ASP.Net MVC,难道没有一个助手可以直接将文件流式传输到浏览器吗?因为我发现了很多将二进制数据返回到浏览器的示例,但是现在,我发现的所有示例基本上都类似于Stream.ToArray() 来填充字节数组并将其返回给浏览器。我发现我可以返回一个FileStreamResult,它可以接受参数Stream。这是正确的方向吗?

(我目前不关心上传大文件,因为它们是由数据库中的重型客户端插入的)

编辑

(对不起,脏代码,只是这里没有 50 种不同的方法。 我又做了一些尝试,但由于分离的部分(我们在哪里生成图层以及在哪里使用它),我目前被“读取”部分卡住了:

        SqlConnection conn = GetConnection();
        conn.Open();
        SqlCommand cmd = new SqlCommand(_selectMetaDataRequest, conn);
        cmd.Parameters.Add(_idFile, SqlDbType.Int).Value = idFile;
        SqlDataReader rdr = cmd.ExecuteReader();
        rdr.Read();
        string serverPath = rdr.GetSqlString(0).Value;
        byte[] serverTxn = rdr.GetSqlBinary(1).Value;
        rdr.Close();
        return new SqlFileStream(serverPath, serverTxn, FileAccess.Read);

但我在rdr.GetSqlBinary(1).Value 遇到异常,因为 GET_FILESTREAM_TRANSACTION_CONTEXT 返回 null。我发现here 这是由于缺少交易。

我尝试使用“TransactionScope”+它的.Complete(); 调用。不会改变任何东西。

我尝试像上一个链接中显示的那样进行 BEGIN TRANSACTION:

        SqlConnection connection = GetConnection();
        connection.Open();
        SqlCommand cmd = new SqlCommand();
        cmd.CommandText = "BEGIN TRANSACTION";
        cmd.CommandType = CommandType.Text;
        cmd.Connection = connection;
        cmd.ExecuteNonQuery();

        cmd = new SqlCommand(_selectMetaDataRequest, connection);
        cmd.Parameters.Add(_idFile, SqlDbType.Int).Value = idFile;
        SqlDataReader rdr = cmd.ExecuteReader();
        rdr.Read();
        string serverPath = rdr.GetSqlString(0).Value;
        byte[] serverTxn = rdr.GetSqlBinary(1).Value;
        rdr.Close();
        SqlFileStream sqlFileStream = new SqlFileStream(serverPath, serverTxn, FileAccess.Read);
      cmd = new SqlCommand();
        cmd.CommandText = "COMMIT TRANSACTION";
        cmd.CommandType = CommandType.Text;
        cmd.Connection = connection;
        cmd.ExecuteNonQuery();

但它在第一个“ExecuteNonQuery”时崩溃,但 "A transaction that was started in a MARS batch is still active at the end of the batch. The transaction is rolled back." 除外,但这是第一个执行的查询!

【问题讨论】:

  • 我会说这是一项更适合文件系统的任务,因为您正在处理文件。假设站点/数据库部署在 Windows 机器上,可以查看的一种替代方法是事务性 NTFS。它甚至应该使用 DTM 与其他事务集成。诚然,那里有一些问题(例如,糟糕的文件共享支持)。 +1 不使用 byte[] :)

标签: c# asp.net asp.net-mvc stream filestream


【解决方案1】:

让我们举个例子。我们可以从定义一个描述我们愿意执行的操作的合约开始:

public interface IPhotosRepository
{
    void GetPhoto(int photoId, Stream output);
}

我们稍后会看到实现。

现在我们可以定义一个自定义操作结果:

public class PhotoResult : FileResult
{
    private readonly Action<int, Stream> _fetchPhoto;
    private readonly int _photoId;
    public PhotoResult(int photoId, Action<int, Stream> fetchPhoto, string contentType): base(contentType)
    {
        _photoId = photoId;
        _fetchPhoto = fetchPhoto;
    }

    protected override void WriteFile(HttpResponseBase response)
    {
        _fetchPhoto(_photoId, response.OutputStream);
    }
}

然后是一个控制器,它允许我们显示照片:

public class HomeController : Controller
{
    private readonly IPhotosRepository _repository;

    public HomeController(IPhotosRepository repository)
    {
        _repository = repository;
    }

    public ActionResult Index()
    {
        return View();
    }

    public ActionResult Photo(int photoId)
    {
        return new PhotoResult(photoId, _repository.GetPhoto, "image/jpg");
    }
}

以及相应的视图,我们将在其中使用Photo 操作在&lt;img&gt; 标记中显示照片:

<img src="@Url.Action("photo", new { photoid = 123 })" alt="" />

现在最后一部分当然是存储库的实现,看起来可能类似于:

public class PhotosRepositorySql : IPhotosRepository
{
    private readonly string _connectionString;
    public PhotosRepositorySql(string connectionString)
    {
        _connectionString = connectionString;
    }

    public void GetPhoto(int photoId, Stream output)
    {
        using (var ts = new TransactionScope())
        using (var conn = new SqlConnection(_connectionString))
        using (var cmd = conn.CreateCommand())
        {
            conn.Open();
            cmd.CommandText =
            @"
                SELECT 
                    Photo.PathName() as path, 
                    GET_FILESTREAM_TRANSACTION_CONTEXT() as txnToken 
                FROM 
                    PhotoAlbum 
                WHERE 
                    PhotoId = @PhotoId
            ";
            cmd.Parameters.AddWithValue("@PhotoId", photoId);
            using (var reader = cmd.ExecuteReader())
            {
                if (reader.Read())
                {
                    var path = reader.GetString(reader.GetOrdinal("path"));
                    var txnToken = reader.GetSqlBinary(reader.GetOrdinal("txnToken")).Value;
                    using (var stream = new SqlFileStream(path, txnToken, FileAccess.Read))
                    {
                        stream.CopyTo(output);
                    }
                }
            }
            ts.Complete();
        }
    }    
}

现在剩下的就是指示您最喜欢的 DI 框架使用 PhotosRepositorySql

这种技术让您可以高效地处理任意大文件,因为它永远不会将整个流加载到内存中。

【讨论】:

  • 哦!使用自定义 HTTP 处理程序的好主意。在 Action<...> 代表之前我不知道,这也是个好主意!我马上去试试!
  • @J4N,我没有使用自定义 HTTP 处理程序。我正在使用自定义操作结果。
  • 是的,请原谅我的困惑。我完成了测试,它工作正常!我刚刚将“文件名”添加到操作结果中。非常感谢你,你拯救了我的一天!
【解决方案2】:

查看此答案以获取成功下载最大 4GB 文件的示例:https://stackoverflow.com/a/3363015/234415

关于使用 Filestream 需要记住的几个有趣点:

  • 文件不计入大小限制的一部分(因此它与SQLEXPRESS 很好地配合使用,SQL 2008 R2 版本的限制为 10GB。因此您可以拥有 10GB 的数据和 TB 的文件,全部免费,这很酷。
  • 您必须使用集成安全 (MSDN),它不支持 SQL 登录。如果您想使用共享主机,这很烦人!

【讨论】:

  • 感谢您的回复,但这并不能真正帮助我对应用程序进行分层。对于其他限制,我们顺便会有一个 SQL Server 标准版
【解决方案3】:

SQL 数据库在处理大文件时非常糟糕,而且还存在注册表大小限制。 我建议使用 NoSQL 数据库(如 MongoDB)来存储这些巨大的文件。你可以使用两个数据库,(SQL 用于普通数据,NoSQL 用于文件)没有问题。

我知道这不是您想要的答案,但却是获得良好表现的最佳方式。

【讨论】:

  • 他在询问专门为此目的设计的 SQL Server 2008 FILESREAM 数据类型。
  • 对不起,Sql Server 2008 是一个约束,我不能使用任何其他数据库类型。而 Filestream 旨在存储大文件,它只管理指向真实文件的“指针”
猜你喜欢
  • 2012-03-28
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-09-12
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多