2011-09-14 8 views
15

Estaba trabajando en una implementación de Unidad de trabajo que funciona tanto en Entity Framework 4.1 como en NHibernate. Encuentra debajo del esqueleto de mis detalles de la implementaciónCómo implementar la Unidad de trabajo que trabaja con EF y NHibernate

IUnitOfWork definición

public interface IUnitOfWork 
{ 
    IRepository<LogInfo> LogInfos { get; } 
    IRepository<AppInfo> AppInfos { get; } 
    void Commit(); 
    void Rollback(); 
} 

IRepository definición

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

Implementación de UoW en 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<LogInfo> LogInfos 
    { 
     get 
     { 
      if (_logInfo == null) 
      { 
       _logInfo = new NHibernateRepository<LogInfo>(Session); 
      } 

      return _logInfo; 
     } 
    } 

    public void Commit() 
    { 
     if (_transaction.IsActive) 
      _transaction.Commit(); 
    } 
} 

unidad de trabajo en 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> _logInfo = null; 

    public IRepository<LogInfo> LogInfos 
    { 
     get 
     { 
      if (_logInfo == null) 
      { 
       _logInfo = new SqlRepository<LogInfo>(_context); 
      } 
      return _logInfo; 
     } 
    } 

    public void Commit() 
    { 
     _context.SaveChanges(); 
    } 
} 

repositorio usando NHibernate

public class NHibernateRepository<T> : IRepository<T> where T : class, IEntity 
{ 
    protected ISession Session; 

    public NHibernateRepository(ISession session) 
    { 
     Session = session; 
    } 

    public IQueryable<T> FindAll() 
    { 
     return Session.Query<T>(); 
    } 

    public IQueryable<T> FindWhere(Expression<Func<T, bool>> predicate) 
    { 
     return Session.Query<T>().Where<T>(predicate); 
    } 

    public T FindById(int id) 
    { 
     return Session.Get<T>(id); 
    } 

    public void Add(T newEntity) 
    { 
     Session.Save(newEntity); 
    } 

    public void Remove(T entity) 
    { 
     Session.Delete(entity); 
    } 
} 

repositorio utilizando Entity Framework

public class SqlRepository<T> : IRepository<T> where T : class, IEntity 
{ 
    protected ObjectSet<T> ObjectSet; 

    public SqlRepository(ObjectContext context) 
    { 
     ObjectSet = context.CreateObjectSet<T>(); 
    } 

    public IQueryable<T> FindAll() 
    { 
     return ObjectSet; 
    } 

    public IQueryable<T> FindWhere(Expression<Func<T, bool>> 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 esta implementación podría conseguir la mayor parte de las características como grabar, borrar, la transacción de trabajo tanto en EF y NH. Pero cuando empiezo a escribir consultas LINQ complejas contra los repositorios, NH falla la mayor parte del tiempo. Algunas funciones como OrderBy y ToList arrojan errores cuando el repositorio devuelve NhQueryable.

En el siguiente código se llama desde el controlador ASP.NET MVC al cual estoy inyectando la instancia de IUnitOfWork usando StructureMap. Cuando NHibernateUnitOfWork se inyecta Donde la condición no se aplica donde funciona como se espera cuando se inyecta SqlUnitOfWork.

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(); 
+0

Al abstraer el acceso a la información esto también podría ser de interés http://stackoverflow.com/a/12913174/671619 – Firo

Respuesta

14

Como una solución lateral que no es compatible con diferentes ofertas en la parte superior del linq es una forma de desastre. Linq y IQueryable son abstracciones con fugas: cada proveedor de Linq puede tener sus propias "características" y limitaciones. Además, el propio EF agrega algo de lógica a través de métodos de extensión personalizados para IQueryable (como Include o AsNoTracking en EFv4.1).Estos métodos convierten internamente IQueryable en clases específicas de ORM.

Si desea tener una solución universal, debe abandonar Linq y agregar un tercer patrón para formar la abstracción. Además de los patrones de Repositorio y Unidad de trabajo, necesita el patrón personalizado Specification. Generalmente, volverá a aplicar la API de criterios de NHibernate.

6

Desde un punto de vista de IoC y un deseo de elegancia, su camino es el camino a seguir. Sin embargo, todo lo que leí sobre el proveedor de linq de NHibernate es que todavía es "beta-ish", porque en primer lugar es tan difícil escribir proveedores de Linq. Por lo que podría ser que acaba de encontrar un error aquí. Actualmente sería muy reacio a escribir código de producción con Linq2Nhibernate. La nueva característica QueryOver es mucho más poderosa. Pero, por supuesto, lamentablemente, QueryOver no se adapta perfectamente a su arquitectura, ya que tendría que utilizar la sintaxis de NHibernate hasta el final. Las consultas complejas de Linq fuera de su repositorio serían inútiles porque nunca se traducirían a SQL.

Me temo que esto efectivamente es el beso de la muerte a la elegancia de su diseño, porque, para empezar, sería inútil dejar que un depósito devuelva un IQueryable<T>. Pero devolver IEnumerable<T> paralizaría su implementación de EF. Entonces, lo que se reduce a esto, creo que para consultar ambas implementaciones son demasiado diferentes para caber detrás de una interfaz genérica ordenada.

Here es una publicación muy útil en QueryOver y Linq.

BTW: esta es una pregunta y diseño muy interesante. ¡Ojalá pudiera dar más de un voto!

2

Además de las dificultades técnicas con QueryOver mencionadas por Ladislav, puede haber un problema de diseño. No tendría este problema si lo aborda desde la perspectiva Domain Driven Design donde Repository interfaz se basa en Ubiquitous Language y no expone cosas como IQueryable que es un concepto de acceso a datos puro. Este answer tiene información y enlaces que pueden resultarle interesantes.

Cuestiones relacionadas