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.