Base de datos de diseño de la capa de abstracción - Uso IRepository de la manera correcta?

StackOverflow https://stackoverflow.com/questions/2347337

Pregunta

Estoy en el proceso de diseño de mi aplicación ASP.NET MVC y me encontré con un par de ideas interesantes.

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

Ahora sé lo que es todo haciendo, empezando a mirar a mi diseño y la maravilla si es la mejor manera de ir actual.

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

En la actualidad, cada vez que quiero carga de consulta de la tabla / usuario en la base de datos, hago algo en la línea de lo 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 1 de cada aplicación), así que no estoy positivo de la mejor curso de acción.

He encontrado una gran pregunta sobre el uso de un repositorio genérico interfaz IRepository<T> aquí y tienen también parece la idea de un RepositoryFactory estática en una serie de blogs. Básicamente sólo el 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 la gente lo hace allí Apps, y lo que es considerado una buena práctica.

¿Las personas tienen repositorys individuales en función de cada tabla (IUserRepository)?
¿Utilizan un IRepository<T> genérico?
¿Utilizan una fábrica repositorio estático?
O algo más completo?

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 crear una instancia de un nuevo IRepository cada vez que quiera usarlo?

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

Estoy más curiosidad por saber lo que la gente hace en sus aplicaciones MVC o lo que creen que es una buena idea.

¿Fue útil?

Solución

Algunos problemas muy evidentes con la idea de un IRepository<T> genérica:

  • Se supone que cada entidad utiliza el mismo tipo de clave, lo cual no es cierto en casi cualquier sistema no trivial. Algunas entidades van a utilizar los GUID, otros pueden tener algún tipo de clave natural y / o compuesto. NHibernate puede apoyar esta bastante bien, pero LINQ to SQL es bastante malo en ello -. Usted tiene que escribir un acuerdo de código de buena hacker hacer el mapeo automático de las teclas

  • Esto significa que cada repositorio sólo puede tratar con exactamente un tipo de entidad y sólo es compatible con las operaciones más triviales. Cuando un repositorio es relegado a un simple ejemplo CRUD envoltorio que tiene muy poco uso en absoluto. Es lo mismo que acaba de entregar al cliente una IQueryable<T> o Table<T>.

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

El punto del patrón de repositorio es crear un patrones abstracción más común de acceso a datos . Creo que algunos programadores se aburren con la creación de repositorios por lo que dicen "Hey, lo sé, voy a crear un über-repositorio que puede manejar cualquier tipo de entidad!" Lo cual es genial, excepto que el depósito es bastante inútil para el 80% de lo que estamos tratando de hacer. bien sea como clase base / interfaz, pero si ese es el alcance total de la obra que lo hace, entonces simplemente estás siendo perezosos (y que garantizan futuros dolores de cabeza).


Lo ideal sería que podría comenzar con un repositorio genérico que es como la siguiente:

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

Se dará cuenta de que este no tiene una función List o GetAll - Eso es porque es absurdo pensar que es aceptable para recuperar los datos de una tabla completa de una vez en cualquier parte del código. Esto es cuando usted necesita para empezar 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 - se entiende la idea. De esta manera tenemos el repositorio "genérico" de si alguna vez realmente se necesita la increíblemente simplista recuperar-by-id semántica, pero en general nunca estamos realmente va a pasar que todo, ciertamente no a una clase controlador.


Ahora en cuanto a los controladores, lo que 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 necesidades a tomar su repositorio desde el mundo exterior . La razón por la que ha creado estos repositorios es lo que puede hacer algún tipo de inversión de control. Su objetivo final aquí es ser capaz de cambiar a cabo un repositorio para otro -. Por ejemplo, para hacer las pruebas unitarias, o si decide cambiar de LINQ to SQL para 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 cualquier repositorio-obras de construcción en allí, es la creación de acoplamiento que realmente no quiere. La razón de que los controladores de ejemplo ASP.NET MVC tienen constructores sin parámetros que crea depósitos de hormigón es que los sitios tienen que ser capaces de compilar y ejecutar sin forzar a configurar todo un marco de inyección de dependencias.

Sin embargo, en un lugar de producción, si no está de paso en el repositorio de la dependencia a través de un constructor o propiedad pública, entonces usted está perdiendo el tiempo con los repositorios de todo, debido a que los controladores están siendo estrechamente unidas a la base de datos lAyer. 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. No se supone que este método de ensayo para hacer cualquier acceso a los datos, sólo se asegura de que el controlador está invocando el método repositorio correcto basado en la acción.


Esto no es DI, sin embargo, que la mente, esto es sólo fingiendo / burlona. Donde DI entra en escena es cuando se decide que LINQ to SQL no está haciendo lo suficiente para ti y que realmente quiere el HQL en NHibernate, pero te va a tomar 3 meses al puerto todo lo más, y quiere 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 ahí está, ahora todo lo que depende de IOrderRepository está utilizando la versión NHibernate, sólo has tenido que cambiar una línea de código en lugar de potencialmente cientos de líneas. Y nos estamos quedando el LINQ to SQL y NHibernate, lado a lado, portar la funcionalidad sobre la pieza a pieza sin romper nada en el medio.


Así que para resumir todos los puntos que he hecho:

  1. no se basan estrictamente en una interfaz genérica IRepository<T>. La mayor parte de la funcionalidad que desea de un repositorio es específica , no genérico . Si desea incluir un IRepository<T> en los niveles superiores de la jerarquía de clases / interfaz, que está muy bien, pero los controladores deben depender de específica repositorios para que no acaban de tener que cambiar el código en 5 diferentes lugares cuando se encuentran que el repositorio genérico falta métodos importantes.

  2. Controladores debería aceptar depósitos desde el exterior, no crea su propia cuenta. Este es un paso importante en la eliminación de acoplamiento y la mejora de la capacidad de prueba.

  3. Normalmente querrá cablear los controladores utilizando un marco de inyección de dependencias, y muchos de ellos se pueden integrar perfectamente con ASP.NET MVC. Si eso es demasiado para que usted tome en, entonces por lo menos usted debe utilizar algún tipo de proveedor de servicios estática forma que puede centralizar toda la lógica de repositorio de la creación. (A la larga, probablemente encontrará que es más fácil simplemente aprender y utilizar un marco DI).

Otros consejos

¿Las personas tienen repositorys individuales sobre la base de cada tabla (IUserRepository)? Me tienden a tener un repositorio para cada conjunto, no cada tabla.

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

¿Utilizan una fábrica repositorio estático? Yo prefiero la inyección de una instancia del repositorio a través de un recipiente COI

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 utilizo también un dedicado un ITRepository para cada agregada para la operación que son distintos a este repositorio. Por ejemplo para el usuario voy a utilizar IUserRepository añadir método que son distintos a UserRepository:

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

La aplicación va a tener este aspecto:

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 instancia utilizando el patrón de inyección de dependencias utilizando la fábrica controlador personalizado. Estoy usando StructureMap como mi capa de inyección de dependencias.

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

Te lo sugiero a vistazo en CodeCampServer estructura, se puede aprender mucho de ella.

Otro proyecto que se puede aprender lado a otro es que me pueda ayudar . Lo cual no cavar lo suficiente en ella todavía.

¿Las personas tienen repositorys individuales sobre la base de cada tabla (IUserRepository)?

Sí, esta fue 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 DDD Entidad / Valor esquema I creado IRepositoryEntity / IRepositoryValue y una IRepository genérico para otras cosas.

¿Utilizan una fábrica repositorio estático?

Sí y no: yo uso un COI contenedor 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 pareció tan grande que estamos creando actualmente nuestro marco de toda la empresa en este modelo (sí, es una empresa joven). Esto es definitivamente algo que vale la pena probar, incluso si siento que los marcos de este tipo se dará a conocer por los principales actores.

Se puede encontrar un excelente Genérico Repopsitory biblioteca que fue escrito para que pueda ser utilizado como un WebForms asp: ObjectDataSource en CodePlex: MultiTierLinqToSql

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

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top