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

95

J'ai toujours utilisé le modèle Repository mais pour mon dernier projet je voulais voir si je pouvais perfectionner son utilisation et mon implémentation de «Unit Of Work». Plus j'ai commencé à creuser, plus je me suis posé la question: «En ai-je vraiment besoin?

Maintenant, tout commence par quelques commentaires sur Stackoverflow avec une trace du post d'Ayende Rahien sur son blog, avec 2 spécifiques,

Cela pourrait probablement être discuté pour toujours et à jamais et cela dépend de différentes applications. Ce que j'aime savoir,

  1. cette approche conviendrait-elle à un projet Entity Framework?
  2. en utilisant cette approche, la logique métier est-elle 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 utilise la session NHib)?

Cela se fait facilement 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();
}

En utilisant cette approche et en Ninjecttant que DI, dois-je créer Contextune interface et l'injecter dans mes contrôleurs?

Dejan.S
la source

Réponses:

103

J'ai emprunté de nombreux chemins et créé de nombreuses implémentations de référentiels sur différents projets et ... j'ai jeté l'éponge et abandonné, voici pourquoi.

Codage de l'exception

Codez-vous pour 1% de chances que votre base de données passe d'une technologie à une autre? Si vous pensez à l'état futur de votre entreprise et que vous dites oui c'est une possibilité, alors a) ils doivent avoir beaucoup d'argent pour se permettre de faire une migration vers une autre technologie DB ou b) vous choisissez une technologie DB pour le plaisir ou c ) quelque chose a terriblement mal tourné avec la première technologie que vous avez décidé d'utiliser.

Pourquoi jeter la riche syntaxe LINQ?

LINQ et EF ont été développés pour que vous puissiez faire des choses intéressantes avec lui pour lire et parcourir des graphiques 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 monstrueuse. D'après mon expérience, chaque fois que j'ai créé un référentiel, j'ai TOUJOURS eu une fuite de logique métier dans la couche du référentiel pour rendre les requêtes plus performantes et / ou réduire le nombre de hits dans la base de données.

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

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

À moins que vous ne développiez quelque chose de VRAIMENT dépouillé comme un blog ou quelque chose, vos requêtes ne seront jamais aussi simples que 90% des exemples que vous trouvez sur Internet autour du modèle de référentiel. Je ne peux insister assez sur ce point! C'est quelque chose qu'il faut ramper dans la boue pour comprendre. Il y aura toujours cette requête qui casse votre référentiel / solution parfaitement pensé que vous avez créé, et ce n'est qu'à ce moment-là que vous vous remettez en question et que la dette / l'érosion technique commence.

Ne me testez pas unitaire, frère

Mais qu'en est-il des tests unitaires si je n'ai pas de référentiel? Comment vais-je me moquer? Simple, vous ne le faites pas. Regardons-le sous les deux angles:

Pas de référentiel - Vous pouvez vous moquer de l' DbContextutilisation d'une IDbContextou d'autres astuces, mais vous testez vraiment LINQ to Objects et non LINQ to Entities car la requête est déterminée au moment de l'exécution ... OK donc ce n'est pas bon! C'est donc maintenant au test d'intégration de couvrir cela.

Avec référentiel - Vous pouvez désormais simuler vos référentiels et tester unitaire la ou les couches intermédiaires. Super non? Eh bien pas vraiment ... Dans les cas ci-dessus où vous devez laisser passer de la logique dans la couche du référentiel pour rendre les requêtes plus performantes et / ou moins heureuses dans la base de données, comment vos tests unitaires peuvent-ils couvrir cela? C'est maintenant dans la couche repo et vous ne voulez pas tester, n'est-ce pas IQueryable<T>? Soyons également honnêtes, vos tests unitaires ne couvriront pas les requêtes qui ont une .Where()clause de 20 lignes et.Include()'un tas de relations et frappe à nouveau la base de données pour faire toutes ces autres choses, bla, bla, bla de toute façon parce que la requête est générée au moment de l'exécution. De plus, puisque vous avez créé un référentiel pour ignorer la persistance des couches supérieures, si vous souhaitez maintenant changer la technologie de votre base de données, désolé, vos tests unitaires ne garantiront certainement pas les mêmes résultats lors de l'exécution, revenons aux tests d'intégration. Donc, tout l'intérêt du référentiel semble étrange.

2 cents

Nous perdons déjà beaucoup de fonctionnalités et de syntaxe lors de l'utilisation d'EF sur des procédures stockées simples (insertions en bloc, suppressions en bloc, CTE, etc.) mais je code également en C # pour ne pas avoir à taper binaire. Nous utilisons EF afin que nous puissions avoir la possibilité d'utiliser différents fournisseurs et de travailler avec des graphes d'objets d'une manière bien liée entre beaucoup de choses. Certaines abstractions sont utiles et d'autres non.

Ryan
la source
17
Vous ne créez pas de référentiels pour pouvoir les tester unitaire. Vous créez des référentiels pour pouvoir tester l'unité de la logique métier . Pour ce qui est de s'assurer que les requêtes fonctionnent: il est beaucoup plus facile d'écrire des tests d'intégration pour les référentiels car ils ne contiennent que de la logique et pas de business.
jgauffin
16
Coding for the exception: L'utilisation de référentiels ne permet pas de changer de moteur de base de données. Il s'agit de séparer les affaires de la persévérance.
jgauffin
2
Ce sont tous des points très valables avec beaucoup de vérité derrière eux. Ce qui manque cependant, c'est la prise de conscience que LINQ éparpillé sur une application au lieu d'être contraint à un emplacement cohérent crée l'équivalent EF des appels SQL dans les pages de code. Chaque requête LINQ est un point de maintenance potentiel dans une application, et plus il y en a (et plus elles sont répandues), plus les coûts et les risques de maintenance sont élevés. Imaginez ajouter un drapeau `` supprimé '' à une entité et devoir localiser chaque endroit dans une grande application où l'entité est interrogée, avoir à modifier chaque ...
DVK
2
Je pense que c'est myope et blasé. Pourquoi perdriez-vous de la logique dans le repo? Et si c'était le cas, pourquoi cela importerait-il? C'est une implémentation de données. Tout ce que nous faisons, c'est isoler le LINQ du reste du code en le cachant derrière le dépôt. Vous dites ne pas le tester, mais ensuite vous utilisez le fait de ne pas pouvoir le tester comme argument contre le faire. Faites donc le dépôt, n'exposez pas IQueryable et ne le testez pas. Au moins, vous pouvez tester tout le reste indépendamment de l'implémentation des données. Et cette chance de 1% d'un changement de base de données est toujours énorme pour le dollar.
Sinaesthetic
5
+1 pour cette réponse. Je trouve que nous n'avons vraiment PAS besoin de référentiels avec Entity Framework Core. Le DbSetest le référentiel et DbContextest l' unité de travail . Pourquoi implémenter un modèle de référentiel alors que ORM le fait déjà pour nous! Pour les tests, changez simplement le fournisseur en InMemory. Et faites vos tests! Il est bien documenté dans MSDN.
Mohammed Noureldin
49

Le modèle de référentiel est une abstraction . Son but est de réduire la complexité et de rendre le reste du code persistant ignorant. En prime, il vous permet d'écrire des tests unitaires au lieu de tests d' intégration .

Le problème est que de nombreux développeurs ne parviennent pas à comprendre le but des modèles et créent des référentiels qui divulguent des informations spécifiques à la persistance jusqu'à l'appelant (généralement en exposant IQueryable<T>). Ce faisant, ils n'obtiennent aucun avantage par rapport à l'utilisation directe de l'OR / M.

Mettre à jour pour répondre à une autre réponse

Codage de l'exception

Utiliser des référentiels ne consiste pas à pouvoir changer de technologie de persistance (c'est-à-dire changer de base de données ou utiliser un service Web, etc.). Il s'agit de séparer la logique métier de la persistance pour réduire la complexité et le couplage.

Tests unitaires vs tests d'intégration

Vous n'écrivez pas de tests unitaires pour les référentiels. période.

Mais en introduisant des référentiels (ou toute autre couche d'abstraction entre la persistance et 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 soucier de l'échec de vos tests en raison d'une base de données mal configurée.

Quant aux requêtes. Si vous utilisez LINQ, vous devez également vous assurer que vos requêtes fonctionnent, tout comme vous avez à faire avec les référentiels. et cela se fait à l'aide de tests d'intégration.

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

Si vous analysez vos tests, vous verrez également qu'ils sont beaucoup plus propres si vous n'avez pas de préoccupations mitigées (c'est-à-dire LINQ + Business logic)

Exemples de référentiel

La plupart des exemples sont des conneries. c'est très vrai. Cependant, si vous recherchez un modèle de conception sur Google, vous trouverez de nombreux exemples merdiques. Ce n'est pas une raison pour éviter d'utiliser un motif.

Construire une implémentation correcte du référentiel est très simple. En fait, vous n'avez qu'à suivre une seule règle:

N'ajoutez rien dans la classe de référentiel jusqu'au moment même où vous en avez besoin

De nombreux codeurs sont paresseux et essaient de créer un référentiel générique et d'utiliser une classe de base avec de nombreuses méthodes dont ils pourraient avoir besoin. YAGNI. Vous écrivez la classe du référentiel une fois et la conservez aussi longtemps que l'application dure (peut être des années). Pourquoi foutre le bordel en étant paresseux. Gardez-le propre sans aucun héritage de classe de base. Cela le rendra beaucoup plus facile à lire et à maintenir.

(La déclaration ci-dessus est une directive et non une loi. Une classe de base peut très bien être motivée. Réfléchissez simplement avant de l'ajouter, afin de l'ajouter pour les bonnes raisons)

Vieilles affaires

Conclusion:

Si cela ne vous dérange pas d'avoir des instructions LINQ dans votre code métier ni de vous soucier des tests unitaires, je ne vois aucune raison de ne pas utiliser Entity Framework directement.

Mettre à jour

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

Mise à jour 2

Pour un type d'entité unique avec plus de 20 champs, comment allez-vous concevoir une méthode de requête pour prendre en charge toute combinaison de permutation? Vous ne voulez pas limiter la recherche uniquement par nom, qu'en est-il de la recherche avec les propriétés de navigation, répertoriez toutes les commandes avec un article avec un code de prix spécifique, 3 niveaux de recherche de propriété de navigation. Toute la raison a IQueryableété inventée était de pouvoir composer n'importe quelle combinaison de recherche contre base de données. Tout a fière allure en théorie, mais le besoin de l'utilisateur l'emporte sur la théorie.

Encore une fois: une entité avec plus de 20 champs n'est pas correctement modélisée. C'est une entité DIEU. Décomposez-le.

Je ne dis pas que ce IQueryablen'était pas fait pour interroger. Je dis que ce n'est pas bon pour une couche d'abstraction comme le modèle de référentiel car il fuit. Il n'y a pas de fournisseur LINQ To Sql complet à 100% (comme EF).

Ils ont tous des éléments spécifiques à l'implémentation, comme comment utiliser le chargement hâtif / lazy ou comment faire des instructions SQL "IN". L'exposition IQueryabledans le référentiel oblige l'utilisateur à connaître toutes ces choses. Ainsi, toute tentative d'abstraction de la source de données est un échec complet. Vous ajoutez simplement de la complexité sans obtenir aucun avantage par rapport à l'utilisation directe de l'OR / M.

Soit implémentez correctement le modèle de référentiel, soit ne l'utilisez pas du tout.

(Si vous voulez vraiment gérer de grandes entités, vous pouvez combiner le modèle de référentiel avec le modèle de spécification . Cela vous donne une abstraction complète qui est également testable.)

Jgauffin
la source
6
Ne pas exposer IQueryable conduit à une recherche limitée, et les gens finissent par créer plus de méthodes Get pour différents types de requêtes, ce qui finit par rendre le référentiel plus complexe.
Akash Kava
3
vous n'avez pas du tout résolu le problème principal: exposer IQueryable via un référentiel n'est pas une abstraction complète.
jgauffin
1
Avoir un objet de requête qui contient toute l'infrastructure nécessaire pour être exécuté en lui-même est la voie à suivre pour imo. Vous lui donnez les champs qui sont les termes de recherche et il renvoie une liste de résultats. En interne au QO, vous pouvez faire ce que vous voulez. Et c'est une interface, si facilement testée. Voir mon message ci-dessus. C'est le meilleur.
h.alex
2
Personnellement, je pense qu'il est également logique d'implémenter l'interface IQueryable <T> sur une classe Repository, plutôt que d'exposer l'ensemble sous-jacent dans l'un de ses membres.
dark_perfect
3
@yat: un dépôt par racine agrégée. Mais à mon humble avis, ce n'est pas une racine agrégée et un agrégat de tables, mais simplement une racine agrégée et des agrégats . Le stockage réel peut n'utiliser qu'une seule table ou plusieurs d'entre eux, c'est-à-dire qu'il ne s'agit pas d'un mappage un-un entre chaque agrégat et une table. J'utilise des référentiels pour réduire la complexité et supprimer toutes les dépendances du stockage sous-jacent.
jgauffin
27

L' Repositoryabstraction et l' UnitOfWorkabstraction de l' OMI ont une place très précieuse dans tout développement significatif. Les gens discuteront des détails de la mise en œuvre, mais tout comme il existe de nombreuses façons d'écorcher un chat, il existe de nombreuses façons d'implémenter une abstraction.

Votre question est spécifiquement d'utiliser ou de ne pas utiliser et pourquoi.

Comme vous avez sans doute réalisé que vous avez déjà ces deux modèles intégrés dans Entity Framework, DbContextest le UnitOfWorket DbSetest le Repository. Vous n'avez généralement pas besoin de tester l'unité UnitOfWorkou Repositoryeux - mêmes car ils facilitent simplement entre vos classes et les implémentations d'accès aux données sous-jacentes. Ce que vous devrez faire, encore et encore, c'est vous moquer de ces deux abstractions lors du test unitaire de la logique de vos services.

Vous pouvez vous moquer, truquer ou autre chose avec des bibliothèques externes en ajoutant des couches de dépendances de code (que vous ne contrôlez pas) entre la logique faisant le test et la logique testée.

Donc, un point mineur est que d' avoir votre propre abstraction pour UnitOfWorket Repositoryvous donne un maximum de contrôle et de flexibilité lorsque vous vous moquez de vos tests unitaires.

Très bien, mais pour moi, le vrai pouvoir de ces abstractions est qu'elles fournissent un moyen simple d'appliquer les techniques de programmation orientée aspect et d'adhérer aux principes SOLID .

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 journalisation - facile avec un décorateur de journalisation .

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 modification de notre code existant . Il existe de nombreuses autres préoccupations transversales que nous pouvons ajouter, telles que la gestion des exceptions, la mise en cache des données, la validation des données ou autre et tout au long de notre processus de conception et de construction, la chose la plus précieuse que nous ayons qui nous permet d'ajouter des fonctionnalités simples sans changer aucun de notre code existant. est notre IRepositoryabstraction .

Maintenant, j'ai souvent vu cette question sur StackOverflow - «comment faire fonctionner Entity Framework dans un environnement multi-locataire?».

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

Si vous avez une Repositoryabstraction, la réponse est "c'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);
    }
}

IMO, vous devez toujours placer une simple abstraction sur tout composant tiers qui sera référencé dans plus d'une poignée d'endroits. De ce point de vue, un ORM est le candidat parfait car il est référencé dans une grande partie de notre code.

La réponse qui me vient normalement à l'esprit lorsque quelqu'un dit «pourquoi devrais-je avoir une abstraction (par exemple Repository) sur telle ou telle bibliothèque tierce» est «pourquoi pas vous?»

Les décorateurs PS sont extrêmement simples à appliquer à l'aide d'un conteneur IoC, tel que 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>)));
        }
    }
}
qujck
la source
11

Tout d'abord, comme suggéré par une réponse, EF lui-même est un modèle de référentiel, il n'est pas nécessaire de créer une abstraction supplémentaire juste pour le nommer comme référentiel.

Référentiel moquable pour les tests unitaires, en avons-nous vraiment besoin?

Nous laissons EF communiquer pour tester DB dans des tests unitaires afin de tester notre logique métier directement par rapport à SQL test DB. Je ne vois aucun avantage à se moquer d'un modèle de référentiel. Quel est vraiment le problème lors des tests unitaires sur la base de données de test? Comme il s'agit d'opérations en bloc ne sont pas possibles et nous finissons par écrire du SQL brut. SQLite en mémoire est le candidat idéal pour effectuer des tests unitaires sur une base de données réelle.

Abstraction inutile

Voulez-vous créer un référentiel juste pour qu'à l'avenir vous puissiez facilement remplacer EF par NHbibernate etc. ou autre chose? Ça a l'air génial, mais est-ce vraiment rentable?

Linq tue les tests unitaires?

J'aimerai voir des exemples sur la façon dont cela peut tuer.

Injection de dépendances, IoC

Wow, ce sont de bons mots, bien sûr qu'ils ont fière allure en théorie, mais parfois vous devez choisir un compromis entre un excellent design et une excellente solution. Nous avons utilisé tout cela, et nous avons fini par tout jeter à la poubelle et choisir une approche différente. La taille par rapport à la vitesse (taille du code et vitesse de développement) compte énormément dans la vie réelle. Les utilisateurs ont besoin de flexibilité, ils ne se soucient pas de savoir si votre code est excellent en termes de conception DI ou IoC.

Sauf si vous créez Visual Studio

Toutes ces excellentes conceptions sont nécessaires si vous créez un programme complexe comme Visual Studio ou Eclipse qui sera développé par de nombreuses personnes et qui doit être hautement personnalisable. Tous les grands modèles de développement sont apparus après des années de développement que ces IDE ont traversées, et ils ont évolué à un endroit où tous ces grands modèles de conception sont si importants. Mais si vous effectuez une paie Web simple ou une application d'entreprise simple, il est préférable que vous évoluiez dans votre développement avec le temps, au lieu de passer du temps à le créer pour des millions d'utilisateurs où il ne sera déployé que pour des centaines d'utilisateurs.

Référentiel en tant que vue filtrée - ISecureRepository

De l'autre côté, le référentiel doit être une vue filtrée d'EF qui protège l'accès aux données en appliquant le remplissage nécessaire en fonction de l'utilisateur / rôle actuel.

Mais cela complique encore plus le référentiel car il se retrouve dans une énorme base de code à maintenir. Les gens finissent par créer différents référentiels pour différents types d'utilisateurs ou une combinaison de types d'entités. Non seulement cela, nous nous retrouvons également avec beaucoup de DTO.

La réponse suivante est un exemple d'implémentation de Filtered Repository sans créer un ensemble complet de classes et de méthodes. Il peut ne pas répondre directement à la question, mais il peut être utile pour en dériver une.

Clause de non-responsabilité: Je suis l'auteur du SDK Entity REST.

http://entityrestsdk.codeplex.com

En gardant à l'esprit ci-dessus, nous avons développé un SDK qui crée un référentiel de vues filtrées basé sur SecurityContext qui contient des filtres pour les opérations CRUD. Et seuls deux types de règles simplifient les opérations complexes. Le premier est l'accès à l'entité, et l'autre est la règle de lecture / écriture pour la propriété.

L'avantage est que vous ne réécrivez pas la logique métier ou les référentiels pour 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 règles LINQ sont évaluées par rapport à Database dans la méthode SaveChanges pour chaque opération, et ces règles agissent comme pare-feu devant Database.

Akash Kava
la source
3
Les tests unitaires par rapport à un DB signifient que vous avez des exigences externes supplémentaires pour vos tests. Si cette base de données est en panne, ou si les données sont effacées, ou si quelque chose arrive à cette base de données, vos tests échoueront. Ce n'est pas souhaité. La configuration des référentiels qui exposent IQueryable prend environ 2 minutes. Pas de temps perdu ici. Pourquoi DI vous a-t-il fallu longtemps? Tout cela prend quelques minutes. Je dirai que tout cela a très bien fonctionné pour les tests unitaires de mes requêtes complexes dans ma couche de service. C'était tellement agréable de ne pas avoir besoin d'une base de données pour se connecter. Obtenir un cadre moqueur de nuget a pris environ une minute. Ce truc ne prend pas de temps.
user441521
@ user441521 Référentiels avec IQueryable 2 minutes pour la configuration? Dans quel monde vous vivez, chaque demande asp.net sur notre site en direct est servie en quelques millisecondes. Le fait de se moquer et de truquer, etc. ajoute plus de complexité au code, sa perte totale de temps. Les tests unitaires sont inutiles lorsque l'unité n'est pas définie comme unité de logique métier.
Akash Kava
7

Il y a beaucoup de débats sur la méthode correcte, donc je considère que les deux sont acceptables, donc j'utilise toujours celle que j'aime le plus (qui n'est pas un référentiel, UoW).

Dans EF, UoW est implémenté via DbContext et les DbSets sont des référentiels.

Quant à savoir comment travailler avec la couche de données, je travaille directement sur l'objet DbContext, pour les requêtes complexes, je créerai des méthodes d'extension pour la requête qui peuvent être réutilisées.

Je crois qu'Ayende a également quelques articles sur la façon dont l'abstraction des opérations CUD est mauvaise.

Je crée toujours une interface et mon contexte en hérite afin de pouvoir utiliser un conteneur IoC pour DI.

Josh
la source
Donc, les méthodes d'extension, quelle est leur étendue? Disons que j'ai besoin d'obtenir l'état d'une autre entité dans mon extension? C'est ma plus grande préoccupation en ce moment. Ça vous dérange de montrer quelques exemples de méthodes d'extension?
Dejan.S
ayende.com/blog/153473/… et ayende.com/blog/153569/… . (Ce sont des critiques d'une architecture (Framework?) Appelée s # arp lite. Surtout bien mais il n'est pas d'accord avec les dépôts et les abstractions CUD).
Josh
Son NHibernate basé. N'avez-vous pas d'exemples utilisant EF? Et encore une fois, lorsque j'ai besoin d'appeler une autre entité, comment cela est-il optimisé dans la méthode d'extension statique?
Dejan.S
3
Tout cela est bien beau jusqu'à ce qu'une propriété de votre objet de domaine doive être hydratée par des données qui ne sont pas stockées dans votre base de données; ou vous devez passer à une technologie plus performante que votre ORM gonflé. OUPS! Un ORM n'est tout simplement PAS un remplacement pour un référentiel, c'est un détail d'implémentation d'un.
cdaq
2

Ce qui s'applique le plus sur EF n'est pas un modèle de référentiel. Il s'agit d'un modèle de façade (abstraction des appels aux méthodes EF dans des versions plus simples et plus faciles à utiliser).

EF est celui qui applique le modèle de référentiel (ainsi que le modèle d'unité de travail). Autrement dit, EF est celui qui fait abstraction de la couche d'accès aux données afin que l'utilisateur n'ait aucune idée qu'il a affaire à SQLServer.

Et à cela, la plupart des «référentiels» sur EF ne sont même pas de bonnes Façades car ils se contentent de mapper, assez simplement, à des méthodes uniques dans EF, au point même d'avoir les mêmes signatures.

Les deux raisons, alors, pour appliquer ce soi-disant modèle "Repository" sur EF est de permettre des tests plus faciles et d'établir un sous-ensemble d'appels "en conserve". Pas mal en eux-mêmes, mais clairement pas un référentiel.

Aratirn
la source
1

Linq est aujourd'hui un «référentiel».

ISession + Linq est déjà le référentiel, et vous n'avez besoin ni de GetXByYméthodes ni de QueryData(Query q)généralisation. Étant un peu paranoïaque à l'utilisation de DAL, je préfère toujours l'interface du référentiel. (Du point de vue de la maintenabilité, nous devons également avoir une certaine façade sur des interfaces d'accès aux données spécifiques).

Voici le référentiel que nous utilisons - il nous dissocie de l'utilisation directe de nhibernate, mais fournit une interface linq (comme un accès ISession dans des cas exceptionnels, qui sont éventuellement sujets à refactor).

class Repo
{
    ISession _session; //via ioc
    IQueryable<T> Query()
    {
        return _session.Query<T>();
    }
}
Mikalai
la source
Que faites-vous pour la couche de service?
Dejan.S
Les contrôleurs interrogent le référentiel pour les données en lecture seule, pourquoi ajouter une couche supplémentaire? L'autre possibilité est d'utiliser "ContentService" qui a de plus en plus tendance à être un référentiel de niveau de service: GetXByY, etc.etc. Pour les opérations de modification - les services d'application ne sont que des abstractions sur des cas d'utilisation - ils utilisent BL et repo librement ..
mikalai
Je suis habitué à faire de la couche de service pour la logique métier. Je ne suis pas vraiment sûr de suivre ce que vous avec ContentService, veuillez préciser. Serait-ce une mauvaise pratique de faire des classes d'assistance en tant que «couche de service»?
Dejan.S
Par «couche de service», je voulais dire «services d'application». Ils peuvent utiliser le référentiel et toute autre partie publique de la couche de domaine. La "couche de service" n'est pas une mauvaise pratique, mais j'éviterais de créer une classe XService juste pour fournir le résultat List <X>. Le champ de commentaire semble trop court pour décrire les services en détail, désolé.
mikalai
Et si, disons un calcul de panier et que vous avez besoin d'obtenir des paramètres de paramètres d'application et des paramètres client spécifiques pour faire un calcul, et cela est réutilisé à plusieurs endroits dans l'application. Comment gérez-vous cette situation? classe d'assistance ou service d'application?
Dejan.S
1

Le référentiel (ou comment on choisit de l'appeler) à ce moment pour moi consiste principalement à faire abstraction de la couche de persistance.

Je l'utilise couplé à des objets de requête donc je n'ai pas de couplage avec une technologie particulière dans mes applications. Et cela facilite également beaucoup les tests.

Donc, j'ai tendance à avoir

public interface IRepository : IDisposable
{
    void Save<TEntity>(TEntity entity);
    void SaveList<TEntity>(IEnumerable<TEntity> entities);

    void Delete<TEntity>(TEntity entity);
    void DeleteList<TEntity>(IEnumerable<TEntity> entities);

    IList<TEntity> GetAll<TEntity>() where TEntity : class;
    int GetCount<TEntity>() where TEntity : class;

    void StartConversation();
    void EndConversation();

    //if query objects can be self sustaining (i.e. not need additional configuration - think session), there is no need to include this method in the repository.
    TResult ExecuteQuery<TResult>(IQueryObject<TResult> query);
}

Ajoutez éventuellement des méthodes asynchrones avec des rappels en tant que délégués. Le dépôt est facile à implémenter de manière générique , je suis donc en mesure de ne pas toucher une ligne de l'implémentation d'une application à l'autre. Eh bien, c'est vrai au moins lors de l'utilisation de NH, je l'ai fait aussi avec EF, mais m'a fait détester EF. 4. La conversation est le début d'une transaction. Très cool si quelques classes partagent l'instance du référentiel. De plus, pour NH, un dépôt dans mon implémentation équivaut à une session qui est ouverte à la première demande.

Puis les objets de requête

public interface IQueryObject<TResult>
{
    /// <summary>Provides configuration options.</summary>
    /// <remarks>
    /// If the query object is used through a repository this method might or might not be called depending on the particular implementation of a repository.
    /// If not used through a repository, it can be useful as a configuration option.
    /// </remarks>
    void Configure(object parameter);

    /// <summary>Implementation of the query.</summary>
    TResult GetResult();
}

Pour la configuration, j'utilise dans NH uniquement pour passer dans l'ISession. En EF n'a plus ou moins de sens.

Un exemple de requête serait .. (NH)

public class GetAll<TEntity> : AbstractQueryObject<IList<TEntity>>
    where TEntity : class
{
    public override IList<TEntity> GetResult()
    {
        return this.Session.CreateCriteria<TEntity>().List<TEntity>();
    }
}

Pour faire une requête EF, vous devez avoir le contexte dans la base abstraite, pas dans la session. Mais bien sûr, le ifc serait le même.

De cette manière, les requêtes sont elles-mêmes encapsulées et facilement testables. Mieux encore, mon code ne repose que sur des interfaces. Tout est très propre. Les objets de domaine (métier) ne sont que cela, par exemple, il n'y a pas de mélange de responsabilités comme lors de l'utilisation du modèle d'enregistrement actif qui est difficilement testable et mélange le code d'accès aux données (requête) dans l'objet de domaine et, ce faisant, mélange les préoccupations (objet qui récupère lui-même ??). Tout le monde est toujours libre de créer des POCO pour le transfert de données.

Dans l'ensemble, cette approche apporte beaucoup de réutilisation et de simplicité du code au détriment de rien que je puisse imaginer. Des idées?

Et merci beaucoup à Ayende pour ses excellents messages et son dévouement continu. C'est ses idées ici (objet de requête), pas les miennes.

h.alex
la source
1
Les entités de persistance (vos POCO) ne sont PAS des entités métier / domaine. Et le but du référentiel est de dissocier la couche métier (n'importe quelle) de la persistance.
MikeSW
Je ne vois pas le couplage. Un peu d'accord sur la partie POCO, mais je m'en fiche. Rien ne vous empêche d'avoir des POCO «authentiques» et d'utiliser toujours cette approche.
h.alex
1
Les entités ne doivent pas du tout être des POCO stupides. En fait, la modélisation de la logique métier en entités est ce que la foule DDD fait tout le temps. Ce style de développement se marie très bien avec NH ou EF.
chris
1

Pour moi, c'est une décision simple, avec relativement peu de facteurs. Les facteurs sont:

  1. Les référentiels sont destinés aux classes de domaine.
  2. Dans certaines de mes applications, les classes de domaine sont les mêmes que mes classes de persistance (DAL), dans d'autres elles ne le sont pas.
  3. Quand ils sont identiques, EF me fournit déjà des référentiels.
  4. EF fournit un chargement différé et IQueryable. Je les aime.
  5. Abstraction / `` facading '' / réimplémentation du référentiel sur EF signifie généralement la perte de lazy et IQueryable

Donc, si mon application ne peut pas justifier le n ° 2, séparer les modèles de domaine et de données, je ne me soucierai généralement pas du n ° 5.

Crayeux
la source