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?
Julie Lerman offre un modo carino per trattare come aggiornare un intero aggregato nel suo libro Programming Entity Framework: DbContext .
Mentre scrive:
Quando un grafico di entità disconnesso arriva sul lato server, il server non conoscerà lo stato delle entità. Devi fornire un modo per scoprire lo stato in modo che il contesto possa essere reso consapevole dello stato di ciascuna entità.
Questa tecnica si chiama painting the state
.
Esistono principalmente due modi per farlo:
La seconda opzione è davvero interessante e consiste nel creare un'interfaccia che verrà implementata da 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 lo stato su Unchanged
per ogni entità recuperata dal DB, aggiungendo un costruttore nella classe Context
che collega un evento:
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 questo 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 corretto delle entità del grafico prima di chiamare ApplyChanges
;-) (Potresti anche mescolare stati Modified
ed 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 monitorate le proprietà modificate. Invece di contrassegnare l'intera entità come modificata, è possibile che solo le proprietà che sono state effettivamente modificate 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 poiché la mia risposta è già troppo lunga, vai a leggere il suo libro se vuoi saperne di più ;-)
La mia risposta (specifica per DDD) sarebbe:
Tagliare le entità EF al livello dati.
Assicurati che il tuo livello dati restituisca solo entità di dominio (non entità EF).
Dimentica il pigro carico e IQueryable()
bontà (leggi: incubo) di EF.
Prendi in considerazione l'utilizzo di un database di documenti.
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.