2012-03-06 19 views
16

Estoy intentando implementar un AuditLog usando el objeto ChangeTracker de DBContext, encontré un problema donde el DbEntityEntry.OriginalValues se borraba y se reemplazaba por el DbEntityEntry.CurrentValues. Me llamó la atención que el problema era cómo estaba actualizando el objeto que estaba siendo rastreado en el DbContext (publicación original: Entity Framework DbContext SaveChanges() OriginalValue Incorrect).EF 4: Cómo actualizar adecuadamente el objeto en DbContext usando MVC con el patrón de repositorio

Así que ahora necesito ayuda para actualizar un objeto persistente utilizando el patrón de repositorio en MVC 3 con Entity Framework 4. Este código de ejemplo está adaptado de la aplicación SportsStore en el libro Pro Asp.NET MVC 3 Framework sacado por Apress.

Aquí es mi 'Editar' acción posterior en el AdminController:

[HttpPost] 
public ActionResult Edit(Product product) 
{ 
    if (ModelState.IsValid) 
    { 
     // Here is the line of code of interest... 
     repository.SaveProduct(product, User.Identity.Name); 

     TempData["message"] = string.Format("{0} has been saved", product.Name); 
     return RedirectToAction("Index"); 
    } 
    else 
    { 
     // there is something wrong with the data values 
     return View(product); 
    } 
} 

Esto pone en EFProductRepository clase concreta (que está implementando la interfaz IProductRepository y se inyecta a través de Ninject). Aquí está el método SaveProduct en la clase repositorio concreto:

public void SaveProduct(Product product, string userID) 
{ 
    if (product.ProductID == 0) 
    { 
     context.Products.Add(product); 
    } 
    else 
    { 
     context.Entry(product).State = EntityState.Modified; 
    } 
    context.SaveChanges(userID); 
} 

El problema (como fue traído a mi atención en mi anterior SO post), es que cuando context.Entry(product).State = EntityState.Modified; se llama de alguna manera arruina la capacidad de la ChangeTracker para informar sobre los cambios. Por lo tanto, en mi método DBContext.SaveChanges (string ID de usuario) sobrecargado, no veo valores exactos en el objeto ChangeTracker.Entries().Where(p => p.State == System.Data.EntityState.Modified).OriginalValues.

Si actualizo mi método EFProductRepository.SaveProduct a este funciona:

public void SaveProduct(Product product, string userID) 
{ 
    if (product.ProductID == 0) 
    { 
     context.Products.Add(product); 
    } 
    else 
    { 
     Product prodToUpdate = context.Products 
      .Where(p => p.ProductID == product.ProductID).FirstOrDefault(); 

     if (prodToUpdate != null) 
     { 
      // Just updating one property to demonstrate.... 
      prodToUpdate.Name = product.Name; 
     } 
    } 
    context.SaveChanges(userID); 
} 

me gustaría saber la forma correcta para actualizar el objeto del producto y persistir en este escenario de tal manera que el ChangeTracker rastrea con precisión mis cambios a la clase POCO en el repositorio. ¿Se supone que debo hacer el último ejemplo (excepto, por supuesto, copiar todos los campos que pueden haber sido actualizados), o debería tomar un enfoque diferente?

En este ejemplo, la clase "Producto" es muy simple y solo tiene propiedades de cadena y propiedades decimales. En nuestra aplicación real, tendremos tipos "complejos" y las clases POCO harán referencia a otros objetos (es decir, a una persona que tenga una lista de direcciones). Sé que también podría necesitar hacer algo especial para rastrear los cambios en este caso. Quizás el conocimiento de esto cambie algunos consejos que recibo aquí.

Respuesta

31

de alguna manera arruina la capacidad de la ChangeTracker para informar sobre los cambios

No, no se mete nada. La función de seguimiento de cambios se basa en el hecho de que Change Tracker conoce a la entidad antes de realizar cambios. Pero en su caso, el rastreador de cambios recibe información sobre la entidad con los cambios ya aplicados y la entidad POCO no conserva ninguna información sobre sus valores originales. La entidad POCO tiene un único conjunto de valores que se interpreta como actual y original. Si quieres algo más, debes codificarlo tú mismo.

Se supone que voy a hacer el último ejemplo

En su caso simple sí y se puede utilizar simplemente:

public void SaveProduct(Product product, string userID) 
{ 
    if (product.ProductID == 0) 
    { 
     context.Products.Add(product); 
    } 
    else 
    { 
     Product prodToUpdate = context.Products 
      .Where(p => p.ProductID == product.ProductID).FirstOrDefault(); 

     if (prodToUpdate != null) 
     { 
      context.Entry(prodToUpdate).CurrentValues.SetValues(product); 
     } 
    } 

    context.SaveChanges(userID); 
} 

El problema es que esto sólo funciona para las propiedades simples y complejas . Otro problema es que esto sobrescribe todas las propiedades, por lo que si, por ejemplo, su entidad tiene algún campo que no desea mostrar en la interfaz de usuario (o no quiere que el usuario edite el campo), debe establecer el valor actual correcto en su product instancia; de lo contrario, ese valor se sobrescribirá al aplicar valores actuales.

Toda la situación se convierte en significantly more complex una vez que intenta aplicar esto al escenario real. Fallará y fallará muchas veces antes de escribir una gran cantidad de código para respaldar exactamente sus casos porque probablemente no haya una solución genérica. EF no tiene métodos de soporte para estos escenarios. El motivo es que EF tiene una máquina de estados interna para cada entidad y algunas asociaciones, y usted debe configurar el estado para cada entidad o asociación que desee actualizar, insertar o eliminar, y debe hacerlo de conformidad con las reglas internas de EF. Establecer el estado de la entidad cambiará el estado de esa entidad única, pero no sus relaciones.

Lo hago simplemente cargando la entidad actual con todas las relaciones desde la base de datos y manualmente (en código) fusionando todo el gráfico de entidad (simplemente ha separado y adjuntado gráfico de entidad y debe transferir todos los cambios desde separado al adjunto).

+1

Gracias por la respuesta. ¿Tienes un ejemplo de tu enfoque? Leí la pregunta SO a la que me vinculaste, pero todavía no estoy del todo claro. –

+4

Ladislav, me acabo de dar cuenta de que nunca había seleccionado esto como la respuesta. Usted respondió mi pregunta original publicada y la agradezco. Además, tus muchas otras respuestas en stackoverflow a las preguntas de EF han sido increíblemente útiles para mí. Gracias de nuevo. –

Cuestiones relacionadas