59 votes

Transactions dans le modèle de référentiel

Comment puis-je encapsuler l'économie de plus d'une entité de manière transactionnelle en utilisant le modèle de référentiel? Par exemple, si je voulais ajouter une commande et de mettre à jour le statut du client basée sur la création de la commande, mais seulement le faire si la commande est terminée avec succès? Gardez à l'esprit que, pour cet exemple, les commandes ne sont pas une collection à l'intérieur du client. Ils sont leur propre entité.

C'est juste un exemple artificiel, donc je ne me soucie pas vraiment de savoir si les commandes doivent ou non être à l'intérieur de l'objet client ou dans le même contexte délimité. Je ne me soucie pas vraiment de ce que la technologie sous-jacente sera utilisé (nHibernate, EF, ADO.Net, Linq, etc.) Je veux juste voir ce que certain appel le code pourrait ressembler à ce schéma, certes artificiel exemple d'un tout ou rien de l'opération.

21voto

Troels Thomsen Points 4839

Le démarrage de mon ordinateur ce matin, j'ai affronté le problème exact pour un projet que je suis en train de travailler sur. J'ai eu quelques idées qui aboutissent à la suite de la conception et des commentaires serait plus que génial. Malheureusement, le design proposé par Josh n'est pas possible, que j'ai de travailler avec un serveur SQL server distant et ne pouvez pas activer le Distribuer Transaction Coordinator service de elle repose.

Ma solution est basée sur quelques simples, mais des modifications à mon code.

Tout d'abord, j'ai tous mes dépôts de mettre en œuvre un simple marqueur de l'interface:

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

Deuxièmement, je laisse toutes mes transactions permis de mettre en œuvre les référentiels de l'interface suivante:

/// <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();
}

L'idée est que, dans tous mes dépots j'implémente cette interface et ajouter le code qui introduit de transaction directement en fonction du fournisseur (pour de faux référentiels j'ai fait une liste des délégués qui sera exécuté lors de la validation). Pour LINQ to SQL, il serait facile de faire des implémentations telles que:

#region IHasTransactions Members

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

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

#endregion

Bien sûr, cela nécessite qu'un nouveau référentiel de la classe est créée pour chaque thread, mais c'est raisonnable pour mon projet.

Chaque méthode à l'aide du référentiel doit invoquer l' BeginTransaction() et de la EndTransaction(), si le dépôt n'implémente IHasTransactions. Pour faire cet appel est encore plus facile, je suis venu avec les extensions suivantes:

/// <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();
		}
	}
}

Les commentaires sont appréciés!

13voto

JoshBerke Points 34238

Je regarde en utilisant un certain type de Transaction Portée et Contexte du système. Oui, vous pourriez avoir le code suivant qui est un peu basé sur .Net Et C#.

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 peut nest donc, disons que vous avez eu une action qui a traversé plusieurs services de votre demande de créer un TransactionScope ainsi. Maintenant dans le courant .net si vous utilisez le TransactionScope ils vous ont risque escallating à un DTC, mais ce problème sera résolu dans l'avenir.

Nous avions créé notre propre classe TransactionScope qui, fondamentalement, a réussi notre DB connections et locales transactions SQL.

6voto

Colin Jack Points 364

Comment puis-je encapsuler l'économie de plus d'une entité dans un forme transactionnelle à l'aide de la modèle de référentiel? Par exemple, ce si je voulais ajouter une commande et mise à jour le statut de client basé sur la création de la commande, mais seulement si les commande effectuée avec succès? Gardez à l' l'esprit que, pour cet exemple, les commandes sont pas une collection à l'intérieur du client. Ils sont leur propre entité.

Ce n'est pas une responsabilité du référentiel, il est généralement quelque chose à un niveau plus élevé. Bien que vous avez dit que vous n'êtes pas intéressé dans des technologies spécifiques, je pense que sa vaut attacher les solutions, par exemple lors de l'utilisation de NHibernate avec une Web app, vous devriez probablement envisager d'utiliser de la session, conformément à la demande.

Donc, si vous pouvez gérer les transactions à un niveau supérieur, puis mes deux options:

  1. D'avance check - Par exemple dans un service de coordonner le comportement de décider si vous souhaitez poursuivre en demandant à l'Ordre/Client, si dire soit ils ne le font pas, alors n'essayez même pas de mettre à jour l'un d'eux.
  2. Restauration - il suffit de procéder de la mise à jour du Client/de la Commande et si les choses ne mi-chemin à travers le rollback de la transaction de base de données.

Si vous optez pour la deuxième option, alors la question est de savoir ce qui se passe pour les objets en mémoire, votre Client peut être laissée dans un état incohérent. Si ce qui compte, et je travaille dans les scénarios où il n'est pas que l'objet était seulement chargé de demande, puis je serais en considérant la signature de vérifier si ses possible parce que c'est beaucoup plus facile que les solutions de rechange (rouler en arrière les changements de la mémoire ou le rechargement d'objets).

5voto

Darin Dimitrov Points 528142

À l'aide de Spring.NET AOP + NHibernate vous pouvez écrire votre référentiel de classe normale et de configurer vos transactions dans le fichier XML personnalisé:

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
    }
}

Dans le fichier XML, vous sélectionnez les méthodes que vous souhaitez exécuter à l'intérieur d'une transaction:

  <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>

Et dans votre code, vous obtenir une instance de la CustomerService classe comme ceci:

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

Spring.NET vous revenez d'un proxy de la CustomerService classe qui va s'appliquer à une transaction lorsque vous appelez CreateOrder méthode. De cette façon, il n'y a pas de transaction code spécifique à l'intérieur de vos classes de service. AOP prend soin de lui. Pour plus de détails, vous pouvez prendre un coup d'oeil à la documentation de Spring.NET.

3voto

Garry Shutler Points 20898

Vous souhaitez examiner la mise en œuvre du modèle d'unité de travail. Il existe des implémentations pour NHibernate. L'un est dans le projet Rhino Commons, il y a aussi le Machine.UoW.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X