9

que tienen una clase base que heredar de que tiene dos cero a muchas relaciones con otras entidades:ChangeTracker Entity Framework 4.1 - Los valores originales de los objetos relacionados con

public abstract class WebObject 
{ 
    public WebObject() 
    { 
     RelatedTags = new List<Tag>(); 
     RelatedWebObjects = new List<WebObject>(); 
    } 

    [Key, DatabaseGenerated(DatabaseGeneratedOption.Identity)] 
    public Guid Id { get; set; } 

    public string MetaKeywords { get; set; } 
    public string MetaDescription { get; set; } 

    [InverseProperty("WebObjects")] 
    public virtual WebSite WebSite { get; set; } 

    [Required(ErrorMessage = "Every WebObject must be associated with a WebSite.")] 
    public Guid WebSiteId { get; set; } 

    public virtual ICollection<Tag> RelatedTags { get; set; } 
    public IList<Guid> RelatedTagIds { get; set; } 
    public virtual ICollection<WebObject> RelatedWebObjects { get; set; } 
    public IList<Guid> RelatedWebObjectIds { get; set; } 
} 

Estoy teniendo dificultades para obtener los valores originales de éstos relaciones (RelatedWebObjects & RelatedTags) cuando se observan las entidades que usan ChangeTracker durante SaveChanges. Puedo ver todos los valores escalares antes y después, y puedo ver las nuevas relaciones, pero no puedo ver las antiguas. Intenté usar los métodos Miembro y Colección, pero esos solo me muestran los valores actuales; no el viejo Además, no me gusta usarlos porque me obliga a conocer el nombre de la propiedad de navegación, que no es lo suficientemente genérica.

Puedo encontrar los objetos relacionados cuya relación está siendo modificada, pero por supuesto los valores dentro de esos objetos relacionados no están cambiando, por lo que tampoco sirve de nada.

¿Existe alguna manera limpia de rastrear las relaciones previas de una entidad durante SaveChanges con ChangeTracker?

A continuación se muestra la sección de código que estoy trabajando en:

public override int SaveChanges() 
    { 
     List<AuditObject> auditTrailList = new List<AuditObject>(); 

     foreach (DbEntityEntry entity in ChangeTracker.Entries().Where(obj => { return obj.State == EntityState.Added || obj.State == EntityState.Modified || obj.State == EntityState.Deleted; })) 
     { 
      if (!(entity.Entity is AuditObject)) 
      { 
       AuditObject auditObject = new AuditObject(); 

       auditObject.Id = Guid.NewGuid(); 

       auditObject.RevisionStamp = DateTime.Now; 

       auditObject.UserName = HttpContext.Current.User.Identity.Name; 

       auditObject.EntityType = Utilities.GetCleanClassNameIfProxyClass(entity.Entity.GetType().Name); 

       if (entity.State == EntityState.Added) 
        auditObject.Action = EntityState.Added.ToString(); 
       else if (entity.State == EntityState.Modified) 
        auditObject.Action = EntityState.Modified.ToString(); 
       else if (entity.State == EntityState.Deleted) 
        auditObject.Action = EntityState.Deleted.ToString(); 

       DbMemberEntry t1 = entity.Member("RelatedWebObjects"); 
       // cannot find original relationship collection... 

       DbCollectionEntry t2 = entity.Collection("RelatedWebObjects"); 
       // cannot find original relationship collection... 

       if (entity.State == EntityState.Added || entity.State == EntityState.Modified) 
       { 
        XDocument currentValues = new XDocument(new XElement(auditObject.EntityType)); 

        foreach (string propertyName in entity.CurrentValues.PropertyNames) 
        { 
         currentValues.Root.Add(new XElement(propertyName, entity.CurrentValues[propertyName])); 
        } 

        auditObject.NewData = Regex.Replace(currentValues.ToString(), @"\r\n+", " "); 
       } 

       if (entity.State == EntityState.Modified || entity.State == EntityState.Deleted) 
       { 
        XDocument originalValues = new XDocument(new XElement(auditObject.EntityType)); 

        foreach (string propertyName in entity.OriginalValues.PropertyNames) 
        { 
         originalValues.Root.Add(new XElement(propertyName, entity.OriginalValues[propertyName])); 
        } 

        auditObject.OldData = Regex.Replace(originalValues.ToString(), @"\r\n+", " "); 
       } 

       auditTrailList.Add(auditObject); 
      } 
     } 

     foreach (var audit in auditTrailList) 
      this.AuditObjects.Add(audit); 

     return base.SaveChanges(); 
    } 
+0

ObjectStateManager también está modificando los objetos relacionados y debería poder obtener las entradas de estado de objeto de la misma forma que lo hace para su objeto principal. Si publica el código con el que está luchando, podría ayudarlo. –

+0

Gracias de nuevo Morteza ... Publiqué la sección de código con la que estoy trabajando ahora - por favor disculpen el descuido; no ha sido refactorado en absoluto, solo tratando de hacer que funcione. Puedo obtener las propiedades escalares de los objetos sin ningún problema usando las cadenas IEnumerable: entity.OriginalValues.PropertyNames & entity.CurrentValues.PropertyNames. Estoy teniendo problemas con entity.Collection() y entity.Member(). ¿Cuál debería usarse para lo que estoy tratando de lograr? ¿Hay alguna manera de hacer este genérico para que no tenga que codificar los nombres de las colecciones? Reflexión tal vez? – DMC

+0

no estoy en posición de probar esto hasta mañana ... ¿alguno de ustedes sabe cómo puedo dividir puntos entre dos respuestas? porque creo que ambos dieron información muy valiosa. – DMC

Respuesta

4

Dado que el cambio de EF seguimiento de cada objeto en el gráfico, siempre se puede pasar a cualquier instancia en la gráfica para el cambio de seguimiento y será darle los valores de seguimiento de cambios. Por ejemplo, el código siguiente obtendrá los valores originales/corriente de propiedad de navegación de la AuditObject:

DbMemberEntry t1 = entity.Member("RelatedWebObjects"); 
// cannot find original relationship collection.... 

AuditObject currentAuditObject = (AuditObject) entity; 
var currValues = this.Entry(currentAuditObject.RelatedWebObjects).CurrentValues; 
var orgValues = this.Entry(currentAuditObject.RelatedWebObjects).OriginalValues; 

o se puede aplicar el mismo truco cuando se trata de una propiedad de colección tipo de navegación:

DbCollectionEntry t2 = entity.Collection("RelatedWebObjects"); 
// cannot find original relationship collection.... 

foreach (WebObject item in currentAuditObject.RelatedWebObjects) 
{ 
    var currValues = this.Entry(item).CurrentValues; 
} 
+0

gracias una vez más Morteza! Te agradezco que vuelvas tan rápido. también gracias de nuevo por el blog! – DMC

12

Bueno, esto es un poco difícil. En primer lugar tiene que ser diferente two types of relationships ofrecido por EF:

  • Asociación independiente (todas las relaciones muchos-a-muchos y algunos de uno a muchos) (todas las relaciones
  • asociación de clave externa de uno a uno y algunos uno-a-muchos)

Ahora, si desea conocer el valor anterior de la asociación de clave externa, solo necesita rastrear los cambios en la entidad dependiente donde ha expuesto la propiedad de clave externa; esto es exactamente lo mismo que rastreando cualquier otro cambio de propiedad.

Si desea hacer un seguimiento de los cambios en una asociación independiente, la situación será más difícil porque DbContext API doesn't provide operations to track them. Debe volver a ObjectContext API y es ObjectStateManager.

ObjectContext objectContext = ((IObjectContextAdapter)dbContext).ObjectContext; 
foreach (ObjectStateEntry entry = objectContext.ObjectStateManager 
               .GetObjectStateEntries(~EntityState.Detached) 
               .Where(e => e.IsRelationship)) 
{ 
    // Track changes here 
} 

Ahora usted tienen acceso a la ObjectStateEntry casos para la relación. Estas instancias nunca deberían tener el estado Modified. Serán Added, Deleted o Unchanged porque "modificación" se procesa como eliminación de la relación anterior y adición de una nueva. ObjectStateEntry también contiene colecciones CurrentValues y OriginalValues. Estas colecciones también deben contener dos elementos, cada uno representando EntityKey de entidad en un lado de la relación.

+0

muchas gracias por su ayuda! Puedo ver que esto funciona. también aprendí sobre el operador bitwise ~, nunca lo había usado antes. ¿Sabes si es posible asignarle puntos tanto a ti como a Morteza? su respuesta llegó primero y también funciona y me comuniqué con él específicamente a través de su blog, pero quiero que ambos obtengan crédito ... – DMC

+0

@DMC: puede marcar solo una respuesta como aceptada, pero dependiendo de su reputación también puede lanzar votaciones ascendentes a tantas respuestas como desee. –

+0

lo siento, se lo daré a Morteza porque él lo recibió primero y me desvié de él para contactarlo directamente sobre esto. pero te di un voto positivo para esto. – DMC

Cuestiones relacionadas