我有一个ASP.NET MVC应用程序(我也使用jQuery)。
我允许用户使用HttpPostedFileBase
类上传文件。
然后,我使用类型InputStream
的{{1}}属性将文件流保存到我拥有的某个数据库,在那里我首先对我的对象进行serislize。 Stream
是可以分割的,所以这里没有问题。
问题在用户没有上传文件时开始,我想在这种情况下使用我在某处的另一个默认文件。
在这种情况下,一切都必须与第一种情况类似,所以最终我的数据库中会有一个Stream
。所以我必须实例Stream
并存储它。 Stream
是抽象的,所以我不能实例Stream
。相反,我使用了Stream
,它继承了FileStream
。问题是Stream
不可分解,所以我遇到了问题。
我该如何解决?是否有另一个我可以使用的流继承FileStream
并且可序列化?
答案 0 :(得分:4)
不要序列化流存储;流是“软管”,而不是“桶”。相反,读取流并存储二进制数据(大多数数据库将具有二进制数据的数据类型,例如varbinary(max)
)。如果这是对象模型的一部分,我倾向于拥有byte[]
属性(具有有意义的名称);这将作为模型的一部分轻松序列化。只需阅读流即可创建byte[]
;任务完成。例如:
public static byte[] ReadToEnd(this Stream s) {
using(var ms = new MemoryStream()) {
s.CopyTo(ms);
return ms.ToArray();
}
}
答案 1 :(得分:0)
Jon和Marc都有很好的答案。我更喜欢Marc's,因为您可以选择从流中顺序读取SQL层,而不是缓冲内存中的所有数据(这可能会导致OutOfMemoryException
)。
但是,在基础级别,您正在接近这个错误。一般来说,SQL引擎真的不喜欢处理大的列值 - 它们的效率非常低。您可以使用另一个“数据库” - 您的文件系统。
通常,您将按如下方式定义数据库结构:
CREATE TABLE [dbo].[Files]
(
[ID] INT IDENTITY(1,1) PRIMARY KEY NOT NULL,
[Name] NVARCHAR(255) NOT NULL,
[Storage] UNIQUEIDENTIFIER NOT NULL
);
在C#land中,您首先将文件写入磁盘,然后使用该标识符更新数据库:
/// <summary>
/// Writes a stream to a file and returns a <see cref="Guid"/> that
/// can be used to retrieve it again.
/// </summary>
/// <param name="incomingFile">The incoming file.</param>
/// <returns>The <see cref="Guid"/> that should be used to identify the file.</returns>
public static Guid WriteFile(Stream incomingFile)
{
var path = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.CommonApplicationData), "MyApplication");
path = Path.Combine(path, "BinaryData");
var guid = Guid.NewGuid();
var ba = guid.ToByteArray();
// Create the path for the GUID.
path = Path.Combine(ba[0].ToString("x2"));
path = Path.Combine(ba[1].ToString("x2"));
path = Path.Combine(ba[2].ToString("x2"));
Directory.CreateDirectory(path); // Always succeeds, even if the directory already exists.
path = Path.Combine(guid.ToString() + ".dat");
using (var fs = new FileStream(path, FileMode.Create, FileAccess.Write, FileShare.None))
{
var buffer = new byte[Environment.SystemPageSize];
var length = 0;
while ((length = incomingFile.Read(buffer, 0, buffer.Length)) != 0)
fs.Write(buffer, 0, buffer.Length);
}
return guid;
}
/// <summary>
/// Deletes a file created by <see cref="WriteFile"/>.
/// </summary>
/// <param name="guid">The original <see cref="Guid"/> that was returned by <see cref="WriteFile"/>.</param>
public static void DeleteFile(Guid guid)
{
var path = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.CommonApplicationData), "MyApplication");
path = Path.Combine(path, "BinaryData");
var ba = guid.ToByteArray();
// Create the path for the GUID.
path = Path.Combine(ba[0].ToString("x2"));
path = Path.Combine(ba[1].ToString("x2"));
path = Path.Combine(ba[2].ToString("x2"));
path = Path.Combine(guid.ToString() + ".dat");
if (File.Exists(path))
File.Delete(path);
}
/// <summary>
/// Reads the a file that was created by <see cref="WriteFile"/>.
/// </summary>
/// <param name="guid">The original <see cref="Guid"/> that was returned by <see cref="WriteFile"/>.</param>
/// <returns>The stream that can be used to read the file.</returns>
public static Stream ReadFile(Guid guid)
{
var path = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.CommonApplicationData), "MyApplication");
path = Path.Combine(path, "BinaryData");
var ba = guid.ToByteArray();
// Create the path for the GUID.
path = Path.Combine(ba[0].ToString("x2"));
path = Path.Combine(ba[1].ToString("x2"));
path = Path.Combine(ba[2].ToString("x2"));
path = Path.Combine(guid.ToString() + ".dat");
return new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read);
}
您还应调查Transactional NTFS以确保您的数据库和文件系统保持同步。在MsSQL中存储BLOB的效率低是Microsoft实现TxF的原因之一 - 所以你应该听取他们的建议 - 不要在SQL中存储BLOB / Files 。
备注:拥有嵌套文件夹(ba[0 through 2]
)对性能和文件系统限制都很重要 - 单个文件夹无法容纳大量文件。