Come implementare l’Unità di lavoro che funziona con EF e NHibernate

Stavo lavorando a un’implementazione dell’unità di lavoro che funziona sia in Entity Framework 4.1 che in NHibernate. Trova sotto lo scheletro dei miei dettagli di implementazione

Definizione IUnitOfWork

public interface IUnitOfWork { IRepository LogInfos { get; } IRepository AppInfos { get; } void Commit(); void Rollback(); } 

Definizione IRepository

 public interface IRepository where T : class, IEntity { IQueryable FindAll(); IQueryable FindWhere(Expression<Func> predicate); T FindById(int id); void Add(T newEntity); void Remove(T entity); } 

Implementazione di UoW in NHibernate

 public class NHibernateUnitOfWork : IUnitOfWork, IDisposable { public ISession Session { get; private set; } public NHibernateUnitOfWork(ISessionFactory sessionFactory) { _sessionFactory = sessionFactory; Session = _sessionFactory.OpenSession(); _transaction = Session.BeginTransaction(); } public IRepository LogInfos { get { if (_logInfo == null) { _logInfo = new NHibernateRepository(Session); } return _logInfo; } } public void Commit() { if (_transaction.IsActive) _transaction.Commit(); } } 

Unità di lavoro in Entity Framework 4.1

     public class SqlUnitOfWork : IUnitOfWork { private readonly ObjectContext _context; public SqlUnitOfWork() { _context = new ObjectContext(connectionString); _context.ContextOptions.LazyLoadingEnabled = true; } private SqlRepository _logInfo = null; public IRepository LogInfos { get { if (_logInfo == null) { _logInfo = new SqlRepository(_context); } return _logInfo; } } public void Commit() { _context.SaveChanges(); } } 

    Repository usando NHibernate

     public class NHibernateRepository : IRepository where T : class, IEntity { protected ISession Session; public NHibernateRepository(ISession session) { Session = session; } public IQueryable FindAll() { return Session.Query(); } public IQueryable FindWhere(Expression<Func> predicate) { return Session.Query().Where(predicate); } public T FindById(int id) { return Session.Get(id); } public void Add(T newEntity) { Session.Save(newEntity); } public void Remove(T entity) { Session.Delete(entity); } } 

    Repository utilizzando Entity Framework

     public class SqlRepository : IRepository where T : class, IEntity { protected ObjectSet ObjectSet; public SqlRepository(ObjectContext context) { ObjectSet = context.CreateObjectSet(); } public IQueryable FindAll() { return ObjectSet; } public IQueryable FindWhere(Expression<Func> predicate) { return ObjectSet.Where(predicate); } public T FindById(int id) { return ObjectSet.Single(i => i.Id == id); } public void Add(T newEntity) { ObjectSet.AddObject(newEntity); } public void Remove(T entity) { ObjectSet.DeleteObject(entity); } } 

    Con questa implementazione ho potuto ottenere la maggior parte delle funzionalità come il salvataggio, l’eliminazione, il funzionamento delle transazioni sia su EF che su NH. Ma quando inizio a scrivere query LINQ complesse contro i repository, NH fallisce il più delle volte. Alcune funzionalità come OrderBy e ToList generano errori quando il repository restituisce NhQueryable.

    Nel seguente codice viene chiamato dal controller ASP.NET MVC a cui sto immettendo istanza di IUnitOfWork utilizzando StructureMap. Quando viene iniettato NHibernateUnitOfWork Dove non viene applicata la condizione dove funziona come previsto quando SqlUnitOfWork viene iniettato.

     var query = from a in _unitOfWork.AppInfos.FindAll() join l in _unitOfWork.LogInfos.FindAll() on a.Id equals l.ApplicationId where l.Level == "ERROR" || l.Level == "FATAL" group l by new { a.Id, a.ApplicationName } into g select new LogInfoSummaryViewModel() { ApplicationId = g.Key.Id, ApplicationName = g.Key.ApplicationName, ErrorCount = g.Where(i => i.Level == "ERROR").Count(), FatalCount = g.Where(i => i.Level == "FATAL").Count() }; return query.AsEnumerable(); 

    Come una parte non build una soluzione che supporti diversi fornisce il linq è il modo di disastro. Linq e IQueryable sono astrazioni che perdono: ogni provider Linq può avere le sue “caratteristiche” e limitazioni. Inoltre EF stesso aggiunge una certa logica tramite i metodi di estensione personalizzati per IQueryable (come Include o AsNoTracking in EFv4.1). Questi metodi converte internamente le classi IQueryable in ORM.

    Se si desidera avere una soluzione universale, è necessario abbandonare Linq e aggiungere un terzo modello per formare l’astrazione. Oltre ai modelli di deposito e unità di lavoro è necessario un modello di specifica personalizzato. In genere si reimplementerà l’API Criteria di NHibernate.

    Da un punto di vista IoC e un desiderio di eleganza la tua strada è la strada da percorrere. Tuttavia, tutto ciò che ho letto sul provider linq di NHibernate è che è ancora “beta-ish”, perché è così dannatamente difficile scrivere provider Linq in primo luogo. Quindi potrebbe benissimo essere che stai incontrando un bug qui. Al momento sarei molto riluttante a scrivere codice di produzione con Linq2Nhibernate. La nuova funzione di QueryOver è molto più potente. Ma ovviamente, purtroppo, QueryOver non si adatta perfettamente alla tua architettura, perché dovresti utilizzare la syntax di NHibernate fino in fondo. Le query complesse di Linq al di fuori del repository non sarebbero utili perché non verrebbero mai tradotte in SQL.

    Temo che questo sia effettivamente il bacio della morte per l’eleganza del tuo design, perché, per cominciare, sarebbe inutile lasciare un repository restituire un IQueryable . Ma restituire IEnumerable potrebbe paralizzare l’implementazione EF. Quindi, a cosa si riduce, penso che per interrogare entrambe le implementazioni sono troppo diverse per adattarsi dietro una semplice interfaccia generica.

    Ecco un post molto utile su QueryOver e Linq.

    BTW: questa è una domanda e un design molto interessanti. Vorrei poter dare più di un voto!

    Oltre alle difficoltà tecniche con QueryOver menzionate da Ladislav, potrebbe esserci un problema di progettazione. Non si avrebbe questo problema se ci si avvicina dalla prospettiva di Domain Driven Design in cui l’interfaccia del repository è basata su Ubiquitous Language e non espone cose come IQueryable che è un puro concetto di accesso ai dati. Questa risposta contiene informazioni e link che potresti trovare interessanti.