Référentiel générique pour mettre à jour un agrégat entier

domain-driven-design entity-framework entity-framework-6 onion-architecture repository-pattern

Question

J'utilise le modèle de référentiel pour fournir un accès à mes agrégats et les sauvegarder.

Le problème est la mise à jour d'agrégats qui consistent en une relation d'entités.

Par exemple, prenons les relations Order et OrderItem . La racine agrégée est Order qui gère sa propre collection OrderItem . Un OrderRepository serait donc chargé de mettre à jour l'ensemble de l'agrégat (il n'y aurait aucun OrderItemRepository ).

La persistance des données est gérée à l'aide d'Entity Framework 6.

Méthode de référentiel de mise à jour ( DbContext.SaveChanges() se trouve ailleurs):

public void Update(TDataEntity item)
{
    var entry = context.Entry<TDataEntity>(item);

    if (entry.State == EntityState.Detached)
    {
        var set = context.Set<TDataEntity>();

        TDataEntity attachedEntity = set.Local.SingleOrDefault(e => e.Id.Equals(item.Id));

        if (attachedEntity != null)
        {
            // If the identity is already attached, rather set the state values
            var attachedEntry = context.Entry(attachedEntity);
            attachedEntry.CurrentValues.SetValues(item);
        }
        else
        {
            entry.State = EntityState.Modified;
        }
    }
}

Dans mon exemple ci-dessus, seule l'entité Order sera mise à jour, pas sa collection OrderItem associée.

Devrais-je attacher toutes les entités OrderItem ? Comment pourrais-je le faire génériquement?

Réponse acceptée

Julie Lerman explique comment mettre à jour un agrégat complet dans son livre Programming Entity Framework: DbContext .

Comme elle écrit:

Lorsqu'un graphique d'entité déconnectée arrive côté serveur, le serveur ne connaît pas l'état des entités. Vous devez fournir un moyen de détecter l'état afin que le contexte puisse être mis au courant de l'état de chaque entité.

Cette technique s'appelle painting the state .

Il y a principalement deux façons de le faire:

  • Parcourez le graphique en utilisant votre connaissance du modèle et définissez l'état de chaque entité.
  • Construire une approche générique pour suivre l'état

La deuxième option est vraiment intéressante et consiste à créer une interface que chaque entité de votre modèle implémentera. Julie utilise une interface IObjectWithState qui indique l'état actuel de l'entité:

 public interface IObjectWithState
 {
  State State { get; set; }
 }
 public enum State
 {
  Added,
  Unchanged,
  Modified,
  Deleted
 }

La première chose à faire est de définir automatiquement sur Unchanged toutes les entités extraites de la base de données en connectant un événement en ajoutant un constructeur dans votre classe Context :

public YourContext()
{
 ((IObjectContextAdapter)this).ObjectContext
  .ObjectMaterialized += (sender, args) =>
 {
  var entity = args.Entity as IObjectWithState;
  if (entity != null)
  {
   entity.State = State.Unchanged;
  }
 };
}

Puis modifiez vos classes Order et OrderItem pour implémenter l'interface IObjectWithState et appelez cette méthode ApplyChanges acceptant l'entité racine en tant que paramètre:

private static void ApplyChanges<TEntity>(TEntity root)
 where TEntity : class, IObjectWithState
{
 using (var context = new YourContext())
 {
  context.Set<TEntity>().Add(root);

  CheckForEntitiesWithoutStateInterface(context);

  foreach (var entry in context.ChangeTracker
  .Entries<IObjectWithState>())
  {
   IObjectWithState stateInfo = entry.Entity;
   entry.State = ConvertState(stateInfo.State);
  }
  context.SaveChanges();
 }
}

private static void CheckForEntitiesWithoutStateInterface(YourContext context)
{
 var entitiesWithoutState =
 from e in context.ChangeTracker.Entries()
 where !(e.Entity is IObjectWithState)
 select e;

 if (entitiesWithoutState.Any())
 {
  throw new NotSupportedException("All entities must implement IObjectWithState");
 }
}

Dernier point mais non le moindre, n'oubliez pas de définir le bon état de vos entités graphiques avant d'appeler ApplyChanges ;-) (vous pouvez même mélanger les états Modified et Deleted dans le même graphique).

Julie propose d'aller encore plus loin dans son livre:

vous voudrez peut-être être plus précis avec la façon dont les propriétés modifiées sont suivies. Plutôt que de marquer l'entité entière comme modifiée, vous pouvez souhaiter que seules les propriétés réellement modifiées soient marquées comme modifiées. En plus de marquer une entité comme modifiée, le client est également responsable de l'enregistrement des propriétés modifiées. Une façon de procéder consiste à ajouter une liste de noms de propriétés modifiés à l'interface de suivi d'état.

Mais comme ma réponse est déjà trop longue, allez lire son livre si vous voulez en savoir plus ;-)


Réponse populaire

Mon opinionated (DDD spécifique) réponse serait:

  1. Coupez les entités EF au niveau de la couche de données.

  2. Assurez-vous que votre couche de données ne renvoie que des entités de domaine (et non des entités EF).

  3. Oubliez le chargement paresseux et la qualité IQueryable() (lire: cauchemar) de EF.

  4. Pensez à utiliser une base de données de documents.

  5. N'utilisez pas de référentiels génériques.

Le seul moyen que j’ai trouvé de faire ce que vous demandez dans EF est d’abord de supprimer ou de désactiver tous les postes de commande de la base de données qui sont un enfant de la commande, puis d’ajouter ou de réactiver tous les postes de commande de la base de données qui font maintenant partie de votre commande. ordre nouvellement mis à jour.



Sous licence: CC-BY-SA with attribution
Non affilié à Stack Overflow
Est-ce KB légal? Oui, apprenez pourquoi
Sous licence: CC-BY-SA with attribution
Non affilié à Stack Overflow
Est-ce KB légal? Oui, apprenez pourquoi