web-dev-qa-db-fra.com

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

J'ai toujours utilisé le modèle de référentiel mais pour mon dernier projet, je voulais voir si je pouvais perfectionner son utilisation et ma mise en œuvre de "Unit Of Work". Plus j'ai commencé à creuser, j'ai commencé à me poser 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,

On pourrait probablement en parler 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 se poursuit-elle 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 Ninject comme DI, dois-je faire du Context une interface et l'injecter dans mes contrôleurs?

89
Dejan.S

J'ai parcouru 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 les 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 dites oui, c'est une possibilité, alors a) ils doivent avoir beaucoup d'argent pour se permettre de migrer vers une autre technologie DB ou b) vous choisissez une technologie DB pour le plaisir ou c ) quelque chose s'est horriblement mal passé 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 trucs soignés 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 d'accès à la base de données.

Je ne veux pas créer une méthode pour chaque permutation unique 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, GetOrderByUserId, et ainsi de suite ... Je veux juste obtenir l'entité principale et parcourir et inclure le graphique d'objet à ma guise.

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

À moins que vous ne développiez quelque chose de VRAIMENT simple comme un blog ou quelque chose que vos requêtes ne seront jamais aussi simples que 90% des exemples que vous trouverez sur Internet autour du modèle de référentiel. Je ne peux insister assez sur ce point! C'est quelque chose que l'on doit ramper dans la boue pour comprendre. Il y aura toujours une seule requête qui casse votre référentiel/solution parfaitement pensé que vous avez créé, et ce n'est que lorsque vous vous devinez et que la dette technique/l'érosion commence.

Ne me testez pas un peu bro

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

Pas de référentiel - Vous pouvez vous moquer du DbContext en utilisant un IDbContext ou quelques autres astuces mais alors vous êtes vraiment un test unitaire 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! Alors maintenant, c'est au test d'intégration de couvrir cela.

Avec référentiel - Vous pouvez maintenant vous moquer de vos référentiels et tester à l'unité les couches intermédiaires. Parfait non? Eh bien pas vraiment ... Dans les cas ci-dessus où vous devez introduire une logique dans la couche de référentiel pour rendre les requêtes plus performantes et/ou moins de hits dans la base de données, comment vos tests unitaires peuvent-ils couvrir cela? Il est maintenant dans la couche repo et vous ne voulez pas tester IQueryable<T> Non? Soyons également honnêtes, vos tests unitaires ne couvriront pas les requêtes qui ont une clause .Where() de 20 lignes et .Include() est un tas de relations et frappe à nouveau la base de données pour faire tout cela d'autres choses, bla, bla, bla de toute façon car la requête est générée au moment de l'exécution. De plus, depuis que vous avez créé un référentiel pour ne pas tenir compte de la persistance des couches supérieures, si vous voulez maintenant changer la technologie de votre base de données, désolé vos tests unitaires ne vont certainement pas garantir les mêmes résultats au moment de l'exécution, retour aux tests d'intégration. Donc tout l'intérêt du référentiel semble bizarre ..

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 masse, suppressions en masse, CTE, etc.) mais je code aussi en C # donc je n'ai pas à 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 reliée à Nice entre autres. Certaines abstractions sont utiles et d'autres non.

96
Ryan

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

Le problème est que de nombreux développeurs ne parviennent pas à comprendre l'objectif des modèles et à créer 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 à utiliser directement l'OR/M.

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

Codage de l'exception

L'utilisation de référentiels ne signifie 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 pouvez é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 le faites 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 partagées (c.-à-d. LINQ + logique métier)

Exemples de référentiel

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

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

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

Beaucoup de codeurs sont paresseux et essaient de créer un référentiel générique et d'utiliser une classe de base avec beaucoup de méthodes qu'ils pourrait avoir besoin. YAGNI. Vous écrivez la classe de référentiel une fois et la conservez aussi longtemps que l'application dure (des années peuvent être). Pourquoi merde en étant paresseux. Gardez-le propre sans héritage de classe de base. Cela le rendra beaucoup plus facile à lire et à entretenir.

(L'énoncé ci-dessus est une directive et non une loi. Une classe de base peut très bien être motivée. Réfléchissez juste 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 d'entreprise ou de vous soucier des tests unitaires, je ne vois aucune raison de ne pas utiliser Entity Framework directement.

Mise à jour

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

Mise à jour 2

Pour le 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 des 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és de navigation. La seule raison pour laquelle IQueryable a été inventé était de pouvoir composer n'importe quelle combinaison de recherche sur base de données. Tout a l'air bien 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 est mal modélisée. C'est une entité DIEU. Décomposez-le.

Je ne dis pas que IQueryable n'a pas été fait pour effectuer une requête. 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 choses spécifiques à l'implémentation, comme comment utiliser le chargement rapide/paresseux ou comment faire des instructions SQL "IN". L'exposition de IQueryable dans le référentiel force l'utilisateur à connaître toutes ces choses. Ainsi, toute tentative d'abstraire la source de données est un échec complet. Vous ajoutez simplement de la complexité sans tirer aucun avantage de 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.)

47
jgauffin

L'OMI, l'abstraction Repository et l'abstraction UnitOfWork ont une place très précieuse dans tout développement significatif. Les gens discuteront des détails de l'implémentation, mais tout comme il existe de nombreuses façons d'habiller 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 ces deux modèles sont déjà intégrés dans Entity Framework, DbContext est le UnitOfWork et DbSet est le Repository. Vous n'avez généralement pas besoin de tester les UnitOfWork ou Repository eux-mêmes, car ils facilitent simplement entre vos classes et les implémentations d'accès aux données sous-jacentes. Ce que vous vous trouverez à faire, encore et encore, est de se moquer de ces deux abstractions lors du test unitaire de la logique de vos services.

Vous pouvez vous moquer, simuler ou quoi que ce soit 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 en train d'être testé.

Donc, un point mineur est que avoir votre propre abstraction pour UnitOfWork et Repository vous donne un maximum de contrôle et de flexibilité lors de la simulation de vos tests unitaires.

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

Vous avez donc 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 une journalisation Decorator.

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 changement à 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 quoi que ce soit et tout au long de notre processus de conception et de construction la chose la plus précieuse que nous avons qui nous permet d'ajouter des fonctionnalités simples sans changer aucun de notre code existant est notre abstraction IRepository.

Maintenant, plusieurs fois, j'ai 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 abstraction Repository, 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);
    }
}

IMO, vous devez toujours placer une abstraction simple 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 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 ne le feriez-vous pas?"

P.S. Les décorateurs 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>)));
        }
    }
}
24
qujck

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 référentiel.

Référentiel modifiable 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 du modèle de référentiel du tout. Qu'est-ce qui ne va vraiment pas faire des tests unitaires contre la base de données de test? Comme il s'agit d'opérations en bloc, ce n'est pas possible 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? Cela semble très bien, mais est-il vraiment rentable?

Linq tue les tests unitaires?

Je voudrais voir des exemples sur la façon dont il peut tuer.

Injection de dépendance, IoC

Wow, ce sont d'excellents mots, bien sûr, 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 en choisissant une approche différente. La taille par rapport à la vitesse (taille du code et vitesse de développement) est très importante dans la vie réelle. Les utilisateurs ont besoin de flexibilité, ils ne se soucient pas si votre code est génial en termes de DI ou d'IoC.

À moins que vous ne construisiez Visual Studio

Toutes ces superbes conceptions sont nécessaires si vous construisez 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 de ces IDE, et ils ont évolué à un endroit où tous ces grands modèles de conception sont si importants. Mais si vous faites une simple paie basée sur le Web, ou une simple application métier, il vaut mieux que vous évoluiez dans votre développement avec le temps, au lieu de passer du temps à la construire pour des millions d'utilisateurs où elle ne sera déployée 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 du référentiel filtré 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 d'en dériver une.

Avertissement: je suis l'auteur de l'entité REST SDK.

http://entityrestsdk.codeplex.com

Gardant à l'esprit ci-dessus, nous avons développé un SDK qui crée un référentiel de vue filtrée 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 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 à la méthode Database dans SaveChanges pour chaque opération, et ces règles agissent comme un pare-feu devant la base de données.

11
Akash Kava

Il y a beaucoup de débats sur la méthode qui est correcte, donc je la considère comme les deux sont acceptables, donc j'utilise celle que j'aime le plus (ce 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 à la façon de travailler avec la couche de données, je travaille directement sur l'objet DbContext, pour les requêtes complexes, je ferai des méthodes d'extension pour la requête qui peuvent être réutilisées.

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

Je crée toujours une interface et j'en ai hérité mon contexte afin que je puisse utiliser un conteneur IoC pour DI.

6
Josh

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

  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 paresseux et IQueryable. Je les aime.
  5. Abstraire/'façonner'/réimplémenter le référentiel sur EF signifie généralement la perte de paresseux et IQueryable

Donc, si mon application ne peut pas justifier le # 2, séparer les modèles de domaine et de données, alors je ne me dérangerai généralement pas avec le # 5.

1
Chalky

Linq est un "référentiel" de nos jours.

ISession + Linq est déjà le référentiel, et vous n'avez besoin ni des méthodes GetXByY ni de la généralisation QueryData(Query q). Étant un peu paranoïaque à l'utilisation de DAL, je préfère toujours l'interface de référentiel. (Du point de vue de la maintenabilité, nous devons également avoir une 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 (en tant qu'accès ISession dans des cas exceptionnels, qui peuvent éventuellement être refactorisés).

class Repo
{
    ISession _session; //via ioc
    IQueryable<T> Query()
    {
        return _session.Query<T>();
    }
}
1
mikalai

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 (résumant les 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 (et le modèle d'unité de travail également). 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 traite avec 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, très simplement, à des méthodes uniques dans EF, même au point d'avoir les mêmes signatures.

Les deux raisons, alors, pour appliquer ce modèle dit de "référentiel" sur EF sont de permettre un test plus facile et d'établir un sous-ensemble d'appels "en conserve". Pas mal en soi, mais clairement pas un référentiel.

1
Aratirn

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

Je l'utilise couplé à des objets de requête donc je n'ai pas de couplage à 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 génériquement, donc je ne peux pas toucher une ligne de l'implémentation d'une application à l'autre. Eh bien, c'est vrai au moins en utilisant NH, je l'ai fait aussi avec EF, mais ça 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 de référentiel. De plus, pour NH, un dépôt dans mon implémentation équivaut à une session ouverte à la première demande.

Ensuite, 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 que j'utilise dans NH uniquement pour passer dans l'ISession. Dans EF n'a pas de sens plus ou moins.

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 la session. Mais bien sûr, l'ifc serait le même.

De cette façon, 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 lorsque vous utilisez le 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 se récupère ??). Tout le monde est toujours libre de créer des POCO pour le transfert de données.

Dans l'ensemble, beaucoup de réutilisation de code et de simplicité sont fournis avec cette approche à la perte de rien de ce que je peux imaginer. Des idées?

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

1
h.alex