7

Estoy anulando SaveChanges en mi DbContext para implementar un registro de auditoría. Trabajar con relaciones de muchos a muchos o asociaciones independientes es relativamente fácil ya que EF crea ObjectStateEntries para cualquier cambio en ese tipo de relaciones.Entity Framework: Seguimiento de cambios en las asociaciones de FK

Estoy utilizando asociaciones de claves foráneas, y cuando una relación entre entidades cambia, todo lo que obtienes es un ObjectStateEnty que dice, por ejemplo, que la entidad "Título" tiene la propiedad "PublisherID" modificada. Para un ser humano, esta es obviamente una clave foránea en la entidad Título, pero ¿cómo puedo determinar esto en tiempo de ejecución? ¿Hay alguna forma de traducir este cambio a una propiedad "PublisherID" para que aparezca una EntityKey para la entidad que representa la clave externa?

Asumo que estoy tratando con entidades que se ven así:

public sealed class Publisher 
{ 
    public Guid ID { get; set; } 
    public string Name { get; set; } 
    public ICollection<Title> Titles { get; set; } 
} 

public class Title 
{ 
    public Guid ID { get; set; } 
    public string Name { get; set; } 
    public Guid? PublisherID { get; set; } 
    public Publisher Publisher { get; set; } 
} 

Existe también EF código EntityConfiguration que define la relación y la clave externa:

public TitleConfiguration() 
{ 
    HasOptional<Publisher>(t => t.Publisher).WithMany(
      p => p.Titles).HasForeignKey(t => t.PublisherID); 
} 

Lo que estoy haciendo ahora parece un poco demasiado complicado. Espero que haya una forma más elegante de lograr mi objetivo. Para cada propiedad modificada de ObjectStateEntry miro todas las referencias referenciales para la entidad actual y veo si alguna de ellas la usa como clave externa. El código siguiente se llama desde SaveChanges():

private void HandleProperties(ObjectStateEntry entry, 
     ObjectContext ctx) 
{ 
    string[] changedProperties = entry.GetModifiedProperties().ToArray(); 
    foreach (string propertyName in changedProperties) 
    { 
     HandleForeignKey(entry, ctx, propertyName); 
    } 
} 

private void HandleForeignKey(ObjectStateEntry entry, 
     ObjectContext ctx, string propertyName) 
{ 
    IEnumerable<IRelatedEnd> relatedEnds = 
      entry.RelationshipManager.GetAllRelatedEnds(); 

    foreach (IRelatedEnd end in relatedEnds) 
    { 
     // find foreign key relationships 
     AssociationType elementType = end.RelationshipSet.ElementType as 
       AssociationType; 
     if (elementType == null || !elementType.IsForeignKey) continue; 

     foreach (ReferentialConstraint constraint in 
       elementType.ReferentialConstraints) 
     { 
      // Multiplicity many means we are looking at a foreign key in a 
      // dependent entity 
      // I assume that ToRole will point to a dependent entity, don't 
      // know if it can be FromRole 
      Debug.Assert(constraint.ToRole.RelationshipMultiplicity == 
        RelationshipMultiplicity.Many); 
      // If not 1 then it is a composite key I guess. 
      // Becomes a lot more difficult to handle. 
      Debug.Assert(constraint.ToProperties.Count == 1); 
      EdmProperty prop = constraint.ToProperties[0]; 

      // entity types of current entity and foreign key entity 
      // must be the same 
      if (prop.DeclaringType == entry.EntitySet.ElementType 
        && propertyName == prop.Name) 
      { 
       EntityReference principalEntity = end as EntityReference; 
       if (principalEntity == null) continue; 

       EntityKey newEntity = principalEntity.EntityKey; 
       // if there is more than one, the foreign key is composite 
       Debug.Assert(newEntity.EntityKeyValues.Length == 1); 

       // create an EntityKey for the old foreign key value 
       EntityKey oldEntity = null; 

       if (entry.OriginalValues[prop.Name] is DBNull) 
       { 
        oldEntity = new EntityKey(); 
        oldEntity.EntityKeyValues = new[] { 
         new EntityKeyMember("ID", "NULL") 
        }; 
        oldEntity.EntitySetName = newEntity.EntitySetName; 
       } 
       else 
       { 
        Guid oldGuid = Guid.Parse(
          entry.OriginalValues[prop.Name].ToString()); 
        oldEntity = ctx.CreateEntityKey(newEntity.EntitySetName, 
          new Publisher() 
          { 
           ID = oldGuid 
          }); 
       } 

       Debug.WriteLine(
         "Foreign key {0} changed from [{1}: {2}] to [{3}: {4}]", 
         prop.Name, 
         oldEntity.EntitySetName, oldEntity.EntityKeyValues[0], 
         newEntity.EntitySetName, newEntity.EntityKeyValues[0]); 
      } 
     } 
    } 
} 

espero que esto ayuda a ilustrar mejor lo que estoy tratando de lograr. Cualquier entrada es bienvenida.

Gracias!

+0

¿Qué pasa con la auditoría solo Id change? Si audita directamente en la base de datos, también habrá cambiado Id. Por cierto. si no te gusta, ¿por qué estás usando asociaciones de FK en lugar de asociaciones independientes? –

+0

Estoy tratando de proporcionarle al usuario final algo más descriptivo que PublisherID cambiado de b087929b-3221-4c43-916b-ad49066969c8 a 6372b8b6-1848-4f7a-ada0-d778a5682d67. Posiblemente un hipervínculo que apunta a la entidad. Entonces, o bien al momento de recopilar datos para el registro de auditoría o al mostrar el registro de auditoría, tendré que determinar si una propiedad es una clave externa y a qué tipo de entidad apunta. – LeffeBrune

+0

Leí su artículo sobre asociaciones independientes y me quedé con la sensación de que podría tener algunos problemas más adelante si elijo seguir ese camino. Además de eso, si alguna vez decidiera serializar mis entidades POCO con un valor de clave externa adecuada, podría ser útil. Con asociaciones independientes tendré que serializar algún tipo de gráfico de entidades para que esos datos sean útiles. – LeffeBrune

Respuesta

1

Parece que mi código es la solución adecuada a este problema:/

que las usamos asociaciones independientes para evitar este problema por completo.

Cuestiones relacionadas