102 votes

PAS en utilisant un modèle de référentiel, utilisez l'ORM tel quel (EF)

J'ai toujours utilisé un modèle de Référentiel, mais pour mon dernier projet, j'ai voulu voir si je pouvais parfait de l'utilisation de l'informatique et ma mise en œuvre d ' "Unité de Travail". Le plus j'ai commencé à creuser, j'ai commencé à me poser la question: "ai-je vraiment besoin?"

Maintenant cela commence avec un couple de commentaires sur Stackoverflow, avec une trace de Ayende Rahien post sur son blog, avec 2 spécifique,

Cela pourrait sans doute faire parler de lui pour toujours et à jamais et elle dépend de différentes applications. Ce que je voudrais savoir,

  1. serait-ce l'approche plus adaptée pour un projet de Entity Framework?
  2. l'utilisation de cette approche est la logique d'affaires toujours en cours dans une couche de service, ou des méthodes d'extension (comme expliqué ci-dessous, je sais, la méthode d'extension est en utilisant le programme des ssna session)?

C'est facilement réalisé en utilisant des méthodes d'extension. Propre, simple et réutilisable.

public static IEnumerable GetAll(
    this ISession instance, Expression<Func<T, bool>> where) where T : class
{
    return instance.QueryOver().Where(where).List();
}

L'utilisation de cette approche et de l' Ninject en DI, ai-je besoin de faire l' Context d'une interface et de les injecter dans mes contrôleurs?

113voto

Ryan Points 614

Je suis allé sur plusieurs routes et créé de nombreuses implémentations de dépôts sur différents projets, et... j'ai jeté l'éponge et renoncé à elle, voici pourquoi.

Le codage de l'exception

Avez-vous un code pour le 1% de chance pour que votre base de données va changer à partir d'une technologie à une autre? Si vous êtes en train de penser au sujet de votre entreprise avenir de l'état et de dire oui c'est une possibilité, alors a) ils doivent avoir beaucoup d'argent pour se permettre de faire une migration vers une autre base de données de la technologie ou b) vous avez le choix d'un DB de la technologie pour le plaisir ou c) quelque chose va terriblement mal avec la première technologie vous avez décidé d'utiliser.

Pourquoi jeter les riches syntaxe LINQ?

LINQ et EF ont été développés afin que vous pourrait faire des trucs sympa avec elle à lire et à parcourir les graphes d'objets. Créer et maintenir un référentiel qui peut vous donner la même flexibilité pour le faire est une tâche aussi monstrueuse. Dans mon expérience, j'ai créé une archive que j'ai TOUJOURS eu de la logique métier de fuite dans le dépôt de la couche d'effectuer des requêtes plus effectuer-ant et/ou de réduire le nombre de requêtes à la base de données.

Je ne veux pas créer une méthode pour chaque permutation d'une requête que j'ai à écrire. Je pourrais tout aussi bien écrire des procédures stockées. Je ne veux pas GetOrder, GetOrderWithOrderItem, GetOrderWithOrderItemWithOrderActivity, GetOrderByUserId, et ainsi de suite... je veux juste la principale entité et le traverse et l'inclure l'objet graphique que j'ai s'il vous plaît.

La plupart des exemples de référentiels sont des conneries

Si vous développez quelque chose de VRAIMENT bare-bones comme un blog ou quelque chose de vos requêtes sont jamais aussi simple que 90% des exemples que vous trouverez sur l'internet entourant le modèle de référentiel. Je ne peux pas insister assez sur ce point! C'est quelque chose que l'on a à ramper dans la boue à la figure. Il y aura toujours qu'une seule requête que les pauses de votre parfaitement pensé référentiel/la solution que vous avez créé, et il n'est pas jusqu'à ce point où vous 2e deviner par vous-même et les techniques de la dette/érosion commence.

Ne faites pas de test de l'unité me bro

Mais qu'en unité de test si je n'ai pas de référentiel? Comment vais-je me moquer? Simple, vous n'avez pas. Permet de regarder sous les deux angles:

Pas de dépôt - vous pouvez Vous moquer de la DbContext à l'aide d'un IDbContext ou quelques autres trucs, mais alors vous êtes vraiment les tests unitaires LINQ to Objects et pas LINQ to entities parce que la requête est déterminé au moment de l'exécution... OK, donc c'est pas bon!!! Alors maintenant, c'est le test d'intégration pour le couvrir.

Avec dépôt - vous pouvez Vous moquer de vos dépôts et de l'unité de test de la couche(s) entre les deux. Grand droit? Bien pas vraiment... Dans le cas ci-dessus où vous avez une fuite de logique dans le dépôt de la couche d'effectuer des requêtes plus effectuer-ant et/ou moins de consultations de la base de données, comment pouvez-vous les tests unitaires de couverture? Il est maintenant dans le repo couche et vous ne voulez pas tester IQuerable droit? Aussi laisse être honnête, vos tests unitaires ne vont pas à couvrir les requêtes qui ont un 20 ligne, .Where() clause et de l' .Include()'s un tas de relations et frappe la base de données de nouveau à faire tous ces trucs-là, blah, blah, blah, qui de toute façon est parce que la requête est générée lors de l'exécution. Aussi depuis que vous avez créé un référentiel de garder les couches supérieures de la persistance des ignorants, maintenant, si vous vous souhaitez modifier votre base de données de la technologie, désolé, votre tests unitaires sont un air de défi ne va pas garantir les mêmes résultats lors de l'exécution, de retour à des tests d'intégration. De sorte que le point de l'ensemble du référentiel semble bizarre..

2 cents

Nous avons déjà perdre beaucoup de fonctionnalités et de la syntaxe lors de l'utilisation de l'EF-dessus de la plaine de procédures stockées (insertions, en vrac supprime, CTE, etc.) mais j'ai aussi le code en C# donc je n'ai pas de type binaire. Nous utilisons EF afin que nous puissions avoir la possibilité d'utiliser différents fournisseurs et de travailler avec des objets graphiques dans une belle harmonie, parmi beaucoup de choses. Certaines abstractions sont utiles et certains ne sont pas.

J'espère que cela aide quelqu'un sur les internets quelque part...

51voto

jgauffin Points 51913

Le modèle de référentiel est une abstraction. Son but est de réduire la complexité et de faire le reste du code persistant dans l'ignorance. Comme un bonus, il vous permet d'écrire de l'unité de tests, plutôt que de l'intégration des tests.

Le problème est que beaucoup de développeurs ne parviennent pas à comprendre les motifs de but et de créer des dépôts de fuite de la persistance des informations spécifiques à l'appelant (généralement en exposant IQueryable<T>). En faisant ainsi, ils ne tirent aucun avantage, par rapport à l'utilisation de l'/M directement.

Mise à jour pour résoudre une autre réponse

Le codage de l'exception

À l'aide de référentiels n'est pas au sujet d'être en mesure de changer la persistance de la technologie (c'est à dire de changer de base de données ou à l'aide d'un webservice, etc au lieu de cela). C'est l'idée de la séparation de la logique métier de la persistance de réduire la complexité et de couplage.

Les tests unitaires vs les tests d'intégration

Vous n'avez pas à écrire des tests unitaires pour les dépôts. période.

Mais en introduisant des dépôts (ou toute autre couche d'abstraction entre la persistance et de l'entreprise), vous êtes en mesure d'écrire des tests unitaires pour la logique métier. c'est à dire que vous n'avez pas à vous inquiéter au sujet de vos tests échoue en raison d'une configuration incorrecte de la base de données.

Comme pour les requêtes. Si vous utilisez LINQ vous aussi vous devez vous assurer que vos requêtes de travail, de même que vous avez à faire avec des dépôts. et ce qui est fait à l'aide de tests d'intégration.

La différence est que si vous n'avez pas mélangé de votre entreprise avec LINQ des instructions que vous pouvez être sûr à 100% que c'est votre code de persistance qui échouent et pas autre chose.

Si vous analysez vos tests, vous verrez aussi qu'ils sont beaucoup plus propre si vous n'avez pas mélangé préoccupations (c'est à dire LINQ + logique d'Entreprise)

Référentiel des exemples

La plupart des exemples sont des conneries. c'est très vrai. Toutefois, si vous google tout modèle de conception, vous trouverez beaucoup d'exemples de merde. C'est pas une raison pour éviter d'utiliser un modèle.

La construction d'un correct du référentiel de mise en œuvre est très facile. En fait, vous n'avez qu'à suivre une seule règle:

Ne pas ajouter quoi que ce soit dans le référentiel de la classe jusqu'au moment où vous en avez besoin

Beaucoup de codeurs sont paresseux et tente de faire un générique référentiel et l'utilisation d'une classe de base avec beaucoup de méthodes qu'ils pourraient avoir besoin. YAGNI. Vous écrivez le référentiel de la classe une fois et de le garder aussi longtemps que l'application des vies (peut-être des années). Pourquoi baiser en étant paresseux. Le garder propre, sans base de l'héritage de classe. Il sera beaucoup plus facile à lire et à maintenir.

(La déclaration ci-dessus est un guide et non un droit. Une classe de base peut très bien être motivé. Pensez juste avant de l'ajouter, de sorte que vous ajouter pour les bonnes raisons)

Des vieux trucs

Conclusion:

Si vous ne me dérange pas d'avoir LINQ déclarations dans votre code de commerce, ni de soins sur les tests unitaires, je ne vois pas de raison de ne pas utiliser Entity Framework directement.

Mise à jour

J'ai blogué à la fois sur le modèle de référentiel et de ce que "l'abstraction" signifie réellement: http://blog.gauffin.org/2013/01/repository-pattern-done-right/

Mise à jour 2

Pour un seul type d'entité avec 20+ champs, comment allez-vous concevoir une méthode de requête à l'appui de toute permutation combinaison? Vous ne voulez pas limiter la recherche seulement par le nom, ce sujet de recherche avec les propriétés de navigation, la liste de toutes les commandes avec un article spécifique du code de prix, niveau 3 de la navigation, de recherche de propriété. La raison pour laquelle l'ensemble IQueryable a été inventé était d'être capable de créer n'importe quelle combinaison de la recherche contre la base de données. Tout semble bien en théorie, mais au besoin de l'utilisateur gagne la théorie ci-dessus.

Nouveau: Une entité avec 20+ champs est mal modélisées. C'est un DIEU de l'entité. Décomposer.

Je suis pas en train de dire qu' IQueryable n'est pas fait pour quering. Je suis en train de dire qu'il n'est pas bon pour une couche d'abstraction comme un modèle de Référentiel car il est perméable. Il n'est pas 100% complet fournisseur LINQ to Sql (comme EF).

Ils ont tous la mise en œuvre de choses spécifiques comme la façon d'utiliser avides/chargement paresseux ou comment SQL "DANS les" déclarations. Exposer IQueryable dans le référentiel des forces à l'utilisateur de connaître toutes ces choses. Ainsi, toute tentative de faire abstraction de la source de données est un échec complet. Vous venez d'ajouter de la complexité sans obtenir aucun avantage, par rapport à l'utilisation de l'/M directement.

Soit mettre en œuvre un modèle de Référentiel correctement ou tout simplement ne pas l'utiliser du tout.

(Si vous voulez vraiment poignée de grandes entités, vous pouvez combiner le modèle de Référentiel avec la Spécification du modèle. Qui vous donne une abstraction complète qui est aussi vérifiable.)

29voto

qujck Points 5994

L'OMI à la fois l' Repository de l'abstraction et de l' UnitOfWork d'abstraction ont une très précieux dans un véritable développement. Les gens diront sur les détails d'implémentation, mais tout comme il existe de nombreuses façons à la peau d'un chat, il existe de nombreuses façons de mettre en œuvre une abstraction.

Votre question est précisément de les utiliser ou ne pas utiliser et pourquoi.

Comme vous l'avez sans doute compris que vous avez déjà ces deux modèles construits dans le Cadre de l'Entité, DbContext est le UnitOfWork et DbSet est le Repository. Vous n'avez généralement pas besoin de test de l'unité de l' UnitOfWork ou Repository eux-mêmes qu'ils sont simplement de faciliter entre les classes et les données sous-jacentes de l'accès des implémentations. Ce que vous trouvez dans le besoin à faire, encore et encore, c'est se moquer de ces deux abstractions lors de tests unitaires de la logique de vos services.

Vous pouvez vous moquer, faux ou que ce soit avec les bibliothèques externes ajoutant des couches de code dépendances (que vous n'avez pas de contrôle) entre la logique de faire l'essai et de la logique en cours d'essai.

Ainsi, un point de détail, c'est que d'avoir votre propre abstraction pour UnitOfWork et Repository vous donne un maximum de flexibilité et un contrôle lorsque les moqueries des tests unitaires.

Très bien, mais pour moi, le véritable pouvoir de ces abstractions, c'est qu'ils fournissent un moyen simple d'appliquer la Programmation Orientée Aspects techniques et de respecter les principes SOLIDES.

Donc, vous avez votre IRepository:

public interface IRepository<T>
    where T : class
{
    T Add(T entity);
    void Delete(T entity);
    IQueryable<T> AsQueryable();
}

Et sa mise en œuvre:

public class Repository<T> : IRepository<T>
    where T : class
{
    private readonly IDbSet<T> _dbSet;
    public Repository(PPContext context) 
    {
        _dbSet = context.Set<T>();
    }

    public T Add(T entity)
    { 
        return _dbSet.Add(entity); 
    }

    public void Delete(T entity)
    {
        _dbSet.Remove(entity); 
    }

    public IQueryable<T> AsQueryable() 
    {
        return _dbSet.AsQueryable();
    }
}

Rien d'extraordinaire jusqu'à présent, mais maintenant, nous voulons ajouter un peu de connexion facile avec un enregistrement Décorateur.

public class RepositoryLoggerDecorator<T> : IRepository<T>
    where T : class
{
    Logger logger = LogManager.GetCurrentClassLogger();
    private readonly IRepository<T> _decorated;
    public RepositoryLoggerDecorator(IRepository<T> decorated)
    {
        _decorated = decorated;
    }

    public T Add(T entity)
    {
        logger.Log(LogLevel.Debug, () => DateTime.Now.ToLongTimeString() );
        T added = _decorated.Add(entity);
        logger.Log(LogLevel.Debug, () => DateTime.Now.ToLongTimeString());
        return added;
    }

    public void Delete(T entity)
    {
        logger.Log(LogLevel.Debug, () => DateTime.Now.ToLongTimeString());
        _decorated.Delete(entity);
        logger.Log(LogLevel.Debug, () => DateTime.Now.ToLongTimeString());
    }

    public IQueryable<T> AsQueryable()
    {
        return _decorated.AsQueryable();
    }
}

Tout est fait et sans modifier notre code existant. Il existe de nombreux autres transversale préoccupations, nous pouvons ajouter, tels que la gestion des exceptions, la mise en cache des données, validation des données, ou quoi que ce soit et tout au long de notre conception et le processus de construction de la chose la plus précieuse que nous avons qui nous permet d'ajouter des fonctionnalités simples, sans modification de notre code existant est notre IRepository d'abstraction.

Maintenant, de nombreuses fois, j'ai vu cette question sur StackOverflow – "comment voulez-vous faire Entité Cadre de travail dans un environnement multi-clients?".

http://stackoverflow.com/search?q=%5Bentity-framework%5D+multi+locataire

Si vous avez un Repository d'abstraction, alors la réponse est "il est facile d'ajouter un décorateur"

public class RepositoryTennantFilterDecorator<T> : IRepository<T>
    where T : class
{
    //public for Unit Test example
    public readonly IRepository<T> _decorated;
    public RepositoryTennantFilterDecorator(IRepository<T> decorated)
    {
        _decorated = decorated;
    }

    public T Add(T entity)
    {
        return _decorated.Add(entity);
    }

    public void Delete(T entity)
    {
        _decorated.Delete(entity);
    }

    public IQueryable<T> AsQueryable()
    {
        return _decorated.AsQueryable().Where(o => true);
    }
}

OMI, vous devez toujours placer une simple abstraction au-dessus de tout la 3ème partie de la composante qui sera référencée dans plus d'une poignée d'endroits. De ce point de vue un ORM est le candidat parfait qu'il est référencé dans de nombreux domaines de notre code.

La réponse qui normalement vient à l'esprit quand quelqu'un dit "pourquoi devrais-je avoir une abstraction (par exemple, Repository) au cours de cette ou que la 3ème partie de la bibliothèque" est "pourquoi pas vous?"

P. S. les Décorateurs sont extrêmement simples à appliquer à l'aide d'un Conteneur IoC, comme SimpleInjector.

[TestFixture]
public class IRepositoryTesting
{
    [Test]
    public void IRepository_ContainerRegisteredWithTwoDecorators_ReturnsDecoratedRepository()
    {
        Container container = new Container();
        container.RegisterLifetimeScope<PPContext>();
        container.RegisterOpenGeneric(
            typeof(IRepository<>), 
            typeof(Repository<>));
        container.RegisterDecorator(
            typeof(IRepository<>), 
            typeof(RepositoryLoggerDecorator<>));
        container.RegisterDecorator(
            typeof(IRepository<>), 
            typeof(RepositoryTennantFilterDecorator<>));
        container.Verify();

        using (container.BeginLifetimeScope())
        {
            var result = container.GetInstance<IRepository<Image>>();

            Assert.That(
                result, 
                Is.InstanceOf(typeof(RepositoryTennantFilterDecorator<Image>)));
            Assert.That(
                (result as RepositoryTennantFilterDecorator<Image>)._decorated,
                Is.InstanceOf(typeof(RepositoryLoggerDecorator<Image>)));
        }
    }
}

12voto

Akash Kava Points 18026

Tout d'abord, comme l'ont suggéré certaines réponse, EF lui-même est un modèle de référentiel, il n'y a pas de nécessité de créer davantage d'abstraction pour n'en nommer comme référentiel.

Mockable Référentiel pour les Tests Unitaires, avons-nous vraiment besoin?

Nous laissons EF communiquer à l'essai DB dans les tests unitaires pour tester notre logique d'entreprise directement à l'encontre de SQL test DB. Je ne vois pas l'avantage d'avoir de se moquer de tout modèle de référentiel à tous. Ce qui est vraiment mal fait les tests unitaires contre la base de données de test?

Inutile D'Abstraction

Voulez-vous créer un référentiel de juste, afin qu'à l'avenir, vous pouvez facilement remplacer EF avec NHbibernate etc ou autre chose? Sons grand plan, mais est-ce vraiment rentable?

Linq tue des tests unitaires?

J'aimerais voir des exemples sur la façon de la tuer.

L'Injection De Dépendance, Cio

Wow ce sont de grands mots, sûr ils semblent bien en théorie, mais parfois, vous avez à choisir entre un beau design et une excellente solution. Nous avons fait usage de tout cela, et nous avons fini par jeter tout à la poubelle et en choisissant une approche différente. Taille vs Vitesse (Taille du code et la Vitesse de développement) questions énorme dans la vie réelle. Les utilisateurs ont besoin de flexibilité, ils ne se soucient pas si votre code est grande dans la conception en termes de DI ou du Cio.

Sauf si vous êtes la construction de Visual Studio

Tous ces grands de la conception sont nécessaires si vous êtes à la construction d'un programme complexe, comme Visual Studio ou Eclipse qui sera développé par de nombreuses personnes et il doit être hautement personnalisable. Tous les grands modèle de développement est entré en image après des années de développement de ces IDEs a traversé, et ils ont évolué à un endroit où tous ces grands patrons de conception a beaucoup d'importance. Mais si vous faites simple basé sur le web de la paie, ou de la simple application d'entreprise, il est préférable de vous faire évoluer dans votre développement avec le temps, au lieu de passer du temps pour le construire pour des millions d'utilisateurs où il sera déployé pour 100s des utilisateurs.

Référentiel Vue Filtrée - ISecureRepository

De l'autre côté, le référentiel doit être une vue filtrée de EF qui garde l'accès aux données par l'application nécessaire de remplissage sur la base actuelle de l'utilisateur/rôle.

Mais cela complique référentiel d'autant plus que cela se termine dans l'énorme base de code à maintenir. Les gens finissent par créer des référentiels différents pour différents types d'utilisateurs ou d'une combinaison de types d'entité. Non seulement cela, nous avons aussi la fin avec beaucoup d'Otd.

Réponse suivante est un exemple de mise en œuvre de Filtré Dépôt sans créer ensemble de classes et de méthodes. Il ne peut pas répondre à la question directement, mais il peut être utile dans l'établissement.

Avertissement: je suis l'auteur de l'Entité RESTE SDK.

http://entityrestsdk.codeplex.com

Garder au-dessus à l'esprit, nous avons développé un SDK qui crée référentiel de vue filtrée basé sur SecurityContext qui détient des filtres pour les opérations CRUD. Et seulement deux types de règles de simplifier les opérations complexes. La première est l'accès à l'entité, et l'autre est en Lecture/Écriture de la règle de la propriété.

L'avantage est que vous n'avez pas de réécriture d'une logique d'entreprise ou des référentiels pour les différents types d'utilisateurs, il vous suffit simplement de bloquer ou de leur accorder l'accès.

public class DefaultSecurityContext : BaseSecurityContext {

  public static DefaultSecurityContext Instance = new DefaultSecurityContext();

  // UserID for currently logged in User
  public static long UserID{
       get{
             return long.Parse( HttpContext.Current.User.Identity.Name );
       }
  }

  public DefaultSecurityContext(){
  }

  protected override void OnCreate(){

        // User can access his own Account only
        var acc = CreateRules<Account>();

        acc.SetRead( y => x=> x.AccountID == UserID ) ;
        acc.SetWrite( y => x=> x.AccountID == UserID );

        // User can only modify AccountName and EmailAddress fields
        acc.SetProperties( SecurityRules.ReadWrite, 
              x => x.AccountName,
              x => x.EmailAddress);

        // User can read AccountType field
        acc.SetProperties<Account>( SecurityRules.Read, 
              x => x.AccountType);

        // User can access his own Orders only
        var order = CreateRules<Order>();
        order.SetRead( y => x => x.CustomerID == UserID );

        // User can modify Order only if OrderStatus is not complete
        order.SetWrite( y => x => x.CustomerID == UserID 
            && x.OrderStatus != "Complete" );

        // User can only modify OrderNotes and OrderStatus
        order.SetProperties( SecurityRules.ReadWrite, 
              x => x.OrderNotes,
              x => x.OrderStatus );

        // User can not delete orders
        order.SetDelete(order.NotSupportedRule);
  }
}

Ces LINQ Règles sont évalués par rapport à la Base de données dans la méthode SaveChanges pour chaque opération, et ces Règles agir comme Pare-feu en face de la Base de données.

7voto

Josh Points 535

Il y a beaucoup de débat sur la méthode qui est correct, donc je regarde ce que les deux sont acceptables donc j'utilise jamais ce qui me plaît le plus (Qui n'est d'aucun référentiel, UoW).

En EF UoW est mis en œuvre par DbContext et la DbSets sont les dépositaires.

Quant à la façon de travailler avec les données de la couche je viens de travailler directement sur le DbContext objet, pour les requêtes complexes, je ferai des méthodes d'extension pour la requête qui peut être réutilisé.

Je crois Ayende a aussi quelques articles sur la façon dont l'abstraction de la CUD opérations est mauvais.

Je fais toujours une interface et d'avoir mon contexte héritent de celui-ci afin que je puisse utiliser un conteneur IoC pour DI.

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