Оба Джон и Марк имеют хорошие ответы.Я предпочитаю Марк, так как у вас есть возможность последовательного чтения слоя 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 # вы сначала пишете файлна диск и используйте этот идентификатор для обновления базы данных:
/// <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);
}
Вам также следует изучить Транзакционная NTFS , чтобы обеспечить синхронизацию вашей БД и файловой системы.Неэффективность хранения BLOB в MsSQL является одной из причин, по которой Microsoft внедрила TxF - поэтому следуйте их советам - не храните BLOB / файлы в SQL .
Примечание: Наличие вложенных папок (ba[0 through 2]
) важно как для производительности, так и для ограничений файловой системы - одна папка не может содержать действительно большое количество файлов.