Injection de dépendances avec la solution Entity Framework à n niveaux

12

Je suis en train de concevoir une solution à n niveaux qui utilise Entity Framework 5 (.net 4) comme stratégie d'accès aux données, mais je me demande comment incorporer l'injection de dépendance pour la rendre testable / flexible.

Ma disposition actuelle de la solution est la suivante (ma solution s'appelle Alcatraz):

Alcatraz.WebUI : un projet de formulaire Web asp.net, l'interface utilisateur frontale, fait référence aux projets Alcatraz.Business et Alcatraz.Data.Models .

Alcatraz.Business : projet de bibliothèque de classes, contient la logique métier, fait référence aux projets Alcatraz.Data.Access , Alcatraz.Data.Models

Alcatraz.Data.Access : Un projet de bibliothèque de classe, qui héberge AlcatrazModel.edmx et AlcatrazEntitiesDbContext, fait référence aux projets Alcatraz.Data.Models .

Alcatraz.Data.Models : projet de bibliothèque de classes, contient des POCO pour le modèle Alcatraz, sans références.

Ma vision du fonctionnement de cette solution est que l'interface utilisateur Web instancierait un référentiel au sein de la bibliothèque d'entreprise, ce référentiel aurait une dépendance (via le constructeur) d'une chaîne de connexion (pas une AlcatrazEntitiesinstance). L'interface Web connaîtrait les chaînes de connexion à la base de données, mais pas qu'il s'agissait d'une chaîne de connexion de structure d'entité.

Dans le projet Business:

public class InmateRepository : IInmateRepository
{
    private string _connectionString;

    public InmateRepository(string connectionString)
    {
        if (connectionString == null)
        {
            throw new ArgumentNullException("connectionString");
        }

        EntityConnectionStringBuilder connectionBuilder = new EntityConnectionStringBuilder();

        connectionBuilder.Metadata = "res://*/AlcatrazModel.csdl|res://*/AlcatrazModel.ssdl|res://*/AlcatrazModel.msl";
        connectionBuilder.Provider = "System.Data.SqlClient";
        connectionBuilder.ProviderConnectionString = connectionString;

        _connectionString = connectionBuilder.ToString();
    }

    public IQueryable<Inmate> GetAllInmates()
    {
        AlcatrazEntities ents = new AlcatrazEntities(_connectionString);

        return ents.Inmates;
    }
}

Dans l'interface utilisateur Web:

IInmateRepository inmateRepo = new InmateRepository(@"data source=MATTHEW-PC\SQLEXPRESS;initial catalog=Alcatraz;integrated security=True;");

List<Inmate> deathRowInmates = inmateRepo.GetAllInmates().Where(i => i.OnDeathRow).ToList();

J'ai quelques questions connexes sur cette conception.

  1. Cette conception a-t-elle même un sens en termes de capacités Entity Frameworks? J'ai entendu dire que le cadre Entity utilise déjà le modèle d'unité de travail, est-ce que j'ajoute simplement une autre couche d'abstrait inutilement?

  2. Je ne veux pas que mon interface Web communique directement avec Entity Framework (ou même le référence d'ailleurs), je veux que tous les accès à la base de données passent par la couche métier, car à l'avenir, j'aurai plusieurs projets utilisant la même couche métier (service Web, application Windows, etc.) et je veux qu'il soit facile à maintenir / à mettre à jour en ayant la logique métier dans une zone centrale. Est-ce une façon appropriée d'y parvenir?

  3. La couche Business doit-elle même contenir des référentiels, ou doit-elle être contenue dans la couche Access? Si leur emplacement est correct, le passage d'une chaîne de connexion est-il une bonne dépendance à assumer?

Merci d'avoir pris le temps de lire!

Matthieu
la source

Réponses:

11

La façon dont vous faites DI est incorrecte.

Tout d'abord, la chaîne de connexion appartient à la couche de données. Ou dans le fichier web.config.

La prochaine abstraction que vous allez traiter est le DbContext, pas une chaîne de connexion. Vos référentiels ne doivent pas connaître les chaînes de connexion. Votre logique métier ne connaîtra pas DbContext, etc.

Votre interface utilisateur n'aura aucune idée et n'instanciera rien lié à EF.

Des réponses concrètes à vos points:

  1. N'ajoutez pas d'abstractions tant que vous n'êtes pas très familier avec EF. Il ajoute déjà de bonnes abstractions comme UoW, des requêtes, l'utilisation de POCO, etc.

  2. Pour que DI fonctionne, vous avez une racine de composition qui référence tous les composants nécessaires. Cela peut ou non être dans le projet WebUI. Si ce n'est pas le cas, vous devez vous attendre à ce qu'il ne fasse pas référence à EF ou à toute autre technologie liée aux données.

  3. Arrêtez-vous ici. Arrêtez d'ajouter des abstractions sur les abstractions. Commencez avec une architecture directe et «naïve» et développez-la au fil du temps.

Les abstractions sont un outil pour gérer la complexité. L'absence de complexité signifie qu'aucune abstraction n'est nécessaire (pour l'instant).

Boris Yankov
la source
Pour être clair que je comprends ce que vous dites: le référentiel (dont l'interface existe dans l'entreprise et le béton existe dans Alcatraz.Data.Access?) Accepte un DbContextcomme sa dépendance. Les classes affaires ont des référentiels comme dépendance. Pour l'injection de dépendance, je fais cela manuellement (donc je comprends ce qui se passe). La raison pour laquelle je veux pouvoir définir la chaîne de connexion sur le DbContextest que j'utilise le partage de base de données, donc dans certains cas, j'ai besoin d'un framework d'entité pour se connecter à différentes bases de données (de la même structure). Je vous comprends bien?
Matthew
D'après le code que vous avez fourni, il semble que vous ne fassiez pas du tout DI. L'objectif principal de DI est de vous libérer, vous et votre code, de la gestion des dépendances. Je ne peux pas imaginer que vous le fassiez efficacement manuellement sans conteneur DI.
Boris Yankov
gardez également l'esprit ouvert avec DI. J'ai déjà, pour le plaisir, posé exactement la même question ici puis sur d'autres forums pour obtenir des réponses opposées. DI est un modèle, pas une architecture. Selon votre objectif, vous pouvez décider de l'utiliser ou non. Je l'utilise mais pas pour les raisons pour lesquelles la plupart des gens me disent de l'utiliser.
Bastien Vandamme
4

Quelques commentaires rapides. Personnellement, je ne passerais probablement pas de chaîne de connexion. Si j'essayais de créer des interfaces pour les référentiels et de simplement passer les interfaces? Demandez aux référentiels d'implémenter ou d'exposer une interface IOW.

De cette façon, il n'a pas besoin d'être une base de données qui implémente vos référentiels. ils pourraient être un cache en mémoire, ou quoi que ce soit. Peut-être que vous pourriez alors utiliser une sorte de cadre d'injection de dépendance pour les instancier même?

Donc, en réponse à certaines de vos questions:

  1. Oui, je pense que ça va
  2. Je voudrais toujours que l'interface utilisateur fasse référence au projet EF et que les interfaces de référence de la couche commerciale que la couche de référentiel EF implémente. De cette façon, d'autres projets pourraient toujours utiliser les mêmes assemblages, mais ils ont la possibilité de les remplacer si vous le souhaitez?
  3. hmmm, Probablement les référentiels dans la couche d'accès mais implémentant une définition d'interface exposée dans la couche métier ??

Ce ne sont là que quelques réflexions.

dreza
la source
À propos du point 2, un objectif que j'essayais de faire est de ne pas avoir CRUD directement dans la couche d'interface utilisateur. Ce que je veux dire, c'est que je veux m'assurer que seul CRUD peut se produire en passant par la couche métier, de cette façon, il est géré.
Matthew