65 votes

Meilleur modèle de référentiel pour ASP.NET MVC

J'ai appris récemment ASP.NET MVC (j'adore ça). Je travaille avec une société qui utilise l'injection de dépendance pour charger une instance de Référentiel dans chaque demande, et je suis familier avec l'utilisation de ce référentiel.

Mais maintenant, je suis en train d'écrire quelques applications MVC de mon propre. Je ne comprends pas tout le comment et le pourquoi du référentiel de mon entreprise utilise, et je suis en train de décider de la meilleure approche pour mettre en œuvre l'accès aux données.

Je suis à l'aide de C# et du Framework Entity (avec toutes les dernières versions).

Je vois trois approches générales pour la manipulation des données d'accès.

  1. Regular DB contexte au sein d'une instruction d'utilisation chaque fois que j'ai accès aux données. C'est simple et ça fonctionne bien. Cependant, si les deux endroits ont besoin de lire les mêmes données dans une requête, les données doivent être lues deux fois. (Avec un référentiel unique pour chaque requête, le même exemple, pourrait être utilisé dans les deux endroits, et je comprends à la lecture du deuxième serait tout simplement de renvoyer les données à partir de la première lecture.)

  2. Typique d'un modèle de référentiel. Pour des raisons que je ne comprends pas, ce modèle typique implique la création d'une classe wrapper pour chaque table de la base de données. Qui semble mauvais pour moi. En fait, depuis qu'ils sont mis en œuvre aussi comme des interfaces, j'avais techniquement, la création de deux classes wrapper pour chaque table. EF crée des tables pour moi. Je ne crois pas que cette approche a du sens.

  3. Il y a aussi un générique modèle de référentiel où un seul référentiel de la classe est créée pour servir tous les objets de l'entité. Cela fait beaucoup plus de sens pour moi. Mais est-il judicieux d'autres? Le lien ci-dessus de la meilleure approche?

J'aimerais obtenir quelques commentaires d'autres personnes sur ce sujet. Êtes-vous d'écrire votre propre référentiel, à l'aide de l'un de ceux ci-dessus, ou de faire quelque chose de différent. S'il vous plaît partager.

36voto

HackedByChinese Points 18294

J'ai utilisé un mélange #2 et #3, mais je préfère un strict générique référentiel si possible (plus strictes que même suggéré dans le lien n ° 3). #1 n'est pas bon parce qu'il joue mal avec les tests unitaires.

Si vous avez un petit domaine ou le besoin de resserrer les entités que votre domaine permet d'être interrogé, je suppose, #2 ou #3 qui définit une entité spécifique du référentiel des interfaces qui eux-mêmes de mettre en œuvre un référentiel générique - sens. Cependant, je trouve que c'est épuisant et inutile d'écrire une interface et une implémentation concrète pour chaque entité, je veux interroger. Ce bon est - public interface IFooRepository : IRepository<Foo> (encore une fois, à moins que j'ai besoin de contraindre les développeurs à un ensemble de permis d'agrégation des racines)?

Je viens de définir mon générique référentiel d'interface, Add, Remove, Get, GetDeferred, Count, et Find méthodes (Trouver retourne un IQueryable interface permettant de LINQ), créer un béton générique de mise en œuvre, et l'appeler un jour. Je compte fortement sur l' Find et ainsi de LINQ. Si j'ai besoin d'utiliser une requête spécifique plus d'une fois, j'ai utiliser des méthodes d'extension et d'écrire la requête à l'aide de LINQ.

Ce couvre plus de 95% de ma persistance des besoins. Si j'ai besoin d'effectuer une sorte de persistance d'action qui ne peut pas être fait de façon générique, j'utilise un home-grown ICommand API. Par exemple, dire que je suis en train de travailler avec NHibernate et j'ai besoin d'effectuer une requête complexe dans le cadre de mon domaine, ou peut-être que je dois faire une commande en vrac. L'API ressemble à peu près comme ceci:

// 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;
}

Maintenant, je peux créer une interface pour représenter une commande spécifique.

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

Je peux créer une mise en œuvre concrète et l'utilisation SQL brut, NHibernate HQL, que ce soit, et l'enregistrer avec mon service locator.

Maintenant, dans ma logique d'entreprise, je peux faire quelque chose comme ceci:

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

var overdueAccounts = query.Execute();

Vous pouvez également utiliser une Spécification de modèle avec IQuery de construire significative, de l'entrée utilisateur pilotée par les requêtes, plutôt que d'avoir une interface avec des millions de confusion propriétés, mais cela suppose que vous ne trouvez pas la spécification du modèle confus dans son propre droit ;).

Une dernière pièce du puzzle est quand votre dépôt doit faire des pré - et post opération de référentiel. Maintenant, vous pouvez très facilement créer une mise en œuvre de votre générique référentiel pour une entité spécifique, puis remplacer la méthode appropriée(s) et faire ce que vous devez faire, et de mettre à jour votre Cio ou le service localisateur d'inscription et être fait avec elle.

Cependant, parfois, cette logique transversale et difficile à mettre en œuvre par la substitution d'un référentiel de méthode. J'ai donc créé IRepositoryBehavior, ce qui est essentiellement un récepteur d'événements. (Ci-dessous une ébauche de définition sur le dessus de ma tête)

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

Maintenant, ces comportements peuvent être n'importe quoi. Audit, contrôle de sécurité, soft-supprimer, l'application de contraintes de domaine, validation, etc. J'ai créer un comportement, l'enregistrer avec le Cio ou le service de localisation, et de modifier mon générique référentiel de prendre dans une collection d'inscrits IRepositoryBehaviors, et vérifier chaque comportement à contre-courant type de référentiel et l'envelopper l'opération dans le pré/post gestionnaires pour chaque comportement.

Voici un exemple de soft-supprimer comportement (soft-supprimer signifie que lorsque quelqu'un vous demande de supprimer une entité, nous venons de le marquer comme étant supprimés de sorte qu'il ne peut pas être retourné à nouveau, mais n'est jamais vraiment physiquement supprimé).

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.
   }
}

Oui, il s'agit essentiellement d'une procédure simplifiée et abstraite de la mise en œuvre de NHibernate est des écouteurs d'événement, mais c'est pourquoi je l'aime. A) je peux unité de test d'un comportement sans apporter de NHibernate dans l'image B) je peux utiliser ces comportements à l'extérieur de NHibernate (dire le référentiel de mise en œuvre du client qui encapsule le RESTE des appels de service) C) NH écouteurs d'événement peuvent être une vraie douleur dans le cul ;)

12voto

Craig Points 15049

Je recommanderais le numéro 1, avec quelques mises en garde. Le numéro 2 est ce qui semble être la plus commune, mais dans mon expérience, le référentiel se termine juste en haut le fouillis d'un dépotoir pour les requêtes. Si vous utilisez un générique du dépôt (2) il est juste un mince wrapper autour de la DBContext, un peu inutile vraiment, sauf si vous prévoyez sur l'évolution de l'ORM (mauvaise idée).

Mais quand j'ai accès DBContext directement, je préfère utiliser un Tuyaux et les Filtres de façon à ce que vous pouvez réutiliser la logique commune, quelque chose comme

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

Le ByPhoneNumber et Par l'Organisation sont juste des méthodes d'extension.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X