Подтвердить что ты не робот

Транзакции в шаблоне хранилища

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

Это всего лишь надуманный пример, поэтому мне не важно, должны ли заказы быть или не быть внутри объекта клиента или даже в том же ограниченном контексте. Мне неинтересно, какие базовые технологии будут использоваться (nHibernate, EF, ADO.Net, Linq и т.д.). Я просто хочу посмотреть, как может выглядеть какой-то код вызова в этом, по общему признанию, надуманном примере операции "все или ничего".

4b9b3361

Ответ 1

Загрузка моего компьютера сегодня утром я столкнулся с точной проблемой для проекта, над которым я работаю. У меня были некоторые идеи, которые приводят к следующему дизайну - и комментарии были бы более чем удивительными. К сожалению, дизайн, предложенный Джошем, невозможен, поскольку я должен работать с удаленным SQL-сервером и не могу включить службу Distribute Transaction Coordinator, на которую он опирается.

Мое решение основано на нескольких, но простых изменениях в моем существующем коде.

Во-первых, у меня есть все мои репозитории, реализующие простой интерфейс маркера:

/// <summary>
/// A base interface for all repositories to implement.
/// </summary>
public interface IRepository
{ }

Во-вторых, я разрешаю всем репозиториям, поддерживающим транзакции, следующий интерфейс:

/// <summary>
/// Provides methods to enable transaction support.
/// </summary>
public interface IHasTransactions : IRepository
{
    /// <summary>
    /// Initiates a transaction scope.
    /// </summary>
    void BeginTransaction();

    /// <summary>
    /// Executes the transaction.
    /// </summary>
    void CommitTransaction();
}

Идея заключается в том, что во всех моих репозиториях я реализую этот интерфейс и добавляю код, который вводит транзакцию напрямую в зависимости от фактического провайдера (для поддельных репозиториев я составил список делегатов, который запускается при фиксации). Для LINQ to SQL было бы легко выполнить такие реализации, как:

#region IHasTransactions Members

public void BeginTransaction()
{
    _db.Transaction = _db.Connection.BeginTransaction();
}

public void CommitTransaction()
{
    _db.Transaction.Commit();
}

#endregion

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

Каждый метод, использующий репозиторий, должен вызывать BeginTransaction() и EndTransaction(), если репозиторий реализует IHasTransactions. Чтобы сделать этот вызов еще проще, я придумал следующие расширения:

/// <summary>
/// Extensions for spawning and subsequently executing a transaction.
/// </summary>
public static class TransactionExtensions
{
    /// <summary>
    /// Begins a transaction if the repository implements <see cref="IHasTransactions"/>.
    /// </summary>
    /// <param name="repository"></param>
    public static void BeginTransaction(this IRepository repository)
    {
        var transactionSupport = repository as IHasTransactions;
        if (transactionSupport != null)
        {
            transactionSupport.BeginTransaction();
        }
    }

    public static void CommitTransaction(this IRepository repository)
    {
        var transactionSupport = repository as IHasTransactions;
        if (transactionSupport != null)
        {
            transactionSupport.CommitTransaction();
        }
    }
}

Комментарии оценены!

Ответ 2

Я бы посмотрел на использование какой-то системы транзакций/контекста. Таким образом, у вас может быть следующий код, который примерно основан на .Net и С#.

public class OrderService
{

public void CreateNewOrder(Order order, Customer customer)
{
  //Set up our transactional boundary.
  using (TransactionScope ts=new TransactionScope())
  {
    IOrderRepository orderRepos=GetOrderRespository();
    orderRepos.SaveNew(order);
    customer.Status=CustomerStatus.OrderPlaced;

    ICustomerRepository customerRepository=GetCustomerRepository();
    customerRepository.Save(customer)
    ts.Commit();   
   }
}
}

TransactionScope может гнездиться, поэтому скажем, что у вас было действие, пересекающее несколько сервисов, которое ваше приложение создало бы TransactionScope. Теперь в текущем .net, если вы используете TransactionScope, у вас есть риск, связанный с DTC, но это будет разрешено в будущем.

Мы создали собственный класс TransactionScope, который в основном управлял нашими соединениями с БД и использовал локальные транзакции SQL.

Ответ 3

Используя Spring.NET AOP + NHibernate, вы можете написать свой класс репозитория как обычно и настроить транзакции в пользовательском XML файле:

public class CustomerService : ICustomerService
{
    private readonly ICustomerRepository _customerRepository;
    private readonly IOrderRepository _orderRepository;

    public CustomerService(
        ICustomerRepository customerRepository, 
        IOrderRepository orderRepository) 
    {
        _customerRepository = customerRepository;
        _orderRepository = orderRepository;
    }

    public int CreateOrder(Order o, Customer c) 
    {
        // Do something with _customerRepository and _orderRepository
    }
}

В XML файле вы выбираете, какие методы вы хотите выполнить внутри транзакции:

  <object id="TxProxyConfigurationTemplate" 
          abstract="true"
          type="Spring.Transaction.Interceptor.TransactionProxyFactoryObject, Spring.Data">

    <property name="PlatformTransactionManager" ref="HibernateTransactionManager"/>

    <property name="TransactionAttributes">
      <name-values>
        <add key="Create*" value="PROPAGATION_REQUIRED"/>
      </name-values>
    </property>
  </object>

  <object id="customerService" parent="TxProxyConfigurationTemplate">
    <property name="Target">
      <object type="MyNamespace.CustomerService, HibernateTest">
          <constructor-arg name="customerRepository" ref="customerRepository" />
          <constructor-arg name="orderRepository" ref="orderRepository" />
      </object>
    </property>

  </object>

И в вашем коде вы получите экземпляр класса CustomerService следующим образом:

ICustomerService customerService = (ICustomerService)ContextRegistry
    .GetContent()
    .GetObject("customerService");

Spring.NET вернет вам прокси класса CustomerService, который будет применять транзакцию при вызове метода CreateOrder. Таким образом, внутри ваших классов обслуживания нет кода транзакции. АОП заботится об этом. Для получения дополнительной информации вы можете ознакомиться с документацией Spring.NET.

Ответ 4

Вы хотите взглянуть на реализацию единицы шаблона работы. Существуют версии для NHibernate. Один из них находится в проекте Rhino Commons, там также Machine.UoW.

Ответ 5

Как я могу инкапсулировать сохранение более одного объекта в транзакционным способом с использованием шаблон репозитория? Например, что если бы я хотел добавить заказ и обновить статус клиента на основе этого создайте заказ, но сделайте это только в том случае, если заказ успешно завершен? Хранить в что для этого примера заказы а не коллекция внутри клиента. Это их собственная сущность.

Это не ответственность репозитория, его обычно что-то делается на более высоком уровне. Хотя вы сказали, что не интересуетесь конкретными технологиями, я думаю, что стоит упомянуть о решениях, например, при использовании NHibernate с веб-приложением, которое вы, вероятно, рассмотрели бы с помощью session -per request.

Итак, если вы можете управлять транзакциями на более высоком уровне, тогда моими двумя параметрами будут:

  • Проверка аванса. Например, в службе, координирующей поведение, решайте, хотите ли вы продолжить, попросив Заказ/Заказчик, если они скажут, что они этого не делают, тогда даже не пытайтесь обновите любой из них.
  • Откат. Просто приступайте к обновлению Customer/Order и, если что-то не удается частично откат транзакции базы данных.

Если вы переходите к второму варианту, тогда возникает вопрос, что происходит с объектами в памяти, ваш клиент может остаться в несогласованном состоянии. Если это имеет значение, и я работаю в сценариях, где это не так, как объект был загружен только для этого запроса, тогда я бы рассматривал предварительную проверку, если это возможно, потому что это намного проще, чем альтернативы (откат в -memory изменяет или перезагружает объекты).