4 votes

Comment passer le conteneur d'unité de travail dans le constructeur du référentiel en utilisant l'injection de dépendance ?

J'essaie de trouver comment compléter mon implémentation du modèle de référentiel dans une application web ASP.NET.

Actuellement, j'ai une interface de référentiel par classe de domaine définissant des méthodes pour, par exemple, charger et sauvegarder les instances de cette classe.

Chaque interface de référentiel est implémentée par une classe qui fait le travail de NHibernate. Castle Windsor trie les DI de la classe dans l'interface en fonction du web.config. Un exemple d'une classe implémentée est fourni ci-dessous :

  public class StoredWillRepository : IStoredWillRepository
  {
    public StoredWill Load(int id)
    {
      StoredWill storedWill;
      using (ISession session = NHibernateSessionFactory.OpenSession())
      {
        storedWill = session.Load<StoredWill>(id);
        NHibernateUtil.Initialize(storedWill);
      }
      return storedWill;
    }

    public void Save(StoredWill storedWill)
    {
      using (ISession session = NHibernateSessionFactory.OpenSession())
      {
        using (ITransaction transaction = session.BeginTransaction())
        {
          session.SaveOrUpdate(storedWill);
          transaction.Commit();
        }
      }
    }
  }

Comme indiqué dans un fil de discussion précédent, la classe du référentiel doit accepter un conteneur d'unité de travail (c'est-à-dire ISession) plutôt que de l'instancier dans chaque méthode.

Je prévois que le conteneur de l'unité de travail sera créé par chaque page aspx lorsque cela sera nécessaire (par exemple, dans une propriété).

Comment puis-je ensuite spécifier que cette instance de conteneur d'unité de travail doit être transmise au constructeur de StoredWillRepository lorsque Windsor le crée pour moi ?

Ou ce schéma est-il complètement faux ?

Merci encore pour vos conseils.

David

1voto

Neil Hewitt Points 2163

J'ai un cadre de persistance construit au-dessus de NHibernate qui est utilisé dans quelques applications Web. Il cache l'implémentation de NH derrière un IRepository y IRepository<T> avec les instances concrètes fournies par Unity (je pourrais donc en théorie remplacer NHibernate par Entity Framework, par exemple, assez facilement).

Puisque Unity ne supporte pas (ou du moins la version que j'utilise) le passage de paramètres de constructeurs autres que ceux qui sont des injections de dépendances elles-mêmes, le passage d'un ISession NH existant n'est pas possible ; mais je veux que tous les objets de l'UOW partagent le même ISession.

Je résous ce problème en ayant une classe de référentiel de contrôle qui gère l'accès à l'ISession sur une base par thread :

    public static ISession Session
    {
        get
        {
            lock (_lockObject)
            {
                // if a cached session exists, we'll use it
                if (PersistenceFrameworkContext.Current.Items.ContainsKey(SESSION_KEY))
                {
                    return (ISession)PersistenceFrameworkContext.Current.Items[NHibernateRepository.SESSION_KEY];
                }
                else
                {
                    // must create a new session - note we're not caching the new session here... that's the job of
                    // BeginUnitOfWork().
                    return _factory.OpenSession(new NHibernateInterceptor());
                }
            }
        }
    }

Dans cet exemple, PersistenceFrameworkContext.Current.Items accède à un IList<object> qui est stocké soit ThreadStatic si ce n'est dans un contexte Web, ou dans HttpContext.Current.Items s'il est dans un contexte Web (pour éviter les problèmes de pool de threads). Le premier appel à la propriété instancie la propriété ISession à partir de l'instance de la fabrique stockée, les appels suivants ne font que la récupérer à partir du stockage. Le verrouillage ralentira légèrement les choses, mais pas autant que le simple verrouillage d'un objet statique de type "appdomain-scoped". ISession instance.

J'ai alors BeginUnitOfWork y EndUnitOfWork J'ai spécifiquement interdit les UOW imbriqués parce qu'ils étaient franchement pénibles à gérer.

    public void BeginUnitOfWork()
    {
        lock (_lockObject)
        {
            if (PersistenceFrameworkContext.Current.Items.ContainsKey(SESSION_KEY))
                EndUnitOfWork();

            ISession session = Session;
            PersistenceFrameworkContext.Current.Items.Add(SESSION_KEY, session);
        }
    }

    public void EndUnitOfWork()
    {
        lock (_lockObject)
        {
            if (PersistenceFrameworkContext.Current.Items.ContainsKey(SESSION_KEY))
            {
                ISession session = (ISession)PersistenceFrameworkContext.Current.Items[SESSION_KEY];
                PersistenceFrameworkContext.Current.Items.Remove(SESSION_KEY);
                session.Flush();
                session.Dispose();
            }
        }
    }

Enfin, une paire de méthodes permet d'accéder aux référentiels spécifiques au domaine :

    public IRepository<T> For<T>()
        where T : PersistentObject<T>
    {
        return Container.Resolve<IRepository<T>>();
    }

    public TRepository For<T, TRepository>()
        where T : PersistentObject<T>
        where TRepository : IRepository<T>
    {
        return Container.Resolve<TRepository>();
    }

(Ici, PersistentObject<T> est une classe de base fournissant le support des ID et des égaux).

L'accès à un référentiel donné se fait donc selon le modèle

NHibernateRepository.For<MyDomainType>().Save();

Il est ensuite recouvert d'une façade de manière à ce que vous puissiez utiliser

MyDomainType.Repository.Save();

Lorsqu'un type donné a un dépôt spécialisé (c'est-à-dire qu'il a besoin de plus que ce qu'il peut obtenir à partir de IRepository<T> ), puis je crée une interface dérivant de IRepository<T> une mise en œuvre étendue héritant de mon IRepository<T> et dans le type de domaine lui-même, je surcharge la fonction statique Repository en utilisant new

    new public static IUserRepository Repository
    {
        get
        {
            return MyApplication.Repository.For<User, IUserRepository>();
        }
    }

( MyApplication [qui s'appelle quelque chose de moins ringard dans le produit réel] est une classe de façade qui s'occupe de fournir la classe Repository via Unity, de sorte que vous ne dépendez pas de l'implémentation spécifique du référentiel NHibernate dans vos classes de domaine).

Cela me permet de disposer d'un plugin complet via Unity pour l'implémentation du référentiel, d'accéder facilement au référentiel dans le code sans avoir à franchir des obstacles, et de bénéficier d'un accès transparent, par thread. ISession gestion.

Il y a beaucoup plus de code que ce qui est ci-dessus (et j'ai beaucoup simplifié le code de l'exemple), mais vous avez l'idée générale.

MyApplication.Repository.BeginUnitOfWork();
User user = User.Repository.FindByEmail("wibble@wobble.com");
user.FirstName = "Joe"; // change something
user.LastName = "Bloggs";
// you *can* call User.Repository.Save(user), but you don't need to, because...
MyApplication.Repository.EndUnitOfWork();
// ...causes session flush which saves the changes automatically

Dans mon application Web, j'ai une session par requête, donc BeginUnitOfWork y EndUnitOfWork sont appelés dans BeginRequest y EndRequest respectivement.

0voto

DanP Points 3311

Voir cet article pour une explication complète de la manière de fournir des sessions à des implémentations concrètes de référentiel à l'aide de castle.

0voto

rebelliard Points 5821

J'ai une structure assez similaire à la vôtre, et voici comment je résous votre question :

1) Pour spécifier mon conteneur sur chaque méthode, j'ai une classe séparée (" SessionManager ") que j'invoque ensuite via une propriété statique. En procédant ainsi, voici un exemple utilisant mon implémentation de Save :

private static ISession NHibernateSession
{
    get { return SessionManager.Instance.GetSession(); }
}

public T Save(T entity)
{
    using (var transaction = NHibernateSession.BeginTransaction())
    {
        ValidateEntityValues(entity);
        NHibernateSession.Save(entity);

        transaction.Commit();
    }

    return entity;
}

2) Mon conteneur n'est pas créé sur chaque page ASPX. J'instancie tout mon NHibernate goodness sur la page ASPX. global.asax page.

** Un peu plus de choses surgissent **

3) Vous n'avez pas besoin d'avoir un helper pour instancier le Load. Vous pourriez tout aussi bien utiliser Get au lieu de Load. Plus d'informations @ Différence entre Load et Get .

4) En utilisant votre code actuel, vous devriez répéter à peu près le même code pour chaque objet de domaine dont vous avez besoin (StoredWillRepository, PersonRepository, CategoryRepository, etc ), ce qui semble être une corvée. Vous pourriez très bien utiliser un classe générique pour opérer sur NHibernate, comme :

public class Dao<T> : IDao<T>
{
    public T SaveOrUpdate(T entity)
    {
        using (var transaction = NHibernateSession.BeginTransaction())
        {
            NHibernateSession.SaveOrUpdate(entity);
            transaction.Commit();
        }

        return entity;
    }
}

Dans ma mise en œuvre, je pourrais alors utiliser quelque chose comme :

Service<StoredWill>.Instance.SaveOrUpdate(will);

0voto

David Points 4672

Techniquement, la réponse à ma question est d'utiliser la surcharge de container.Resolve qui vous permet de spécifier l'argument du constructeur comme un type anonyme :

IUnitOfWork unitOfWork = [Code to get unit of work];
_storedWillRepository = container.Resolve<IStoredWillRepository>(new { unitOfWork = unitOfWork });

Mais avouons-le, les réponses fournies par tous les autres ont été beaucoup plus instructives.

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