Repositorio genérico para actualizar un agregado completo

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

Pregunta

Estoy utilizando el patrón de repositorio para proporcionar acceso y guardar mis agregados.

El problema es la actualización de agregados que consisten en una relación de entidades.

Por ejemplo, tome la relación Order and OrderItem . La raíz agregada es Order que administra su propia colección OrderItem . OrderRepository lo tanto, un OrderRepository sería responsable de actualizar todo el agregado (no habría un OrderItemRepository ).

La persistencia de datos se maneja utilizando Entity Framework 6.

El método del repositorio de actualización ( DbContext.SaveChanges() ocurre en otro lado):

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

En mi ejemplo anterior, solo se actualizará la entidad Order , no su colección de artículos de OrderItem asociada.

¿Tendría que adjuntar todas las entidades OrderItem ? ¿Cómo podría hacer esto genéricamente?

Respuesta aceptada

Julie Lerman ofrece una buena manera de lidiar con la forma de actualizar un agregado completo en su libro Programming Entity Framework: DbContext .

Como ella escribe:

Cuando un gráfico de entidad desconectada llega al lado del servidor, el servidor no sabrá el estado de las entidades. Debe proporcionar una forma de descubrir el estado para que el contexto pueda conocer el estado de cada entidad.

Esta técnica se llama painting the state .

Hay principalmente dos formas de hacer eso:

  • Iterar a través del gráfico utilizando su conocimiento del modelo y establecer el estado para cada entidad
  • Construir un enfoque genérico para rastrear el estado

La segunda opción es realmente agradable y consiste en crear una interfaz que implementarán todas las entidades de su modelo. Julie usa una interfaz IObjectWithState que indica el estado actual de la entidad:

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

Lo primero que debes hacer es establecer automáticamente en Unchanged todas las entidades recuperadas de la base de datos conectando un evento agregando un constructor en tu clase de Context :

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

Luego cambie sus clases Order y OrderItem para implementar la interfaz IObjectWithState y llame a ese método ApplyChanges aceptando la entidad raíz como parámetro:

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

Por último, pero no menos importante, no olvide establecer el estado correcto de sus entidades gráficas antes de llamar a ApplyChanges ;-) (incluso podría mezclar los estados Modified y Deleted dentro de la misma gráfica)

Julie propone ir aún más lejos en su libro:

es posible que desee ser más granular con la forma en que se rastrean las propiedades modificadas. En lugar de marcar la entidad completa como modificada, es posible que desee que solo se marquen como modificadas las propiedades que realmente han cambiado. Además de marcar una entidad como modificada, el cliente también es responsable de registrar qué propiedades se han modificado. Una forma de hacerlo sería agregar una lista de nombres de propiedades modificados a la interfaz de seguimiento de estado.

Pero como mi respuesta ya es demasiado larga, ve a leer su libro si quieres saber más ;-)


Respuesta popular

Mi respuesta valorada (específica de DDD) sería:

  1. Cortar las entidades EF en la capa de datos.

  2. Asegúrese de que su capa de datos solo devuelva entidades de dominio (no entidades EF).

  3. Olvídate de la carga perezosa y la bondad de IQueryable() (leer: pesadilla) de EF.

  4. Considere el uso de una base de datos de documentos.

  5. No utilice repositorios genéricos.

La única forma que encontré para hacer lo que pide en EF es eliminar o desactivar primero todos los artículos de pedido en la base de datos que son elementos secundarios del pedido, luego agregar o reactivar todos los artículos de pedido en la base de datos que ahora forman parte de su orden recién actualizada.



Related

Licencia bajo: CC-BY-SA with attribution
No afiliado con Stack Overflow
¿Es esto KB legal? Sí, aprende por qué
Licencia bajo: CC-BY-SA with attribution
No afiliado con Stack Overflow
¿Es esto KB legal? Sí, aprende por qué