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

Создание карточки-сателлита

Создание карточки-сателлита

Карточка-сателлит - это специальная карточка, которая прозрачно для пользователя сопровождает основную карточку, т.е. пользователь явно не создаёт и не удаляет сателлит, и даже не подозревает о его существовании. Например, карточка с настройками сотрудника, которая позволяет соотнести с каждым сотрудником его настройки рабочего места, при этом не изменяя внешний вид и структуру основной карточки сотрудника.

Сателлит даёт следующие преимущества:

  • Можно изменять связанные с карточкой данные, не изменяя версию карточки.

  • Можно выполнять параллельное изменение данных (например, при параллельном согласовании), не блокируя основную карточку.

  • Можно связать данные с типом карточки, не изменяя его структуру. Это позволяет иметь нестандартное API (такое, как ролевая модель), которое продолжит корректно взаимодействовать с основной карточкой.

  • Неизменность структуры основной карточки также обеспечивает бинарную совместимость этой карточки. Т.е. ранее удалённые или экспортированные карточки без сателлита по-прежнему можно будет восстановить.

Для карточки-сателлита необходимо обеспечить:

  • Создание по первому требованию со стороны расширений. Это может быть старт бизнес-процесса или явный запрос настроек сотрудника при старте приложения.

  • Загрузка по идентификатору основной карточки. Писатели расширений должны легко получить данные сателлита как на клиенте, так и на сервере, зная только идентификатор основной карточки.

  • Автоматическое удаление при удалении основной карточки.

  • Автоматический экспорт при экспорте основной карточки.

  • Автоматический импорт при импорте основной карточки.

  • Восстановление из корзины вместе с восстановлением основной карточки.

Сателлит с настройками сотрудника

Рассмотрим подробнее процесс создания карточки-сателлита PersonalRoleSatellite с настройками сотрудника по его рабочим местам.

Структура карточки:

PersonalRoleSatellite (entry) MainCardID : Guid // ссылка на карточку сотрудника WorkplaceExtensions: byte[] // сериализованные настройки рабочего места или null, если настройки пока не заданы

Напишем некоторые полезные хэлперы в классе RoleExtensionHelper:

  • GetPersonalRoleSatelliteID - определяет идентификатор карточки-сателлита по идентификатору сотрудника, выполняя запрос в базе.

    public static Guid? GetPersonalRoleSatelliteID(IDbScope dbScope, Guid personalRoleID) { DbManager db = dbScope.Db;

    return db .SetCommand( "SELECT [ID]" + " FROM [dbo].[PersonalRoleSatellite] with(nolock)" + " WHERE [MainCardID] = @MainCardID", db.Parameter("@MainCardID", SqlHelper.NotNull(personalRoleID))) .LogCommand() .ExecuteScalar<Guid?>(); }

  • SetPersonalRoleSatellite - “зашивает” пакет карточки-сателлита satellite в Info карточки сотрудника personalRole. Метод нужен для того, чтобы сериализовать сателлит вместе с данными основной карточки. Таким образом, сателлит можно будет восстановить вместе с карточкой сотрудника из сериализованного состояния, в котором они пребывают в корзине. Сателлит доступен как хеш-таблица (метод GetStorage()) по строковому ключу PersonalRoleSatelliteKey, префикс которого SystemKeyPrefix устанавливается только для системных карточек. Для любых других карточек ключ следует задавать без префикса как “MySatellite”.

    private const string PersonalRoleSatelliteKey = CardHelper.SystemKeyPrefix + "satellite";

    public static void SetPersonalRoleSatellite(Card personalRole, Card satellite) { personalRole.Info[PersonalRoleSatelliteKey] = satellite != null ? satellite.GetStorage() : null;

    ThreadCache<Card, Card> .Reset(personalRole); }

  • SatelliteWasNotFound - возвращает признак того, что в Info карточки сотрудника personalRole методом SetPersonalRoleSatellite сателлит был явно установлен как null. Таким образом, можно будет отличить состояния “поиск сателлита был выполнен, и сателлит не найден” (установлен в null) от “поиск сателлита ещё не был выполнен” (ключ отсутствует в хеш-таблице).

    public static bool SatelliteWasNotFound(Card personalRole) { object value; return personalRole.Info.TryGetValue(PersonalRoleSatelliteKey, out value) && value == null; }

  • TryGetPersonalRoleSatellite - возвращает карточку сателлита, ранее установленную методом SetPersonalRoleSatellite в Info заданной карточки сотрудника personalRole. Метод возвращает null, если сателлит не был установлен или же был установлен как null (см. метод SatelliteWasNotFound). Отметим, что возвращается объект Card, а в Info задана хеш-таблица Dictionary<string, object>. Чтобы объект Card по возможности создавался один раз для хеш-таблицы, используется ThreadCache, которому передаётся фабрика, создающая объект Card по хеш-таблице, причём фабрика будет вызвана только в том случае, если этот метод ещё не вызывается для объекта personalRole, или же он был вызван для карточки, отличной от personalRole.

    public static Card TryGetPersonalRoleSatellite(Card personalRole) { Dictionary<string, object> info = personalRole.TryGetInfo(); if (info == null) { return null; }

    return ThreadCache<Card, Card> .Get(personalRole, card => { object value; if (!card.Info.TryGetValue(PersonalRoleSatelliteKey, out value)) { return null; }

    var dictionary = value as Dictionary<string, object>; return dictionary != null ? new Card(dictionary) : null; }); }

Вспомогательные методы можно упростить, если использовать API из Tessa.Cards.Extensions.Templates:

public static Guid? GetPersonalRoleSatelliteID(IDbScope dbScope, Guid personalRoleID) { return CardSatelliteHelper.TryGetSatelliteID(dbScope, personalRoleID, "PersonalRoleSatellite"); }

public static void SetPersonalRoleSatellite(Card personalRole, Card satellite) { CardSatelliteHelper.SetSatellite(personalRole, satellite); }

public static bool SatelliteWasNotFound(Card personalRole) { return CardSatelliteHelper.SatelliteCardWasNotFound(personalRole); }

public static Card TryGetPersonalRoleSatellite(Card personalRole) { return CardSatelliteHelper.TryGetSatelliteCard(personalRole); }

Расширение на создание и загрузку сателлита

Все расширения на сателлит делаем серверными, чтобы с сателлитом можно было работать как на клиенте, так и на сервере.

Пусть сателлит с настройками сотрудника автоматически создаётся в момент, когда эти настройки запросили через Get, если ранее он не был создан. Поэтому расширение на загрузку сателлита по ID основной карточки также будет и расширением на создание сателлита при его отсутствии.

Если сателлит отсутствует, то необходимо обеспечить его создание не более, чем в одном экземпляре даже в том случае, если одновременно произошло несколько запросов на сателлит, каждый из которых попытается его создать. Алгоритм будет такой:

  1. При наличии сателлита подменяем ID загружаемой карточки на ID сателлита, чтобы стандартное API штатным образом прочитало сателлит.

  2. При отсутствии сателлита ставим блокировку без проверки версии на запись карточки сотрудника, для которого нужен сателлит. Одновременно захватить блокировку может не более одного процесса загрузки.

  3. Внутри блокировки ещё раз проверяем наличие сателлита, и если он есть - также поменяем ID. Это может произойти, если блокировка на запись ждала другой процесс, который уже создал сателлит параллельно с ожиданием, т.е. позже проверки из п.1.

  4. Создаём пустой пакет карточки-сателлита, в котором заполняем поле MainCardID, а остальные поля оставляем по умолчанию.

  5. Охраняем сателлит (т.е. создаём его в базе) вместе с расширениями, а потом уже подменяем ID, чтобы сателлит был загружен штатным образом.

Класс расширения:

public sealed class PersonalRoleSatelliteGetExtension : CardGetExtension { private readonly ICardRepository cardRepository;

private readonly ICardTransactionStrategy cardTransactionStrategy;

private readonly IDbScope dbScope;

public PersonalRoleSatelliteGetExtension( ICardRepository cardRepository, ICardTransactionStrategy cardTransactionStrategy, IDbScope dbScope) { this.cardRepository = cardRepository; this.cardTransactionStrategy = cardTransactionStrategy; this.dbScope = dbScope; }

public override void BeforeRequest(ICardGetExtensionContext context) { Guid? cardID = context.Request.CardID; if (!cardID.HasValue) { return; }

// dbScope гарантирует наличие не более одного соединения с базой данных using (this.dbScope.Create()) { Guid? satelliteID = RoleExtensionHelper.TryGetPersonalRoleSatelliteID(this.dbScope, cardID.Value); if (satelliteID.HasValue) { context.Request.CardID = satelliteID; return; }

// блокируем карточку сотрудника, чтобы гарантировать то, что не будет создано более одной карточки сателлита this.cardTransactionStrategy.ExecuteInWriterLock( cardID.Value, CardComponentHelper.DoNotCheckVersion, context.ValidationResult, p => { // проверяем наличие сателлита внутри блокировки, т.к. сателлит уже мог быть создан // одновременным запросом, пока ожидалась блокировка Guid? transactionSatelliteID = RoleExtensionHelper.TryGetPersonalRoleSatelliteID(this.dbScope, cardID.Value); if (transactionSatelliteID.HasValue) { context.Request.CardID = transactionSatelliteID; return; }

// создаём пакет карточки сателлита CardNewResponse newResponse = this.cardRepository.New(new CardNewRequest { CardTypeID = RoleHelper.PersonalRoleSatelliteTypeID, NewMode = CardNewMode.Valid });

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

// заполняем пакет карточки Card card = newResponse.Card; Guid newSatelliteID = Guid.NewGuid(); card.ID = newSatelliteID; card.Sections["PersonalRoleSatellite"].RawFields["MainCardID"] = cardID.Value;

// сохраняем сателлит в базу CardStoreResponse storeResponse = this.cardRepository.Store(new CardStoreRequest { Card = card });

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

context.Request.CardID = newSatelliteID; }); } } }

Расширение можно упростить, если использовать шаблон из Tessa.Cards.Extensions.Templates:

public sealed class PersonalRoleSatelliteGetExtension : CardSatelliteGetExtension { public PersonalRoleSatelliteGetExtension( ICardRepository cardRepository, ICardTransactionStrategy cardTransactionStrategy, IDbScope dbScope) : base(cardRepository, cardTransactionStrategy) { }

protected override Guid SatelliteTypeID { get { return RoleHelper.PersonalRoleSatelliteTypeID; } }

protected override Guid? TryGetSatelliteID(IDbScope dbScope, Guid mainCardID) { return RoleExtensionHelper.TryGetPersonalRoleSatelliteID(dbScope, mainCardID); }

protected override void SetSatelliteMainCardID(Card satellite, Guid mainCardID) { satellite.Sections["PersonalRoleSatellite"].RawFields["MainCardID"] = mainCardID; } }

Регистрируем расширение, указывая идентификатор типа карточки сателлита RoleHelper.PersonalRoleSatelliteTypeID:

extensions .RegisterExtension<ICardGetExtension, PersonalRoleSatelliteGetExtension>(x => x .WithOrder(ExtensionStage.AfterPlatform) .WithUnity(unityContainer .RegisterType<PersonalRoleSatelliteGetExtension>(new ContainerControlledLifetimeManager())) .WhenCardTypes(RoleHelper.PersonalRoleSatelliteTypeID));

Использование расширения на сервере:

using Tessa.Cards; using Tessa.Platform.Storage; using Tessa.Roles;

ICardRepository cardRepository = unityContainer.Resolve<ICardRepository>(); Guid userID = ... ; // идентификатор сотрудника, для которого нужен сателлит

CardGetResponse response = cardRepository.Get(new CardGetRequest { CardID = userID, CardTypeID = RoleHelper.PersonalRoleSatelliteTypeID // идентификатор типа сателлита // по умолчанию не сжимаем карточку, т.к. до клиента она не дойдёт };

if (!response.ValidationResult.IsSuccessful()) { logger.Error("При получении настроек пользователя возникли ошибки: {0}", response.ValidationResult.Build().ToString()); return; }

Card satellite = getResponse.Card; byte[] currentSettings = satellite.Sections["PersonalRoleSatellite"].Fields.Get<byte[]>("WorkplaceExtensions"); // настройки currentSettings могут быть равны null, если их ещё никто не установил

Использование расширения на клиенте:

using Tessa.Cards; using Tessa.Platform.Storage; using Tessa.Roles; using Tessa.UI.Cards;

ICardRepository cardRepository = unityContainer.Resolve<ICardRepository>(); Guid userID = ... ; // идентификатор сотрудника, для которого нужен сателлит

CardGetResponse response = cardRepository.Get(new CardGetRequest { CardID = userID, CardTypeID = RoleHelper.PersonalRoleSatelliteTypeID, CompressionMode = CardCompressionMode.Full // между клиентом и сервером может выполняться компрессия сателлита штатным образом };

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

Card satellite = getResponse.Card; byte[] currentSettings = satellite.Sections["PersonalRoleSatellite"].Fields.Get<byte[]>("WorkplaceExtensions"); // настройки currentSettings могут быть равны null, если их ещё никто не установил

Восстановление карточки после удаления

Для типа карточки сотрудника стоит флаг “Delete with backup”. Это означает, что по умолчанию (из UI) сотрудник будет удаляться с возможностью восстановления, при этом в сериализованном виде карточка сотрудника помещается в “корзину” - в карточку типа Deleted. Позднее сотрудник может быть восстановлен или окончательно удалён из корзины. Однако, карточка сотрудника может быть удалена сразу, минуя “корзину”, если метод удаления будет вызван из расширения с установленным в запросе свойством DeletionMode = CardDeletionMode.WithoutBackup. Это приводит к 3 возможным сценариям:

  1. Карточка удаляется сразу без возможности восстановления.

  2. Карточка удаляется, для неё создаётся карточка Deleted с сериализованными данными, после чего администратор её восстанавливает.

  3. Карточка удаляется, для неё создаётся карточка Deleted с сериализованными данными, после чего администратор или плагин Chronos её удаляют.

Процесс удаления карточки с восстановлением:

  1. При удалении карточки внутри транзакции срабатывает платформенное расширение, которое загружает удаляемую карточку (сотрудника) со всеми расширениями и методом CardGetMethod.Backup.

  2. Далее платформенное расширение создаёт карточку Deleted в транзакции на удаление.

  3. Стандартное API удаляет карточку Deleted.

  4. Окончательное удаление карточки Deleted - это обычное удаление такой карточки, причём в контексте расширений можно получить десериализованную удаляемую карточку, чтобы определить её тип (сотрудник) и свойства.

  5. Восстановление карточки из Deleted - это удаление карточки Deleted с точно такой же восстанавливаемой карточкой в контексте расширений, но и с одновременным созданием сериализованной карточки через Store методом CardStoreMethod.Restore.

Карточка-сателлит должна быть “невидимой” как для пользователя (и стандартного редактора карточек ICardEditorModel), так и для писателей расширений, использующих стандартный API для взаимодействия с основной карточкой. Поэтому каждый из этих сценариев необходимо рассмотреть и написать расширения, обеспечивающие прозрачную работу сателлита.

  1. При загрузке карточки сотрудника в режиме CardGetMethod.Backup в Info должна загружаться и сериализоваться карточка-сателлит.

  2. При удалении карточки (с восстановлением или без) карточка-сателлит должна быть удалена.

  3. При восстановлении карточки сотрудника (создании методом CardStoreMethod.Restore) при наличии сериализованного сателлита он также должен восстановиться.

Расширения при удалении основной карточки будут отрабатывать таким образом:

  1. Платформенное расширение на удаление

    1. Загрузка карточки сотрудника с расширениями

      1. Штатная загрузка

      2. Наше расширение на загрузку, записывающее сателлит в Info

    2. Загруженная карточка записывается в контекст расширения

  2. Наше расширение на удаление сотрудника, получающее карточку из контекста вместе с сателлитом

Расширение на загрузку сателлита при помещении карточки сотрудника в корзину

Это обычное расширение, которое после успешной загрузки основной карточки методом CardGetMethod.Backup загружает карточку-сателлит в AfterRequest, после чего записывает её в Info основной карточки. Если карточка-сателлит не найдена, то в Info записывается null, поэтому наше расширение на удаление карточки позже корректно обработает эту ситуацию.

public sealed class PersonalRoleBackupExtension : CardGetExtension { private readonly ICardRepository cardRepository;

private readonly IDbScope dbScope;

public PersonalRoleBackupExtension(ICardRepository cardRepository, IDbScope dbScope) { this.cardRepository = cardRepository; this.dbScope = dbScope; }

public override void AfterRequest(ICardGetExtensionContext context) { if (!context.RequestIsSuccessful || !context.CardTypeIs(RoleHelper.PersonalRoleTypeID)) { return; }

using (this.dbScope.Create()) { Card card = context.Response.Card; Guid? satelliteID = RoleExtensionHelper.TryGetPersonalRoleSatelliteID(this.dbScope, card.ID); if (!satelliteID.HasValue) { RoleExtensionHelper.SetPersonalRoleSatellite(card, null); return; }

// карточку-сателлит требуется загрузить и сохранить в пакете удаляемой карточки CardGetResponse getResponse = this.cardRepository.Get(new CardGetRequest { CardID = satelliteID.Value, CardTypeID = RoleHelper.PersonalRoleSatelliteTypeID, Method = CardGetMethod.Backup, GetMode = CardGetMode.ReadOnly });

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

RoleExtensionHelper.SetPersonalRoleSatellite(card, getResponse.Card); } } }

Расширение с использованием шаблона:

public sealed class PersonalRoleBackupExtension : CardSatelliteBackupExtension { public PersonalRoleBackupExtension( ICardRepository cardRepository, IDbScope dbScope) : base(cardRepository) { }

protected override bool IsMainCardType(CardType cardType) { return cardType.ID == RoleHelper.PersonalRoleTypeID; }

protected override Guid SatelliteTypeID { get { return RoleHelper.PersonalRoleSatelliteTypeID; } }

protected override Guid? TryGetSatelliteID(IDbScope dbScope, Guid mainCardID) { return RoleExtensionHelper.TryGetPersonalRoleSatelliteID(dbScope, mainCardID); }

protected override void SetSatellite(Card mainCard, Card satellite) { RoleExtensionHelper.SetPersonalRoleSatellite(mainCard, satellite); } }

Регистрация расширения выполняется на карточку сотрудника:

extensionContainer .RegisterExtension<ICardGetExtension, PersonalRoleBackupExtension>(x => x .WithOrder(ExtensionStage.AfterPlatform) .WithUnity(unityContainer .RegisterType<PersonalRoleBackupExtension>(new ContainerControlledLifetimeManager())) .WhenMethod(CardGetMethod.Backup));

Расширение на удаление сотрудника с одновременным удалением сателлита

Это расширение выполняется после того, как карточка сотрудника вместе с сателлитом были загружены для сериализации. Поэтому, идентификатор сателлита может быть получен из этой сериализованной карточки.

public sealed class PersonalRoleDeleteExtension : CardDeleteExtension { private readonly ICardRepository extendedRepositoryWithoutTransaction;

public PersonalRoleDeleteExtension(ICardRepository extendedRepositoryWithoutTransaction) { this.extendedRepositoryWithoutTransaction = extendedRepositoryWithoutTransaction; }

public override void AfterBeginTransaction(ICardDeleteExtensionContext context) { Guid? satelliteID = null;

Card cardToDelete = context.TryGetCardToDelete(); if (cardToDelete != null) { // карточка удаляется с восстановлением if (RoleExtensionHelper.SatelliteWasNotFound(cardToDelete)) { // сателлит не найден, удалять его не требуется return; }

Card satelliteCard = RoleExtensionHelper.TryGetPersonalRoleSatellite(cardToDelete); if (satelliteCard != null) { satelliteID = satelliteCard.ID; }

// иначе расширение на поиск сателлита не было выполнено }

if (!satelliteID.HasValue) { // карточка сохраняется без восстановления или расширение на поиск сателлита не было выполнено

// CardID гарантированно не равен null, т.к. null не пропустил бы стандартный API, открывший транзакцию // ReSharper disable once PossibleInvalidOperationException Guid cardID = context.Request.CardID.Value;

satelliteID = RoleExtensionHelper.TryGetPersonalRoleSatelliteID(context.DbScope, cardID); if (!satelliteID.HasValue) { return; } }

CardDeleteResponse deleteResponse = this.extendedRepositoryWithoutTransaction.Delete(new CardDeleteRequest { CardID = satelliteID.Value, CardTypeID = RoleHelper.PersonalRoleSatelliteTypeID, DeletionMode = CardDeletionMode.WithoutBackup });

context.ValidationResult.Add(deleteResponse.ValidationResult); } }

Расширение с использованием шаблона:

public sealed class PersonalRoleDeleteExtension : CardSatelliteDeleteExtension { public PersonalRoleDeleteExtension(ICardRepository extendedRepositoryWithoutTransaction) : base(extendedRepositoryWithoutTransaction) { }

protected override Guid SatelliteTypeID { get { return RoleHelper.PersonalRoleSatelliteTypeID; } }

protected override Guid? TryGetSatelliteID(IDbScope dbScope, Guid mainCardID) { return RoleExtensionHelper.TryGetPersonalRoleSatelliteID(dbScope, mainCardID); }

protected override bool SatelliteCardWasNotFound(Card mainCard) { return RoleExtensionHelper.SatelliteWasNotFound(mainCard); }

protected override Card TryGetSatelliteCard(Card mainCard) { return RoleExtensionHelper.TryGetPersonalRoleSatellite(mainCard); } }

Удаление карточки-сателлита выполняется с расширениями, но без транзакции, т.к. само расширение выполняется в транзакции на удаление карточки сотрудника, что и показывается в регистрации расширения:

extensionContainer .RegisterExtension<ICardDeleteExtension, PersonalRoleDeleteExtension>(x => x .WithOrder(ExtensionStage.AfterPlatform) .WithUnity(unityContainer .RegisterType<PersonalRoleRestoreExtension>( new ContainerControlledLifetimeManager(), new InjectionConstructor( new ResolvedParameter<ICardRepository>(CardRepositoryNames.ExtendedWithoutTransaction))))) .WhenCardTypes(RoleHelper.PersonalRoleTypeID));

Расширение на восстановление сателлита при восстановлении карточки сотрудника

Это расширение на удаление карточки Deleted с установленным флагом RestoreMode. Для восстанавливаемой карточки следует проверить, что это действительно сотрудник, в котором присутствует сериализованный в Info сателлит.

public sealed class PersonalRoleRestoreExtension : CardDeleteExtension { private readonly ICardRepository extendedRepositoryWithoutTransaction;

public PersonalRoleRestoreExtension(ICardRepository extendedRepositoryWithoutTransaction) { this.extendedRepositoryWithoutTransaction = extendedRepositoryWithoutTransaction; }

public override void BeforeCommitTransaction(ICardDeleteExtensionContext context) { Card mainCard; Card satelliteCard; if (!context.Request.GetRestoreMode() || (mainCard = context.TryGetCardToDelete()) == null || (mainCard.TypeID != RoleHelper.PersonalRoleTypeID) || (satelliteCard = RoleExtensionHelper.TryGetPersonalRoleSatellite(mainCard)) == null) { return; }

// карточка сателлита будет заново создана, при этом версия обнуляется satelliteCard.Version = 0;

CardStoreResponse storeResponse = this.extendedRepositoryWithoutTransaction.Store(new CardStoreRequest { Card = satelliteCard });

context.ValidationResult.Add(storeResponse.ValidationResult); } }

Расширение с использованием шаблона:

public sealed class PersonalRoleRestoreExtension : CardSatelliteRestoreExtension { public PersonalRoleRestoreExtension(ICardRepository extendedRepositoryWithoutTransaction, ICardMetadata cardMetadata) : base(extendedRepositoryWithoutTransaction, cardMetadata) { }

protected override bool IsMainCardType(CardType cardType) { return cardType.ID == RoleHelper.PersonalRoleTypeID; }

protected override Card TryGetSatelliteCard(Card mainCard) { return RoleExtensionHelper.TryGetPersonalRoleSatellite(mainCard); } }

Восстановление сателлита выполняется как создание Store с расширениями, но без транзакции, т.к. наше расширение уже выполняется в транзакции на удаление карточки Deleted.

extensionContainer .RegisterExtension<ICardDeleteExtension, PersonalRoleRestoreExtension>(x => x .WithOrder(ExtensionStage.AfterPlatform) .WithUnity(unityContainer .RegisterType<PersonalRoleRestoreExtension>( new ContainerControlledLifetimeManager(), new InjectionConstructor( new ResolvedParameter<ICardRepository>(CardRepositoryNames.ExtendedWithoutTransaction), typeof(ICardMetadata)))) .WhenCardTypes(CardHelper.DeletedTypeID));

Расширение на экспорт сателлита при экспорте карточки сотрудника

При экспорте карточки сотрудника в Info этой карточки нужно записать сателлит, если он был создан.

Расширение с использованием шаблона:

public sealed class PersonalRoleExportExtension : CardSatelliteExportExtension { public PersonalRoleExportExtension( ICardRepository cardRepository, ICardTransactionStrategy cardTransactionStrategy, IDbScope dbScope) : base(cardRepository, cardTransactionStrategy) { }

protected override bool IsMainCardType(CardType cardType) { return cardType.ID == RoleHelper.PersonalRoleTypeID; }

protected override Guid? TryGetSatelliteID(IDbScope dbScope, Guid mainCardID) { return RoleExtensionHelper.TryGetPersonalRoleSatelliteID(dbScope, mainCardID); }

protected override void SetSatellite(Card mainCard, Card satellite) { RoleExtensionHelper.SetPersonalRoleSatellite(mainCard, satellite); } }

Экспорт сателлита выполняется при загрузке карточки сотрудника Get с указанием Method = CardGetMethod.Export.

extensionContainer .RegisterExtension<ICardGetExtension, PersonalRoleExportExtension>(x => x .WithOrder(ExtensionStage.AfterPlatform) .WithUnity(unityContainer .RegisterType<PersonalRoleExportExtension>(new ContainerControlledLifetimeManager())) .WhenMethod(CardGetMethod.Export));

Расширение на импорт сателлита при импорте карточки сотрудника

При импорте карточки сотрудника из Info этой карточки нужно получить сателлит, если он был установлен. В противном случае при экспорте карточки сателлит не существовал, поэтому при импорте он также не создаётся.

Расширение с использованием шаблона:

public sealed class PersonalRoleImportExtension : CardSatelliteImportExtension { public PersonalRoleImportExtension(ICardRepository extendedRepositoryWithoutTransaction) : base(extendedRepositoryWithoutTransaction) { }

protected override Card TryGetSatelliteCard(Card mainCard) { return RoleExtensionHelper.TryGetPersonalRoleSatellite(mainCard); } }

Экспорт сателлита выполняется при импорте, т.е. при сохранении карточки сотрудника Store с указанием Method = CardGetMethod.Import. Регистрация должна выполняться с ICardRepository с расширениями и без транзакции, т.е. по имени CardRepositoryNames.ExtendedWithoutTransaction. Важно не забыть добавить фильтр по типу карточки сотрудника RoleHelper.PersonalRoleTypeID. Если фильтр не используется, то в классе расширения необходимо переопределить метод IsMainCardType.

extensionContainer .RegisterExtension<ICardGetExtension, PersonalRoleImportExtension>(x => x .WithOrder(ExtensionStage.AfterPlatform) .WithUnity(unityContainer .RegisterType<PersonalRoleImportExtension>( new ContainerControlledLifetimeManager(), new InjectionConstructor( new ResolvedParameter<ICardRepository>(CardRepositoryNames.ExtendedWithoutTransaction)))) .WhenCardTypes(RoleHelper.PersonalRoleTypeID) .WhenMethod(CardGetMethod.Import));

Сохранение карточки-сателлита

Как загружать карточку уже было показано выше. Рассмотрим процесс сохранения.

Пусть сателлит с настройками сотрудника загружен в переменной satellite, изменим эти настройки:

byte[] newSettings = ... ; satellite.Sections["PersonalRoleSatellite"].Fields["WorkplaceExtensions"] = newSettings;

Сохраняем сателлит на сервере:

// удаляем данные, которые не изменялись и не будут сохранены satellite.RemoveAllButChanged();

CardStoreResponse response = cardRepository.Store(new CardStoreRequest { Card = satellite });

if (!response.ValidationResult.IsSuccessful()) { logger.Error("При сохранении настроек пользователя возникли ошибки: {0}", response.ValidationResult.Build().ToString()); return; }

При сохранении сателлита на клиенте не требуется вызывать метод RemoveAllButChanged:

CardStoreResponse response = cardRepository.Store(new CardStoreRequest { Card = satellite });

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

При показанных вариантах сохранения карточку в переменной satellite нельзя будет повторно использовать, т.к. пакет карточки будет изменён расширениями и методом RemoveAllButChanged. Для повторного использования нужно клонировать карточку перед сохранением, а после сохранения изменить её версию, чтобы повторное сохранение успешно выполнилось.

Сохранение на сервере с повторным использованием satellite:

Card satelliteClone = satellite.Clone(); satelliteClone.RemoveAllButChanged();

CardStoreResponse response = cardRepository.Store(new CardStoreRequest { Card = satelliteClone });

// устанавливаем новую версию (даже при наличии ошибок), чтобы карточку satellite потом можно было сохранить ещё раз satellite.Version = response.CardVersion;

if (!response.ValidationResult.IsSuccessful()) { logger.Error("При сохранении настроек пользователя возникли ошибки: {0}", response.ValidationResult.Build().ToString()); return; }

Сохранение на клиенте с повторным использованием satellite:

CardStoreResponse response = cardRepository.Store(new CardStoreRequest { Card = satellite.Clone() });

if (!response.ValidationResult.IsSuccessful()) { TessaDialog.ShowNotEmpty(response.ValidationResult.Build()); return; }

Back to top