Base de données de conception couche d'abstraction - En utilisant IRepository la bonne façon?

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

Question

Je suis en train de concevoir mon application ASP.NET MVC et j'ai couru à travers quelques réflexions intéressantes.

De nombreux échantillons que j'ai vu décrire et utiliser le modèle du référentiel (de IRepository) donc c'est la façon dont je l'ai fait pendant que j'apprenais MVC.

Maintenant, je sais ce qu'il en est fait, je commence à regarder ma conception actuelle et je me demande si elle est la meilleure façon d'aller.

Actuellement, j'ai un IUserRepository de base, qui définit des méthodes telles que FindById(), SaveChanges(), etc.

À l'heure actuelle, chaque fois que je veux charger / interroger la table d'utilisateur dans le DB, je fais quelque chose le long des lignes de ce qui suit:

    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");
    }

Maintenant, je ne comprends pas bien comment fonctionne MVC en ce qui concerne la création d'un contrôleur lorsqu'un utilisateur crée un lien (ne sais pas s'il y a 1 contrôleur par utilisateur ou 1 contrôleur par application), donc je ne suis pas positif de la meilleur plan d'action.

J'ai trouvé une grande question concernant l'utilisation d'une interface de référentiel générique IRepository<T> et ont semblent aussi l'idée d'une RepositoryFactory statique sur un certain nombre de blogs. Fondamentalement seulement 1 instance du référentiel est toujours conservée et il est obtenu par cette usine

Ma question tourne autour de la façon dont les gens font là-dedans des applications, et ce qui est considéré comme une bonne pratique.

Les gens ont repositorys individuels en fonction de chaque table (IUserRepository)?
Est-ce qu'ils utilisent un IRepository<T> générique?
Est-ce qu'ils utilisent une usine de stockage statique?
Ou quelque chose d'autre complètement?

EDIT: Je viens de réaliser que je devrais demander probablement aussi:

Le fait d'avoir un IRepository privé sur chaque contrôleur une bonne façon d'aller? ou devrais-je instancier un nouveau IRepository chaque fois que je veux l'utiliser?

BOUNTY EDIT: Je commence une prime pour obtenir des perspectives plus (pas que Tim n'a pas été utile).

Je suis plus curieux de savoir ce que les gens font dans leurs applications MVC ou ce qu'ils pensent est une bonne idée.

Était-ce utile?

La solution

Certains problèmes très évidents avec l'idée d'un IRepository<T> générique:

  • Il suppose que chaque entité utilise le même type de clé, ce qui est vrai dans presque tous les systèmes non-trivial. Certaines entités utiliseront GUIDs, d'autres peuvent avoir une sorte de clé naturelle et / ou composite. NHibernate peut soutenir ce assez bien, mais LINQ to SQL est assez mauvaise à elle -. Vous devez écrire beaucoup de code de hackish faire la cartographie automatique des clés

  • Cela signifie que chaque dépôt ne peut traiter exactement un type d'entité et ne supporte que les opérations les plus triviales. Lorsqu'un dépôt est reléguée à une si simple wrapper CRUD il a très peu d'utilité du tout. Vous pourriez tout aussi bien remettre au client un IQueryable<T> ou Table<T>.

  • Il suppose que vous effectuez exactement les mêmes opérations sur toutes les entités. En réalité, cela va être très loin de la vérité. Bien sûr, peut-être vous voulez obtenir que Order par son ID, mais plus probablement vous voulez obtenir une liste des objets Order pour un client spécifique et dans une plage de dates. La notion d'un IRepository<T> totalement générique ne permet pas le fait que vous voudrez certainement effectuer différents types de requêtes sur différents types d'entités.

Le point de l'ensemble du motif dépôt est de créer un abstraction sur des modèles d'accès de données communs. Je pense que certains programmeurs se lassent de créer des dépôts afin qu'ils disent: « Hé, je sais, je vais créer un über-référentiel qui peut gérer tout type d'entité! » Ce qui est génial, sauf que le dépôt est à peu près inutile pour 80% de ce que vous essayez de faire. Il est très bien en tant que classe de base / interface, mais si c'est l'étendue du travail que vous faites alors vous êtes juste être paresseux (et garantissant un mal de tête).


Idéalement, je pourrais commencer par un référentiel générique qui ressemble à ceci:

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

Vous remarquerez que ce ne pas ont une fonction List ou GetAll - c'est parce qu'il est absurde de penser qu'il est acceptable de récupérer les données à partir d'une table entière à la fois partout dans le code. C'est quand vous devez commencer à aller dans des dépôts spécifiques:

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

Et ainsi de suite - vous voyez l'idée. De cette façon, nous avons le référentiel « générique » car si nous jamais réellement besoin de la sémantique retrieve-par-id incroyablement simpliste, mais en général, nous ne pourrons jamais en fait passer que autour, certainement pas à une classe de contrôleur.


Maintenant que pour les contrôleurs, vous devez faire ce droit, sinon vous avez à peu près réduit à néant tout le travail que vous venez de faire à mettre ensemble tous les dépôts.

Un contrôleur doit prendre son dépôt du monde extérieur . La raison pour laquelle vous avez créé ces dépôts est donc vous pouvez faire une sorte de l'inversion de contrôle. Votre but ultime est ici pour être en mesure d'échanger sur un référentiel pour une autre -. Par exemple, pour faire des tests unitaires, ou si vous décidez de passer de LINQ to SQL à Entity Framework à un moment donné dans l'avenir

Un exemple de ce principe est:

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 d'autres termes, le contrôleur a aucune idée de la façon de créer un référentiel , ne le devrait pas. Si vous avez des garde-construction en cours là-bas, cela crée un couplage que vous ne voulez vraiment pas. La raison pour laquelle les contrôleurs d'échantillons ASP.NET MVC ont des constructeurs qui crée des dépôts sans paramètre béton est que les sites doivent être en mesure de compiler et exécuter sans vous forcer à mettre en place un cadre d'injection de dépendance entière.

Mais dans un site de production, si vous n'êtes pas dans la dépendance de passage du référentiel par un constructeur ou d'un bien public, alors vous perdez votre temps d'avoir des dépôts à tous, parce que les contrôleurs sont toujours étroitement couplés à la base de données layer. Vous devez être en mesure d'écrire du code de test comme ceci:

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

Vous ne pouvez pas le faire si votre OrderController va hors et créer son propre référentiel. Cette méthode d'essai n'est pas censé faire tout accès de données, il est tout simplement vous que le contrôleur est d'appeler la méthode référentiel approprié en fonction de l'action.


Ce n'est pas encore DI, vous l'esprit, ceci est juste simulez / moqueur. Où DI est dans l'image est quand vous décidez que LINQ to SQL ne fait pas assez pour vous et que vous voulez vraiment le HQL dans NHibernate, mais il va vous prendre 3 mois pour tout le port sur, et vous voulez être en mesure de faire un dépôt à la fois. Ainsi, par exemple, en utilisant un cadre DI comme Ninject , tout ce que vous avez à faire est de changer ceci:

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

Pour:

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

Et vous, maintenant tout ce qui dépend de IOrderRepository utilise le la version NHibernate, vous avez seulement dû changer une ligne code par opposition à potentiellement des centaines de lignes. Et nous courons le LINQ to SQL et versions NHibernate côte à côte, la fonctionnalité de portage sur pièce par pièce sans jamais rien casser au milieu.


Donc, pour résumer tous les points que j'ai fait:

  1. Ne vous fiez pas uniquement sur une interface générique IRepository<T>. La plupart des fonctionnalités que vous voulez à partir d'un référentiel est spécifique , pas générique . Si vous souhaitez inclure un IRepository<T> aux niveaux supérieurs de la hiérarchie de classe / interface, qui est très bien, mais les contrôleurs devraient dépendre de spécifique dépôts afin de ne pas avoir à changer votre code dans 5 endroits quand vous trouvez que le référentiel générique est manquant des méthodes importantes.

  2. Les contrôleurs doivent accepter les dépôts de l'extérieur, pas créer leur propre. Ceci est une étape importante dans l'élimination de couplage et l'amélioration de la testabilité.

  3. Normalement, vous voulez câbler les contrôleurs à l'aide d'un cadre d'injection de dépendance, et beaucoup d'entre eux peut être intégré de façon transparente avec ASP.NET MVC. Si c'est trop pour vous de prendre en, puis à tout le moins, vous devriez utiliser une sorte de fournisseur de services statique afin que vous puissiez centraliser l'ensemble de la logique référentiel de création. (À long terme, vous trouverez probablement plus facile à apprendre et à utiliser simplement un cadre DI).

Autres conseils

Les gens ont repositorys individuels en fonction de chaque table (IUserRepository)? Je tendance à avoir un dépôt pour chaque agrégat, et non chaque table.

Est-ce qu'ils utilisent un IRepository générique? si possible, oui

Est-ce qu'ils utilisent une usine de stockage statique? Je préfère l'injection d'une instance par dépôt d'un conteneur du CIO

Voici comment je l'utilise. J'utilise IRepository pour toutes les opérations qui sont communes à tous mes dépôts.

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

et I utilise aussi un consacré un ITRepository pour chaque agrégat pour l'opération qui sont distincts à ce référentiel. Par exemple, pour l'utilisateur, je vais utiliser IUserRepository ajouter méthode qui sont propres à UserRepository:

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

La mise en œuvre ressemblera à ceci:

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 dans le UserController regardera comme:

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 le dépôt est instancié à l'aide modèle d'injection de dépendance à l'aide de l'usine contrôleur personnalisé. J'utilise StructureMap comme ma couche d'injection de dépendance.

La couche de base est NHibernate. ISession est la porte d'entrée à la base de données de cette session.

Je vous suggère de regarder la structure CodeCampServer , vous pouvez apprendre beaucoup de celui-ci.

Un autre projet que vous pouvez apprendre FRO est Qui peut me aider . Ce que je ne creuse pas assez encore.

Les gens ont repositorys individuels en fonction de chaque table (IUserRepository)?

Oui, ce fut le meilleur choix pour 2 raisons:

  • mon DAL est basée sur LINQ to Sql (mais mes DTO sont des interfaces basées sur les entités LTS)
  • les opérations effectuées sont atomique (addition est une opération atomique, l'épargne est une autre, etc.)

Est-ce qu'ils utilisent un IRepository générique?

Oui, exclusivement , inspiré de DDD système Entité / valeur que j'ai créé IRepositoryEntity / IRepositoryValue et un IRepository générique pour d'autres choses.

utilisent-ils une usine de stockage statique?

Oui et non: j'utiliser un conteneur du CIO invoqué par une classe statique. Eh bien ... on peut dire que c'est une sorte d'usine.

Remarque : J'ai conçu cette architecture moi-même et un de mes collègues trouvé si grand que nous créons actuellement notre cadre global de l'entreprise sur ce modèle (oui, il est une jeune entreprise). C'est certainement quelque chose la peine d'essayer, même si je pense que les cadres de ce genre seront publiés par les principaux acteurs.

Vous pouvez trouver un excellent Repopsitory générique bibliothèque qui a été écrit pour lui permettre d'être utilisé comme asp WebForms: ObjectDataSource sur CodePlex: MultiTierLinqToSql

Chacun de mes contrôleurs disposent de dépôts privés pour les actions dont ils ont besoin pour soutenir.

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top