2010-02-27 12 views
14

Estoy en el proceso de diseño de mi aplicación ASP.NET MVC y me encontré con un par de pensamientos interesantes.Diseño de la capa de abstracción de la base de datos: ¿usar IRepository correctamente?

Muchas muestras que he visto describen y usan el patrón Repositorio (IRepository) así que esta es la manera en que lo hice mientras estaba aprendiendo MVC.

Ahora sé lo que hace todo, empiezo a ver mi diseño actual y me pregunto si es la mejor manera de hacerlo.

Actualmente tengo un básico IUserRepository, que define métodos como FindById(), SaveChanges(), etc.

En la actualidad, cada vez que quiero cargar/consultar la tabla de usuario en la base de datos, hago algo en la línea de la siguiente:

private IUserRepository Repository; 

    public UserController() 
     : this(new UserRepository()) 
    { } 

    [RequiresAuthentication] 
    [AcceptVerbs(HttpVerbs.Get)] 
    public ActionResult Edit(string ReturnUrl, string FirstRun) 
    { 
     var user = Repository.FindById(User.Identity.Name); 

     var viewModel = Mapper.Map<User, UserEditViewModel>(user); 
     viewModel.FirstRun = FirstRun == "1" ? true : false; 

     return View("Edit", viewModel); 
    } 

    [AcceptVerbs(HttpVerbs.Post), ValidateAntiForgeryToken(Salt = "SaltAndPepper")] 
    public ActionResult Edit(UserEditViewModel viewModel, string ReturnUrl) 
    { 
     //Map the ViewModel to the Model 
     var user = Repository.FindById(User.Identity.Name); 

     //Map changes to the user 
     Mapper.Map<UserEditViewModel, User>(viewModel, user); 

     //Save the DB changes 
     Repository.SaveChanges(); 

     if (!string.IsNullOrEmpty(ReturnUrl)) 
      return Redirect(ReturnUrl); 
     else 
      return RedirectToAction("Index", "User"); 
    } 

Ahora no entiendo completamente cómo funciona MVC en lo que respecta a la creación de un controlador cuando un usuario crea un enlace (no estoy seguro si hay 1 regulador por usuario o por aplicación del controlador 1), por lo que No estoy seguro del mejor curso de acción.

Encontré una gran pregunta con respecto al uso de una interfaz genérica de repositorio IRepository<T>here y también parece ser la idea de una estática RepositoryFactory en una serie de blogs. Básicamente, solo 1 instancia del repositorio se mantiene siempre y se obtiene a través de esta fábrica

Así que mi pregunta gira en torno a cómo las personas lo hacen en sus aplicaciones, y qué se considera una buena práctica.

¿Las personas tienen repositorios individuales basados ​​en cada tabla (IUserRepository)?
¿Utilizan un genérico IRepository<T>?
¿Utilizan una fábrica de repositorios estáticos?
¿O algo completamente diferente?

EDIT: me he dado cuenta de que probablemente debería pedir así:

está teniendo un IRepository privada en cada controlador de un buen camino a seguir? ¿O debería instanciar un nuevo IRepository cada vez que quiero usarlo?

BOUNTY EDIT: Estoy empezando una recompensa para obtener algunas perspectivas más (no es que Tim no fue útil).

Tengo más curiosidad por saber qué hacen las personas en sus aplicaciones MVC o qué creen que es una buena idea.

+1

Genérico Static repositories! ¡HURRA! SingletonSquared, ¡justo lo que necesitamos! ;-) –

+0

@Sky, uno nunca puede tener suficiente. Personalmente estoy pensando en repositorios genéricos parcialmente parciales. –

Respuesta

24

algunos problemas muy evidentes con la idea de un genérico IRepository<T>:

  • Se supone que cada entidad utiliza el mismo tipo de clave, lo cual no es cierto en casi cualquier sistema no trivial. Algunas entidades usarán GUID, otras pueden tener algún tipo de clave natural y/o compuesta. NHibernate puede soportar esto bastante bien, pero Linq to SQL es bastante malo en eso; tienes que escribir una gran cantidad de código de hack para hacer un mapeo de teclas automático.

  • Significa que cada repositorio solo puede tratar exactamente con un tipo de entidad y solo admite las operaciones más triviales. Cuando un repositorio se relega a un contenedor CRUD tan simple, tiene muy poco uso. También podría entregarle al cliente un IQueryable<T> o Table<T>.

  • Supone que realiza exactamente las mismas operaciones en cada entidad. En realidad, esto va a estar muy lejos de la verdad. Claro, tal vez quiere obtener ese Order por su ID, pero es más probable que quiera obtener una lista de objetos Order para un cliente específico y dentro de algún rango de fechas. La noción de un IRepository<T> totalmente genérico no permite el hecho de que seguramente querrá realizar diferentes tipos de consultas en diferentes tipos de entidades.

El punto del patrón de repositorio es crear una abstracción sobre los patrones de acceso de datos común. Creo que algunos programadores se aburren de crear repositorios, por lo que dicen "¡Oye, lo sé, crearé un über-repositorio que pueda manejar cualquier tipo de entidad!" Lo cual es genial, excepto que el repositorio es bastante inútil para el 80% de lo que estás tratando de hacer. Está bien como una clase/interfaz base, pero si esa es toda la extensión del trabajo que haces, entonces estás siendo perezoso (y garantizando futuros dolores de cabeza).


Lo ideal sería que podría comenzar con un repositorio genérico que se ve algo como esto:

public interface IRepository<TKey, TEntity> 
{ 
    TEntity Get(TKey id); 
    void Save(TEntity entity); 
} 

Se dará cuenta de que este no lo hace tienen una función List o GetAll - eso es porque es absurdo pensar que es aceptable recuperar los datos de una tabla completa a la vez en cualquier parte del código. Aquí es cuando necesita comenzar a entrar en repositorios específicos:

public interface IOrderRepository : IRepository<int, Order> 
{ 
    IEnumerable<Order> GetOrdersByCustomer(Guid customerID); 
    IPager<Order> GetOrdersByDate(DateTime fromDate, DateTime toDate); 
    IPager<Order> GetOrdersByProduct(int productID); 
} 

Y así sucesivamente - ya entendió. De esta manera tenemos el repositorio "genérico" para si realmente necesitamos la semántica de recuperación por ID increíblemente simplista, pero en general nunca vamos a pasar eso, ciertamente no a una clase de controlador.


Ahora en cuanto a los controladores, usted tiene que hacer esto derecha, si no has negado casi todo el trabajo que has hecho en la elaboración de todos los repositorios.

Un controlador necesita tomar su repositorio del mundo exterior. La razón por la que creó estos repositorios es para poder realizar algún tipo de Inversión de control. Su objetivo final aquí es poder cambiar un repositorio por otro, por ejemplo, para realizar pruebas unitarias, o si decide cambiar de Linq a SQL a Entity Framework en algún momento en el futuro.

Un ejemplo de este principio es:

public class OrderController : Controller 
{ 
    public OrderController(IOrderRepository orderRepository) 
    { 
     if (orderRepository == null) 
      throw new ArgumentNullException("orderRepository"); 
     this.OrderRepository = orderRepository; 
    } 

    public ActionResult List(DateTime fromDate, DateTime toDate) { ... } 
    // More actions 

    public IOrderRepository OrderRepository { get; set; } 
} 

En otras palabras, el controlador tiene ni idea de cómo crear un repositorio, ni debe. Si tiene alguna construcción de repositorio allí, está creando un acoplamiento que realmente no desea. La razón por la que los controladores de muestra ASP.NET MVC tienen constructores sin parámetros que crean repositorios concretos es porque los sitios deben poder compilarse y ejecutarse sin forzarle a configurar un marco de inyección de dependencias completo.

Pero en un sitio de producción, si no transfiere la dependencia del repositorio a través de un constructor o propiedad pública, está perdiendo el tiempo con repositorios, porque los controladores aún están estrechamente conectados a la capa de la base de datos . Tienes que ser capaz de escribir código de prueba como esta:

[TestMethod] 
public void Can_add_order() 
{ 
    OrderController controller = new OrderController(); 
    FakeOrderRepository fakeRepository = new FakeOrderRepository(); 
    controller.OrderRepository = fakeRepository; //<-- Important! 
    controller.SubmitOrder(...); 
    Assert.That(fakeRepository.ContainsOrder(...)); 
} 

No se puede hacer esto si su OrderController va fuera y crear su propio repositorio. Se supone que este método de prueba no permite el acceso a datos, solo se asegura de que el controlador invoca el método de depósito correcto en función de la acción.


Esto todavía no es DI, es solo cuestión de fingir/burlarse. Donde DI entra en escena es cuando decides que Linq to SQL no está haciendo lo suficiente para ti y realmente quieres HQL en NHibernate, pero te va a tomar tres meses llevar a cabo todo, y quieres ser capaz de hacer esto un repositorio a la vez. Así, por ejemplo, utilizando un marco DI como Ninject, todo lo que tiene que hacer es cambiar esta situación:

Bind<ICustomerRepository>().To<LinqToSqlCustomerRepository>(); 
Bind<IOrderRepository>().To<LinqToSqlOrderRepository>(); 
Bind<IProductRepository>().To<LinqToSqlProductRepository>(); 

Para:

Bind<ICustomerRepository>().To<LinqToSqlCustomerRepository>(); 
Bind<IOrderRepository>().To<NHibernateOrderRepository>(); 
Bind<IProductRepository>().To<NHibernateProductRepository>(); 

y ya está, ahora todo lo que depende de IOrderRepository está utilizando En la versión de NHibernate, solo ha tenido que cambiar una línea de código en lugar de cientos de líneas. Y estamos ejecutando las versiones de Linq a SQL y NHibernate una al lado de la otra, portando la funcionalidad más de una pieza por pieza sin romper nada en el medio.


Entonces, para resumir todos los puntos que he hecho:

  1. No se base estrictamente en una interfaz genérica IRepository<T>. La mayor parte de la funcionalidad que desea de un repositorio es específico, no genérico. Si desea incluir un IRepository<T> en los niveles superiores de la jerarquía de clase/interfaz, está bien, pero los controladores deben depender de los repositorios específicos para no tener que cambiar el código en 5 lugares diferentes cuando lo encuentre. al repositorio genérico le faltan métodos importantes.

  2. Los controladores deben aceptar repositorios desde el exterior, no crear los propios. Este es un paso importante para eliminar el acoplamiento y mejorar la capacidad de prueba.

  3. Normalmente, querrá conectar los controladores utilizando un marco de Inyección de Dependencia, y muchos de ellos se pueden integrar sin problemas con ASP.NET MVC.Si eso es demasiado para que lo asimile, al menos debería usar algún tipo de proveedor de servicios estáticos para poder centralizar toda la lógica de creación de repositorios. (A la larga, probablemente le resultará más fácil simplemente aprender y usar un marco DI).

+0

esa es una respuesta fantástica, y verdaderamente digno de la recompensa. Gracias por todos los consejos y pensamientos. –

+1

Sé que esta es una vieja pregunta, pero esta es una gran respuesta. Simplemente respondí tantas preguntas que tuve sobre el uso de la interfaz estándar de IRepository. ¡Gracias! –

+0

Excelente respuesta independientemente de la antigüedad. Es un gran consejo. – slimflem

3

¿Las personas tienen repositorios individuales basados ​​en cada tabla (IUserRepository)? Tiendo a tener un repositorio para cada agregado, no cada tabla.

¿Utilizan un IRepository genérico? si es posible, sí

¿Utilizan una fábrica de repositorios estáticos? Prefiero la inyección de una instancia de Repositorio a través de un contenedor IOC

+3

Tengo curiosidad por lo que quiere decir por agregado. –

+2

Creo que la referencia al agregado es con respecto al Modelo de dominio: he leído sobre 'agregados' en el contexto de Diseño impulsado por dominio (DDD), pero supongo que debería aplicarse a todos los dominios. Eric Evans define un agregado como 'un grupo de objetos que pertenecen juntos (un grupo de objetos individuales que representa una unidad)' – Ahmad

+0

es de hecho lo que intento decir –

1

Así es como lo estoy usando.Estoy usando IRepository para todas las operaciones que son comunes a todos mis repositorios.

public interface IRepository<T> where T : PersistentObject 
{ 
    T GetById(object id); 
    T[] GetAll(); 
    void Save(T entity); 
} 

y usar también un dedicado un ITRepository para cada aggregate para la operación que son distintos a este repositorio. Por ejemplo para el usuario a utilizar IUserRepository añadir método que son distintos a UserRepository:

public interface IUserRepository : IRepository<User> 
{ 
    User GetByUserName(string username); 
} 

La aplicación se verá así:

public class UserRepository : RepositoryBase<User>, IUserRepository 
{ 
    public User GetByUserName(string username) 
    { 
     ISession session = GetSession(); 
     IQuery query = session.CreateQuery("from User u where u.Username = :username"); 
     query.SetString("username", username); 

     var matchingUser = query.UniqueResult<User>(); 

     return matchingUser; 
    } 
} 


public class RepositoryBase<T> : IRepository<T> where T : PersistentObject 
{ 
    public virtual T GetById(object id) 
    { 
     ISession session = GetSession(); 
     return session.Get<T>(id); 
    } 

    public virtual T[] GetAll() 
    { 
     ISession session = GetSession(); 
     ICriteria criteria = session.CreateCriteria(typeof (T)); 
     return criteria.List<T>().ToArray(); 
    } 

    protected ISession GetSession() 
    { 
     return new SessionBuilder().GetSession(); 
    } 

    public virtual void Save(T entity) 
    { 
     GetSession().SaveOrUpdate(entity); 
    } 
} 

que en el UserController se verá como:

public class UserController : ConventionController 
{ 
    private readonly IUserRepository _repository; 
    private readonly ISecurityContext _securityContext; 
    private readonly IUserSession _userSession; 

    public UserController(IUserRepository repository, ISecurityContext securityContext, IUserSession userSession) 
    { 
     _repository = repository; 
     _securityContext = securityContext; 
     _userSession = userSession; 
    } 
} 

Que el repositorio se crea una instancia usando el patrón de inyección de dependencia usando fábrica de controlador personalizado. Estoy usando StructureMap como mi capa de inyección de dependencia.

La capa de la base de datos es NHibernate. ISession es la puerta de entrada a la base de datos en esta sesión.

Le sugiero que busque en la estructura CodeCampServer, puede aprender mucho de ella.

Otro proyecto que puedes aprender es Who Can Help Me. Lo cual no cavo lo suficiente todavía.

+0

¿Qué es esta ISesión? –

+0

Olvidé mencionar que usa NHibernate como la capa de persistencia. La interfaz de ISession le brinda lo que necesita para comunicarse con el servidor para esta sesión. –

+0

Ahhh, ahora tiene sentido :) –

0

¿Las personas tienen repositorios individuales basados ​​en cada tabla (IUserRepository)?

Sí, esto era la mejor opción por 2 razones:

  • mi DAL se basa en LINQ to SQL (pero mis dtos son interfaces basadas en las entidades LTS)
  • las operaciones realizadas son atómica (adición es una operación atómica, el ahorro es otro, etc.)

¿Utilizan un IRepository genérico?

Sí, exclusivamente, inspirado en el esquema/Valor DDD Entidad creé IRepositoryEntity/IRepositoryValue y una IRepository genérico para otras cosas.

¿Utilizan una fábrica de repositorio estático?

Sí y no: Yo uso un recipiente COI invocado a través de una clase estática. Bueno ... podemos decir que es una especie de fábrica.

Nota: Diseñé esta arquitectura por mi cuenta y un colega mío lo encontró tan bueno que actualmente estamos creando todo el marco de la compañía en este modelo (sí, es una empresa joven). Definitivamente, esto es algo que vale la pena intentar, incluso si siento que los principales actores divulgarán ese tipo de marcos.

0

puede encontrar un excelente Repopsitory Genérico biblioteca que fue escrito para que pueda ser utilizado como un ASP Web Forms: ObjectDataSource en CodePlex: MultiTierLinqToSql

Cada uno de mis controladores tienen repositorios privados para las acciones necesitan apoyo.

Cuestiones relacionadas