对于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/
我还有一些其他问题,希望你能帮助我/指出我的好方向:
Stream.ToArray()
那样填充一个字节数组并将其返回给浏览器。我发现我可以返回一个FileStreamResult
,它可以接受参数a 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."
但它是第一个执行的查询!
答案 0 :(得分:7)
让我们举个例子。我们可以从定义一个描述我们愿意执行的操作的合同开始:
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");
}
}
以及我们将使用<img>
操作在Photo
代码中显示照片的相应视图:
<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
。
此技术允许您有效地处理任意大文件,因为它从不将整个流加载到内存中。
答案 1 :(得分:0)
请查看此答案,以获取成功下载最高4GB文件的示例:https://stackoverflow.com/a/3363015/234415
关于使用Filestream
:
SQLEXPRESS
很好地配合,SQL 2008 R2
版本的限制为10GB。因此,您可以拥有10GB的数据和数TB的文件,一切都是免费的,这很酷。答案 2 :(得分:-1)
SQL数据库对于大文件非常糟糕,并且还存在注册表大小限制。 我建议使用NoSQL数据库(如MongoDB)来存储那些巨大的文件。你可以使用两个数据库(用于普通数据的SQL,用于文件的NoSQL)没有问题。
我知道这不是你想要的答案,但却是获得良好表现的最佳方式。