2012-06-07 30 views
59

Recientemente aprendí ASP.NET MVC (me encanta). Estoy trabajando con una empresa que usa la inyección de dependencia para cargar una instancia de repositorio en cada solicitud, y estoy familiarizado con el uso de ese repositorio.Mejor patrón de repositorio para ASP.NET MVC

Pero ahora estoy escribiendo un par de aplicaciones de MVC. No entiendo completamente los cómos y los por qué del repositorio que usa mi empresa, y estoy tratando de decidir el mejor enfoque para implementar el acceso a los datos.

Estoy usando C# y Entity Framework (con todas las últimas versiones).

Veo tres enfoques generales para manejar el acceso a los datos.

  1. Contexto de base de datos regular dentro de una instrucción using cada vez que accedo a los datos. Esto es simple y funciona bien. Sin embargo, si dos ubicaciones necesitan leer los mismos datos dentro de una solicitud, los datos deben leerse dos veces. (Con un único repositorio por solicitud, la misma instancia se usaría en ambos lugares y entiendo que la segunda lectura simplemente devolverá los datos de la primera lectura).

  2. Un típico repository pattern. Por razones que no entiendo, este patrón típico implica la creación de una clase contenedora para cada tabla utilizada desde la base de datos. Eso me parece equivocado. De hecho, dado que se implementan también como interfaces, técnicamente crearía dos clases de contenedor para cada tabla. EF crea tablas para mí. No creo que este enfoque tenga sentido.

  3. También hay un generic repository pattern donde se crea una única clase de repositorio para servir a todos los objetos de entidad. Esto tiene mucho más sentido para mí. Pero, ¿tiene sentido para los demás? ¿Es el enlace de arriba el mejor enfoque?

Me gustaría obtener información de otros sobre este tema. ¿Estás escribiendo tu propio repositorio, usando uno de los anteriores, o haciendo algo completamente diferente? Por favor comparte.

+1

Yo diría que el enlace en el número 2 no es un patrón de repositorio típico. Normalmente, tiene un repositorio para cada Raíz agregada en [DDD] (http://en.wikipedia.org/wiki/Domain-driven_design) speak. Este es un buen [SO thread] (http://stackoverflow.com/questions/1958621/whats-an-aggregate-root) sobre este tema. El ejemplo en el número 2, como usted menciona, parece simplemente envolver una tabla. Parece que están implementando el patrón solo para implementar el patrón sin ningún beneficio real. Entonces estaría de acuerdo contigo. – RobertMS

+0

Puede que tengas razón. Sin embargo, al buscar en la web, la mayoría de los ejemplos que encontré crearon envoltorios separados para cada entidad, incluidos los de algunos libros que tengo. En ese sentido, el código en el enlace que publiqué parecía típico. Gracias por los enlaces. Los veré. –

+1

@JonathanWood Aquí está la [solución que más me gusta] (http://huyrua.wordpress.com/2010/07/13/entity-framework-4-poco-repository-and-specification-pattern/) (maldición, yo uso este enlace mucho). A saber, la interfaz de repositorio no genérica con métodos genéricos. Todavía es una envoltura relativamente delgada alrededor del 'DbContext', pero permite una prueba más sencilla. –

Respuesta

34

He utilizado una mezcla de # 2 y # 3, pero prefiero un repositorio genérico estricto si es posible (más estricto que incluso sugerido en el enlace para # 3). # 1 no es bueno porque funciona mal con pruebas unitarias.

Si tiene un dominio más pequeño o necesita restringir qué entidades le permiten consultar su dominio, supongo que el # 2- o # 3 que define las interfaces de repositorio específicas de cada entidad que implementan un repositorio genérico tiene sentido. Sin embargo, considero que es agotador e innecesario escribir una interfaz y una implementación concreta para cada entidad que deseo consultar. ¿De qué sirve el public interface IFooRepository : IRepository<Foo> (nuevamente, a menos que deba restringir a los desarrolladores a un conjunto de raíces agregadas permitidas)?

acabo de definir la interfaz de mi repositorio genérico, con Add, Remove, Get, GetDeferred, Count y Find métodos (Encontrar devuelve una interfaz que permite IQueryable LINQ), cree una aplicación genérica concreta, y lo llaman un día. Confío mucho en Find y, por lo tanto, LINQ. Si necesito usar una consulta específica más de una vez, utilizo métodos de extensión y escribo la consulta usando LINQ.

Esto cubre el 95% de mis necesidades de persistencia. Si necesito realizar algún tipo de acción de persistencia que no se puede hacer de forma genérica, uso una API de ICommand de fabricación propia. Por ejemplo, supongamos que estoy trabajando con NHibernate y necesito realizar una consulta compleja como parte de mi dominio, o tal vez necesite hacer un comando masivo. La API se ve más o menos así:

// marker interface, mainly used as a generic constraint 
public interface ICommand 
{ 
} 

// commands that return no result, or a non-query 
public interface ICommandNoResult : ICommand 
{ 
    void Execute(); 
} 

// commands that return a result, either a scalar value or record set 
public interface ICommandWithResult<TResult> : ICommand 
{ 
    TResult Execute(); 
} 

// a query command that executes a record set and returns the resulting entities as an enumeration. 
public interface IQuery<TEntity> : ICommandWithResult<IEnumerable<TEntity>> 
{ 
    int Count(); 
} 

// used to create commands at runtime, looking up registered commands in an IoC container or service locator 
public interface ICommandFactory 
{ 
    TCommand Create<TCommand>() where TCommand : ICommand; 
} 

Ahora puedo crear una interfaz para representar un comando específico.

public interface IAccountsWithBalanceQuery : IQuery<AccountWithBalance> 
{ 
    Decimal MinimumBalance { get; set; } 
} 

puedo crear una aplicación concreta y utilizar SQL crudo, NHibernate HQL, lo que sea, y registrarlo con mi localizador de servicios.

Ahora en mi lógica de negocio que puede hacer algo como esto:

var query = factory.Create<IAccountsWithBalanceQuery>(); 
query.MinimumBalance = 100.0; 

var overdueAccounts = query.Execute(); 

También puede utilizar un patrón Especificación con IQuery para construir, fácil de entrada impulsada consultas significativas, en lugar de tener una interfaz con millones propiedades confusas, pero eso supone que no encuentra el patrón de especificación confuso en sí mismo;).

Una última parte del rompecabezas es cuando su repositorio necesita hacer operaciones específicas de depósito previo y posterior. Ahora, puede crear fácilmente una implementación de su repositorio genérico para una entidad específica, luego anular los métodos relevantes y hacer lo que necesita hacer, y actualizar su IoC o registro de localizador de servicios y finalizarlo.

Sin embargo, a veces esta lógica es transversal e incómoda de implementar anulando un método de repositorio. Así que creé IRepositoryBehavior, que básicamente es un receptor de eventos. (Continuación es solo una definición aproximada de la parte superior de mi cabeza)

public interface IRepositoryBehavior 
{ 
    void OnAdding(CancellableBehaviorContext context); 
    void OnAdd(BehaviorContext context); 

    void OnGetting(CancellableBehaviorContext context); 
    void OnGet(BehaviorContext context); 

    void OnRemoving(CancellableBehaviorContext context); 
    void OnRemove(BehaviorContext context); 

    void OnFinding(CancellableBehaviorContext context); 
    void OnFind(BehaviorContext context); 

    bool AppliesToEntityType(Type entityType); 
} 

Ahora, estos comportamientos pueden ser cualquier cosa. Auditoría, comprobación de seguridad, borrado suave, imposición de restricciones de dominio, validación, etc. Creo un comportamiento, lo registro con el IoC o localizador de servicios, y modifico mi repositorio genérico para tomar una colección de IRepositoryBehavior s registrados, y verifico cada comportamiento contra el tipo de repositorio actual y ajuste la operación en los controladores pre/post para cada comportamiento aplicable.

Aquí hay un ejemplo de comportamiento de borrado suave (borrado suave significa que cuando alguien solicita eliminar una entidad, simplemente la marcamos como eliminada para que no se pueda devolver nuevamente, pero en realidad nunca se elimina físicamente).

public SoftDeleteBehavior : IRepositoryBehavior 
{ 
    // omitted 

    public bool AppliesToEntityType(Type entityType) 
    { 
     // check to see if type supports soft deleting 
     return true; 
    } 

    public void OnRemoving(CancellableBehaviorContext context) 
    { 
     var entity = context.Entity as ISoftDeletable; 
     entity.Deleted = true; // when the NHibernate session is flushed, the Deleted column will be updated 

     context.Cancel = true; // set this to true to make sure the repository doesn't physically delete the entity. 
    } 
} 

Sí, esto es básicamente una aplicación simplificada y abstracta de los detectores de eventos de NHibernate, pero por eso me gusta.A) Puedo probar un comportamiento sin poner NHibernate en la imagen B) Puedo usar estos comportamientos fuera de NHibernate (digamos que el repositorio es la implementación del cliente que envuelve las llamadas al servicio REST) ​​C) Los oyentes del evento de NH pueden ser un verdadero dolor en el culo ;)

+0

Gracias por los fragmentos de código. Pasaré algún tiempo revisándolo más de cerca. –

+0

Finalmente conseguí más tiempo para pasar esto. Estoy un poco sorprendido por parte de este código. Parecía estar diciendo que le gusta un enfoque muy genérico y, sin embargo, parece que está creando interfaces especializadas, que es más específico que incluso los ejemplos que he examinado. ¿Por qué es esto necesario? (Por cierto, si alguna vez quieres escribir un artículo más completo con el código fuente, me gustaría publicar algo como esto en mi sitio web http://www.blackbeltcoder.com) –

12

Recomendaría el número 1, con algunas advertencias. El número 2 es lo que parece ser más común, pero según mi experiencia, el repositorio acaba siendo un sucio basurero para las consultas. Si utilizas un repositorio genérico (2), es solo una capa delgada alrededor del DBContext, un poco inútil a menos que estés planeando cambiar los ORM (mala idea).

Pero cuando accedo directamente DBContext Yo prefiero usar un patrón de Tuberías y filtros para que pueda volver a utilizar la lógica común, algo así como

items = DBContext.Clients 
    .ByPhoneNumber('1234%') 
    .ByOrganisation(134); 

El ByPhoneNumber y por la Organización son sólo métodos de extensión.

+0

Gracias, pero ¿qué pasa con los posibles problemas de rendimiento como los que planteé? Si actualiza un DBContext cada vez que lo necesita, existe la posibilidad de que diferentes partes del código soliciten los mismos datos y no se almacenarán en caché. –

+4

@ Johnathan: utilice la inyección de dependencia de modo que cualquier cosa que requiera DBContext reciba el mismo contexto por solicitud de por vida. –

0

Hay una solución lista para usar en URF - Unit of Work & (extensible/generic) Repositories Framework. Esto le ahorrará mucho tiempo. Implementaron un repositorio genérico (también hay un repositorio asíncrono). Para extender cualquier repositorio que han utilizado extensiones de la siguiente manera:

 public static decimal GetCustomerOrderTotalByYear(this IRepository<Customer> repository, string customerId, int year) 
    { 
     return repository 
      .Queryable() 
      .Where(c => c.CustomerID == customerId) 
      .SelectMany(c => c.Orders.Where(o => o.OrderDate != null && o.OrderDate.Value.Year == year)) 
      .SelectMany(c => c.OrderDetails) 
      .Select(c => c.Quantity*c.UnitPrice) 
      .Sum(); 
    } 

Algunas clases como QueryObject pueden ser una sobrecarga de trabajo en función de su proyecto, pero fue demasiado es buena solución para ayudarle a ponerse en marcha.

1

Aquí vamos para mejor patrón de repositorio en Asp.Net MVC:

El patrón Repositorio añade una capa de separación entre las capas de datos y de dominio de una aplicación. También hace que las partes de acceso a datos de una aplicación sean mejor comprobables.

Base de datos de fábrica (IDatabaseFactory.cs):

public interface IDatabaseFactory : IDisposable 
{ 
    Database_DBEntities Get(); 
} 

Implementaciones de fábrica base de datos (DatabaseFactory.cs):

public class DatabaseFactory : Disposable, IDatabaseFactory 
{ 
    private Database_DBEntities dataContext; 
    public Database_DBEntities Get() 
    { 
     return dataContext ?? (dataContext = new Database_DBEntities()); 
    } 

    protected override void DisposeCore() 
    { 
     if (dataContext != null) 
      dataContext.Dispose(); 
    } 
} 

interfaz base (IRepository.cs) :

public interface IRepository<T> where T : class 
{ 
    void Add(T entity); 
    void Update(T entity); 
    void Detach(T entity); 
    void Delete(T entity); 
    T GetById(long Id); 
    T GetById(string Id); 
    T Get(Expression<Func<T, bool>> where); 
    IEnumerable<T> GetAll(); 
    IEnumerable<T> GetMany(Expression<Func<T, bool>> where); 
    void Commit(); 
} 

Resumen Clase (Repository.cs):

public abstract class Repository<T> : IRepository<T> where T : class 
    { 
     private Database_DBEntities dataContext; 
     private readonly IDbSet<T> dbset; 
     protected Repository(IDatabaseFactory databaseFactory) 
     { 
      DatabaseFactory = databaseFactory; 
      dbset = DataContext.Set<T>(); 

     } 

     /// <summary> 
     /// Property for the databasefactory instance 
     /// </summary> 
     protected IDatabaseFactory DatabaseFactory 
     { 
      get; 
      private set; 
     } 

     /// <summary> 
     /// Property for the datacontext instance 
     /// </summary> 
     protected Database_DBEntities DataContext 
     { 
      get { return dataContext ?? (dataContext = DatabaseFactory.Get()); } 
     } 

     /// <summary> 
     /// For adding entity 
     /// </summary> 
     /// <param name="entity"></param> 
     public virtual void Add(T entity) 
     { 

      try 
      { 
       dbset.Add(entity); 
       // dbset.Attach(entity); 
       dataContext.Entry(entity).State = EntityState.Added; 
       int iresult = dataContext.SaveChanges(); 
      } 
      catch (UpdateException ex) 
      { 

      } 
      catch (DbUpdateException ex) //DbContext 
      { 

      } 
      catch (Exception ex) 
      { 
       throw ex; 
      } 

     } 

     /// <summary> 
     /// For updating entity 
     /// </summary> 
     /// <param name="entity"></param> 
     public virtual void Update(T entity) 
     { 
      try 
      { 
       // dbset.Attach(entity); 
       dbset.Add(entity); 
       dataContext.Entry(entity).State = EntityState.Modified; 
       int iresult = dataContext.SaveChanges(); 
      } 
      catch (UpdateException ex) 
      { 
       throw new ApplicationException(Database_ResourceFile.DuplicateMessage, ex); 
      } 
      catch (DbUpdateException ex) //DbContext 
      { 
       throw new ApplicationException(Database_ResourceFile.DuplicateMessage, ex); 
      } 
      catch (Exception ex) { 
       throw ex; 
      } 
     } 



     /// <summary> 
     /// for deleting entity with class 
     /// </summary> 
     /// <param name="entity"></param> 
     public virtual void Delete(T entity) 
     { 
      dbset.Remove(entity); 
      int iresult = dataContext.SaveChanges(); 
     } 


     //To commit save changes 
     public void Commit() 
     { 
      //still needs modification accordingly 
      DataContext.SaveChanges(); 
     } 

     /// <summary> 
     /// Fetches values as per the int64 id value 
     /// </summary> 
     /// <param name="id"></param> 
     /// <returns></returns> 
     public virtual T GetById(long id) 
     { 
      return dbset.Find(id); 
     } 

     /// <summary> 
     /// Fetches values as per the string id input 
     /// </summary> 
     /// <param name="id"></param> 
     /// <returns></returns> 
     public virtual T GetById(string id) 
     { 
      return dbset.Find(id); 
     } 

     /// <summary> 
     /// fetches all the records 
     /// </summary> 
     /// <returns></returns> 
     public virtual IEnumerable<T> GetAll() 
     { 
      return dbset.AsNoTracking().ToList(); 
     } 

     /// <summary> 
     /// Fetches records as per the predicate condition 
     /// </summary> 
     /// <param name="where"></param> 
     /// <returns></returns> 
     public virtual IEnumerable<T> GetMany(Expression<Func<T, bool>> where) 
     { 
      return dbset.Where(where).ToList(); 
     } 

     /// <summary> 
     /// 
     /// </summary> 
     /// <param name="entity"></param> 
     public void Detach(T entity) 
     { 
      dataContext.Entry(entity).State = EntityState.Detached; 
     } 

     /// <summary> 
     /// fetches single records as per the predicate condition 
     /// </summary> 
     /// <param name="where"></param> 
     /// <returns></returns> 
     public T Get(Expression<Func<T, bool>> where) 
     { 
      return dbset.Where(where).FirstOrDefault<T>(); 
     } 

    } 

Ahora bien, el punto principal es ¿Cómo acceder a este patrón repositorio en el controlador Aquí vamos:

1. Usted tener modelo de usuario:

public partial class User 
{ 
     public int Id { get; set; } 
     public string Name { get; set; } 
} 

2. Ahora hay que crear la clase repositorio de su UserModel

public class UserRepository : Repository<User>, IUserRepository 
{ 
    private Database_DBEntities dataContext; 

    protected IDatabaseFactory DatabaseFactory 
    { 
     get; 
     private set; 
    } 

    public UserRepository(IDatabaseFactory databaseFactory) 
     : base(databaseFactory) 
    { 
     DatabaseFactory = databaseFactory; 
    } 

    protected Database_DBEntities DataContext 
    { 
     get { return dataContext ?? (dataContext = DatabaseFactory.Get()); } 
    } 

    public interface IUserRepository : IRepository<User> 
    { 
    } 
} 

3. Ahora usted tiene que crear UserService Interface (IUserService.cs) con todos los métodos CRUD:

public interface IUserService 
{ 

    #region User Details 
    List<User> GetAllUsers(); 
    int SaveUserDetails(User Usermodel); 
    int UpdateUserDetails(User Usermodel); 
    int DeleteUserDetails(int Id); 
    #endregion 

} 

4. Ahora tiene que crear UserService Interface (UserService.cs) con todos los métodos CRUD:

public class UserService : IUserService 
{ 
    IUserRepository _userRepository; 
    public UserService() { } 
    public UserService(IUserRepository userRepository) 
    { 
    this._userRepository = userRepository; 
    } 
    public List<User> GetAllUsers() 
    { 
     try 
     { 
      IEnumerable<User> liUser = _userRepository.GetAll(); 
      return liUser.ToList(); 
     } 
     catch (Exception ex) 
     { 
      throw ex; 
     } 
    } 
    /// <summary> 
    /// Saves the User details. 
    /// </summary> 
    /// <param name="User">The deptmodel.</param> 
    /// <returns></returns> 
    public int SaveUserDetails(User Usermodel) 
    { 
     try 
     { 
      if (Usermodel != null) 
      { 
       _userRepository.Add(Usermodel); 
       return 1; 
      } 
      else 
       return 0; 
     } 
     catch 
     { 
      throw; 
     } 

    } 

    /// <summary> 
    /// Updates the User details. 
    /// </summary> 
    /// <param name="User">The deptmodel.</param> 
    /// <returns></returns> 
    public int UpdateUserDetails(User Usermodel) 
    { 
     try 
     { 
      if (Usermodel != null) 
      { 
       _userRepository.Update(Usermodel); 
       return 1; 
      } 
      else 
       return 0; 
     } 
     catch 
     { 
      throw; 
     } 
    } 

    /// <summary> 
    /// Deletes the User details. 
    /// </summary> 
    /// <param name="Id">The code identifier.</param> 
    /// <returns></returns> 
    public int DeleteUserDetails(int Id) 
    { 
     try 
     { 
      User Usermodel = _userRepository.GetById(Id); 
      if (Usermodel != null) 
      { 
       _userRepository.Delete(Usermodel); 
       return 1; 
      } 
      else 
       return 0; 
     } 
     catch 
     { 
      throw; 
     } 
    } 

} 

5.Ahora todo listo para su modelo de repositorio y se puede acceder a todos los datos de usuario del controlador:

//Here is the User Controller 
public class UserProfileController : Controller 
{ 

    IUserService _userservice; 
    public CustomerProfileController(IUserService userservice) 
    { 
     this._userservice = userservice; 
    } 

    [HttpPost] 
    public ActionResult GetAllUsers(int id) 
    { 
    User objUser=new User(); 

    objUser = _userservice.GetAllUsers().Where(x => x.Id == id).FirstOrDefault(); 

    } 
} 

Saludos !!

+0

Parece que mucho de este código es ya implementado para usted con DbContext. No estoy seguro de cómo este enfoque tiene sentido más. –

Cuestiones relacionadas