Перейти к содержанию

Перенос контента файлов из БД на диск

Перенос контента файлов из БД на диск

Если контент файла хранится в БД, но доступ к ним может быть несколько медленнее, чем к файлам на файловой системе в силу особенностей СУБД. Если для некоторых из файлов требуется перенести контент на диск, то можно написать универсальный запрос к сервису карточек и использовать его из кода или по нажатию кнопки.

Это расширение можно не реализовывать самостоятельно, т.к. оно уже есть в типовом решении (Tessa.Extensions.Default.Server.Cards и Tessa.Extensions.Default.Shared.Cards). Для пояснения работы расширения его код приведён ниже.

Код расширения пишется на сервере в сборке Tessa.Extensions.Server:

public sealed class MoveFileRequestExtension : CardRequestExtension { #region Fields

private readonly ICardContentStrategy contentStrategy; private readonly ITransactionStrategy transactionStrategy; private readonly ICardFileVersionStrategy versionStrategy; private readonly ICardFileVersionStrategy deletedVersionStrategy;

#endregion

#region Constructors

public MoveFileRequestExtension( ICardContentStrategy contentStrategy, ITransactionStrategy transactionStrategy, ICardFileVersionStrategy versionStrategy, [Dependency(CardFileVersionStrategyNames.Deleted)] ICardFileVersionStrategy deletedVersionStrategy) { this.contentStrategy = NotNullOrThrow(contentStrategy); this.transactionStrategy = NotNullOrThrow(transactionStrategy); this.versionStrategy = NotNullOrThrow(versionStrategy); this.deletedVersionStrategy = NotNullOrThrow(deletedVersionStrategy); }

#endregion

#region Base Overrides

public override async Task AfterRequest(ICardRequestExtensionContext context) { if (!context.RequestIsSuccessful || !context.ValidationResult.IsSuccessful()) { return; }

if (!context.Session.User.IsAdministrator()) { ValidationSequence .Begin(context.ValidationResult) .SetObjectName(this) .Error(ValidationKeys.UserIsNotAdmin) .End();

return; }

if (context.Request.CardID is not { } requestCardID) { ValidationSequence .Begin(context.ValidationResult) .SetObjectName(this) .Error(CardValidationKeys.UnspecifiedCardID) .End();

return; }

if (context.Request.Info.TryGet<int?>(DefaultExtensionHelper.SourceIDKey) is not { } sourceID) { context.ValidationResult.AddError(this, $"Parameter request.Info[\"{DefaultExtensionHelper.SourceIDKey}\"] is not specified." + " Please, use method DefaultExtensionHelper.SetSourceID(...)");

return; }

CardFileSourceType targetSourceType = new(sourceID); Guid[]? requestFileIDs = context.Request.FileID is { } fileID ? new[] { fileID } : null;

// Получение контекстов для существующих файлов в карточке var contentContexts = await CardComponentHelper.GetContentContextsAsync( requestCardID, context.ValidationResult, this.versionStrategy, requestFileIDs, context.CancellationToken);

if (!context.ValidationResult.IsSuccessful()) { return; }

List<CardContentContext> contentsToMove = contentContexts .Where(c => c.Source != targetSourceType) .ToList();

// Получение контекстов для удалённых файлов в карточке List<CardContentContext> deletedContentContexts = await CardComponentHelper.GetContentContextsAsync( requestCardID, context.ValidationResult, this.deletedVersionStrategy, requestFileIDs, context.CancellationToken);

if (!context.ValidationResult.IsSuccessful()) { return; }

List<CardContentContext> deletedContentsToMove = deletedContentContexts .Where(c => c.Source != targetSourceType) .ToList();

// Выход, если ни одного контента не нашлось if (!contentsToMove.Any() && !deletedContentsToMove.Any()) { return; }

// Группировка контентов по таблицам, к которым они относятся Dictionary<string, IEnumerable<CardContentContext>>? groupedContentsToMove = new(2) { ["FileVersions"] = contentsToMove, ["DeletedFileVersions"] = deletedContentContexts };

await using IDbScopeInstance _ = context.DbScope!.Create(); DbManager db = context.DbScope!.Db; IQueryBuilderFactory builderFactory = context.DbScope!.BuilderFactory;

// Транзакция используется только в том случае, если она отсутствовала на момент запуска bool useTransaction = db.DataConnection.Transaction is null;

if (useTransaction) { await db.ExecuteSetXactAbortOnAsync(context.CancellationToken); }

foreach ((string tableName, IEnumerable<CardContentContext> contents) in groupedContentsToMove) { foreach (var content in contents) { CardContentContext newContent = new( content.CardID, content.FileID, content.VersionRowID, targetSourceType, content.ValidationResult);

await using (Stream contentStream = await this.contentStrategy.GetAsync(content, context.CancellationToken)) { await this.contentStrategy.StoreAsync(newContent, contentStream, context.CancellationToken); }

async Task ExecuteAsync(CancellationToken cancellationToken) { await this.contentStrategy.DeleteAsync(content, cancellationToken);

await db .SetCommand( builderFactory .Update(tableName).C("SourceID").Assign().P("SourceID") .Where().C("RowID").Equals().P("RowID") .Build(), db.Parameter("RowID", content.VersionRowID, DataType.Guid), db.Parameter("SourceID", (short) targetSourceType, DataType.Int16)) .LogCommand() .ExecuteNonQueryAsync(cancellationToken); }

if (useTransaction) { await this.transactionStrategy.ExecuteInTransactionAsync( context.ValidationResult, p => ExecuteAsync(p.CancellationToken), context.CancellationToken); } else { await ExecuteAsync(CancellationToken.None); } } } }

#endregion }

Регистрация расширения:

[Registrator] public sealed class Registrator : RegistratorBase { public override void RegisterUnity() => this.UnityContainer.RegisterSingleton<MoveFileRequestExtension>();

public override void RegisterExtensions(IExtensionContainer extensionContainer) => extensionContainer .RegisterExtension<ICardRequestExtension, MoveFileRequestExtension>(x => x .WithOrder(ExtensionStage.BeforePlatform, 1) .WithUnity(this.UnityContainer) .WhenRequestTypes(DefaultRequestTypes.MoveFiles)); }

В общей для клиента и сервера сборке Tessa.Extensions.Shared нужен хэлпер с идентификатором запроса:

public static class CardExtensionRequestTypes { public static readonly Guid MoveFilesToDisk = new Guid(0x64565091, 0x2c00, 0x4ecd, 0xb6, 0x41, 0x1c, 0x8c, 0x8f, 0x91, 0x2c, 0x58); }

Чтобы переместить на диск все файлы карточки со всеми версиями, на клиенте можно сделать следующий вызов:

Guid cardID = ... ; // идентификатор карточки var request = new CardRequest { CardID = cardID, RequestType = CardExtensionRequestTypes.MoveFilesToDisk }; CardResponse response = cardRepository.Request(request);

ValidationResult result = response.ValidationResult.Build(); if (!result.IsSuccessful) { CardUIHelper.ShowResult(result); }

Чтобы переместить все версии конкретного файла, не затрагивая остальные файлы карточки, вызов с клиента будет выглядеть так:

Guid cardID = ... ; // идентификатор карточки Guid fileID = ... ; // идентификатор файла var request = new CardRequest { CardID = cardID, FileID = fileID, RequestType = CardExtensionRequestTypes.MoveFilesToDisk }; CardResponse response = cardRepository.Request(request);

ValidationResult result = response.ValidationResult.Build(); if (!result.IsSuccessful) { CardUIHelper.ShowResult(result); }

Back to top