c# - pattern - repository entity framework




Réduire les dépôts dans les racines agrégées (4)

C'est une vieille question, mais la pensée vaut la peine d'afficher une solution simple.

  1. Le Contexte EF vous donne déjà à la fois Unité de Travail (changements de pistes) et Référentiels (référence en mémoire à des éléments de DB). Une abstraction supplémentaire n'est pas obligatoire.
  2. Supprimez le DBSet de votre classe de contexte, Phone n'étant pas une racine agrégée.
  3. Utilisez la propriété de navigation "Téléphones" sur Utilisateur à la place.

static void updateNumber (int userId, chaîne oldNumber, chaîne newNumber)

static void updateNumber(int userId, string oldNumber, string newNumber)
    {
        using (MyContext uow = new MyContext()) // Unit of Work
        {
            DbSet<User> repo = uow.Users; // Repository
            User user = repo.Find(userId); 
            Phone oldPhone = user.Phones.Where(x => x.Number.Trim() == oldNumber).SingleOrDefault();
            oldPhone.Number = newNumber;
            uow.SaveChanges();
        }

    }

J'ai actuellement un référentiel pour à peu près toutes les tables de la base de données et je voudrais m'aligner davantage sur DDD en les réduisant à des racines agrégées uniquement.

Supposons que j'ai les tables suivantes, User et Phone . Chaque utilisateur peut avoir un ou plusieurs téléphones. Sans la notion de racine agrégée, je pourrais faire quelque chose comme ceci:

//assuming I have the userId in session for example and I want to update a phone number
List<Phone> phones = PhoneRepository.GetPhoneNumberByUserId(userId);
phones[0].Number = “911”;
PhoneRepository.Update(phones[0]);

Le concept des racines agrégées est plus facile à comprendre sur papier que dans la pratique. Je n'aurai jamais de numéros de téléphone qui n'appartiennent pas à un utilisateur, alors est-ce qu'il serait judicieux de supprimer PhoneRepository et d'incorporer des méthodes liées au téléphone dans UserRepository? En supposant que la réponse est oui, je vais réécrire l'exemple de code précédent.

Suis-je autorisé à avoir une méthode sur le UserRepository qui renvoie les numéros de téléphone? Ou doit-il toujours renvoyer une référence à un utilisateur, puis traverser la relation via l'utilisateur pour accéder aux numéros de téléphone:

List<Phone> phones = UserRepository.GetPhoneNumbers(userId);
// Or
User user = UserRepository.GetUserWithPhoneNumbers(userId); //this method will join to Phone

Peu importe la façon dont j'achète les téléphones, en supposant que j'ai modifié l'un d'entre eux, comment puis-je les mettre à jour? Ma compréhension limitée est que les objets sous la racine devraient être mis à jour à travers la racine, ce qui me conduirait vers le choix # 1 ci-dessous. Bien que cela fonctionne parfaitement avec Entity Framework, cela semble extrêmement peu descriptif, car en lisant le code, je n'ai aucune idée de ce que je suis en train de mettre à jour, même si Entity Framework garde un onglet sur les objets modifiés dans le graphique.

UserRepository.Update(user);
// Or
UserRepository.UpdatePhone(phone);

Enfin, en supposant que j'ai plusieurs tables de recherche qui ne sont pas vraiment liées à quelque chose, comme CountryCodes , ColorsCodes , SomethingElseCodes . Je pourrais les utiliser pour remplir des listes déroulantes ou pour toute autre raison. Ces référentiels sont-ils autonomes? Peuvent-ils être combinés dans une sorte de groupement / référentiel logique tel que CodesRepository ? Ou est-ce contre les meilleures pratiques.


Si le téléphone n'a aucun sens sans utilisateur, c'est une entité (si vous vous intéressez à son identité) ou un objet de valeur et vous devez toujours le modifier par l'utilisateur et l'extraire / le mettre à jour ensemble.

Pensez aux racines agrégées comme définisseurs de contextes - elles dessinent des contextes locaux mais sont dans un contexte global (votre application) elles-mêmes.

Si vous suivez une conception orientée domaine, les référentiels sont censés être 1: 1 par racine agrégée.
Pas d'excuses.

Je parie que ce sont des problèmes auxquels vous êtes confrontés:

  • difficultés techniques - relation d'objet inadéquation d'impédance. Vous luttez avec des graphes d'objets entiers persistants avec facilité et le type d'infrastructure ne parvient pas à vous aider.
  • Le modèle de domaine est centré sur les données (par opposition au comportement centré). à cause de cela - Vous perdez la connaissance de la hiérarchie des objets (contextes mentionnés précédemment) et magiquement tout devient une racine agrégée.

Je ne suis pas sûr de savoir comment résoudre le premier problème, mais j'ai remarqué que corriger le second corrige d'abord assez bien. Pour comprendre ce que je veux dire avec un comportement centré, essayez ce document .

Ps Réduire le dépôt pour agréger la racine n'a aucun sens.
Pps Évitez les "CodeRepositories" . Cela conduit à centré sur les données -> code de procédure.
Ppps Éviter l'unité de travail. Les racines agrégées doivent définir les limites de transaction.


Votre exemple sur le référentiel racine d'agrégat est parfaitement correct, c'est-à-dire que toute entité qui ne peut pas raisonnablement exister sans dépendance à une autre ne devrait pas avoir son propre référentiel (dans votre cas Phone). Sans cette considération, vous pouvez rapidement vous retrouver avec une explosion de dépôts dans une table de mappage 1-1 vers db.

Vous devriez envisager d'utiliser le modèle Unit of Work pour les changements de données plutôt que les dépôts eux-mêmes car je pense qu'ils vous causent une certaine confusion autour de l'intention quand il s'agit de changements persistants à la base de données. Dans une solution EF, l'unité de travail est essentiellement une enveloppe d'interface autour de votre contexte EF.

En ce qui concerne votre référentiel pour les données de recherche, nous créons simplement un ReferenceDataRepository qui devient responsable des données qui n'appartiennent pas spécifiquement à une entité de domaine (Pays, Couleurs, etc.).


Vous êtes autorisé à avoir n'importe quelle méthode que vous voulez dans votre référentiel :) Dans les deux cas que vous mentionnez, il est logique de retourner l'utilisateur avec la liste de téléphone peuplée. Normalement, l'objet utilisateur ne serait pas entièrement rempli avec toutes les sous-informations (par exemple toutes les adresses, numéros de téléphone) et nous pourrions avoir différentes méthodes pour obtenir l'objet utilisateur rempli avec différents types d'informations. Ceci est appelé chargement paresseux.

User GetUserDetailsWithPhones()
{
    // Populate User along with Phones
}

Pour la mise à jour, dans ce cas, l'utilisateur est mis à jour, pas le numéro de téléphone lui-même. Le modèle de stockage peut stocker les téléphones dans différentes tables et ainsi vous pouvez penser que seulement les téléphones sont mis à jour mais ce n'est pas le cas si vous pensez du point de vue de DDD. En ce qui concerne la lisibilité, alors que la ligne

UserRepository.Update(user)

seul ne transmet pas ce qui est en cours de mise à jour, le code ci-dessus indiquerait clairement ce qui est en cours de mise à jour. En outre, il ferait probablement partie d'un appel de méthode frontale qui peut signifier ce qui est en cours de mise à jour.

Pour les tables de recherche, et même sinon, il est utile d'avoir GenericRepository et de l'utiliser. Le référentiel personnalisé peut hériter de GenericRepository.

public class UserRepository : GenericRepository<User>
{
    IEnumerable<User> GetUserByCustomCriteria()
    {
    }

    User GetUserDetailsWithPhones()
    {
        // Populate User along with Phones
    }

    User GetUserDetailsWithAllSubInfo()
    {
        // Populate User along with all sub information e.g. phones, addresses etc.
    }
}

Rechercher Generic Repository Entity Framework et vous obtiendrez de nombreuses implémentations sympa. Utilisez l'un de ceux-ci ou écrivez le vôtre.