Repository generico per aggiornare un intero aggregato

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

Domanda

Sto usando il modello di repository per fornire accesso e salvataggio dei miei aggregati.

Il problema è l'aggiornamento degli aggregati che consistono in una relazione di entità.

Ad esempio, prendi la relazione Order e OrderItem . La radice aggregata è Order che gestisce la propria raccolta OrderItem . Un OrderRepository sarebbe quindi responsabile per l'aggiornamento dell'intero aggregato (non ci sarebbe OrderItemRepository ).

La persistenza dei dati viene gestita utilizzando Entity Framework 6.

Aggiornamento del metodo di repository ( DbContext.SaveChanges() verifica altrove):

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;
        }
    }
}

Nel mio esempio precedente, solo l'entità Order verrà aggiornata, non la sua raccolta OrderItem associata.

Dovrei collegare tutte le entità OrderItem ? Come potrei fare questo genericamente?

Risposta accettata

Julie Lerman offre un buon modo per gestire come aggiornare un intero aggregato nel suo libro Programming Entity Framework: DbContext .

Come lei scrive:

Quando un grafico di entità disconnessa arriva sul lato server, il server non conoscerà lo stato delle entità. È necessario fornire un modo per lo stato di essere scoperto in modo che il contesto possa essere reso consapevole dello stato di ciascuna entità.

Questa tecnica è chiamata painting the state .

Ci sono principalmente due modi per farlo:

  • Iterate attraverso il grafico usando la vostra conoscenza del modello e impostate lo stato per ogni entità
  • Costruire un approccio generico per tenere traccia dello stato

La seconda opzione è davvero carina e consiste nella creazione di un'interfaccia che implementerà ogni entità nel modello. Julie utilizza un'interfaccia IObjectWithState che indica lo stato corrente dell'entità:

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

La prima cosa che devi fare è impostare automaticamente su Unchanged tutte le entità recuperate dal DB collegando un evento aggiungendo un costruttore nella tua classe Context :

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

Quindi modificare le classi Order e OrderItem per implementare l'interfaccia IObjectWithState e chiamare il metodo ApplyChanges accettando l'entità root come parametro:

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");
 }
}

Ultimo ma non meno importante, non dimenticare di impostare lo stato giusto delle tue entrate grafiche prima di chiamare ApplyChanges ;-) (potresti anche mescolare stati Modified e Deleted all'interno dello stesso grafico)

Julie propone di andare ancora oltre nel suo libro:

potresti trovarti a voler essere più granulare con il modo in cui vengono tracciate le proprietà modificate. Piuttosto che contrassegnare l'intera entità come modificata, è possibile che solo le proprietà che sono effettivamente cambiate vengano contrassegnate come modificate. Oltre a contrassegnare un'entità come modificata, il cliente è anche responsabile della registrazione delle proprietà modificate. Un modo per farlo sarebbe quello di aggiungere un elenco di nomi di proprietà modificati all'interfaccia di tracciamento dello stato.

Ma dato che la mia risposta è già troppo lunga, vai a leggere il suo libro se vuoi saperne di più ;-)


Risposta popolare

La mia risposta (specifica per DDD) sarebbe:

  1. Tagliare le entità EF al livello dati.

  2. Assicurati che il tuo livello dati restituisca solo entità di dominio (non entità EF).

  3. Dimentica il pigro carico e IQueryable() bontà (leggi: incubo) di EF.

  4. Prendi in considerazione l'utilizzo di un database di documenti.

  5. Non usare repository generici.

L'unico modo che ho trovato per fare ciò che chiedi in EF è quello di eliminare o disattivare tutti gli elementi dell'ordine nel database che sono figli dell'ordine, quindi aggiungere o riattivare tutti gli elementi dell'ordine nel database che ora fanno parte del tuo ordine appena aggiornato.



Autorizzato sotto: CC-BY-SA with attribution
Non affiliato con Stack Overflow
È legale questo KB? Sì, impara il perché
Autorizzato sotto: CC-BY-SA with attribution
Non affiliato con Stack Overflow
È legale questo KB? Sì, impara il perché