update one many framework first collection entity-framework many-to-many change-tracking

many - Entity Framework 4.1+ relaciones de muchos a muchos cambian de seguimiento



update list entity framework c# (2)

¿Cómo puedo detectar cambios en las propiedades de ICollection <> (relaciones de muchos a muchos)?

public class Company { ... public virtual ICollection<Employee> Employees { get; set; } } using (DataContext context = new DataContext(Properties.Settings.Default.ConnectionString)) { Company company = context.Companies.First(); company.Employees.Add(context.Employees.First()); context.SaveChanges(); } public class DataContext : DbContext { public override int SaveChanges() { return base.SaveChanges(); // Company''s entity state is "Unchanged" in this.ChangeTracker } }


Aquí es cómo encontrar todas las relaciones cambiadas de muchos a muchos. Implementé el código como métodos de extensión:

public static class IaExtensions { public static IEnumerable<Tuple<object, object>> GetAddedRelationships( this DbContext context) { return GetRelationships(context, EntityState.Added, (e, i) => e.CurrentValues[i]); } public static IEnumerable<Tuple<object, object>> GetDeletedRelationships( this DbContext context) { return GetRelationships(context, EntityState.Deleted, (e, i) => e.OriginalValues[i]); } private static IEnumerable<Tuple<object, object>> GetRelationships( this DbContext context, EntityState relationshipState, Func<ObjectStateEntry, int, object> getValue) { context.ChangeTracker.DetectChanges(); var objectContext = ((IObjectContextAdapter)context).ObjectContext; return objectContext .ObjectStateManager .GetObjectStateEntries(relationshipState) .Where(e => e.IsRelationship) .Select( e => Tuple.Create( objectContext.GetObjectByKey((EntityKey)getValue(e, 0)), objectContext.GetObjectByKey((EntityKey)getValue(e, 1)))); } }

Alguna explicación Las relaciones de muchos a muchos están representadas en EF como asociaciones independientes o IA. Esto se debe a que las claves externas para la relación no están expuestas en ningún lugar del modelo de objetos. En la base de datos, los FK están en una tabla de unión, y esta tabla de unión está oculta del modelo de objetos.

Los IA se rastrean en EF utilizando "entradas de relación". Estos son similares a los objetos DbEntityEntry que obtienes de DbContext.Entry, excepto que representan una relación entre dos entidades en lugar de una entidad en sí misma. Las entradas de relación no están expuestas en la API de DbContext, por lo que debe acceder a ObjectContext para acceder a ellas.

Se crea una nueva entrada de relación cuando se crea una nueva relación entre dos entidades, por ejemplo, agregando un empleado a la empresa. Colección de empleados. Esta relación está en el estado Agregado.

Del mismo modo, cuando se elimina una relación entre dos entidades, la entrada de relación se coloca en el estado Eliminado.

Esto significa que para encontrar relaciones cambiadas de muchos a muchos (o en realidad cualquier IA modificado) necesitamos encontrar entradas de relación añadidas y eliminadas. Esto es lo que hacen GetAddedRelationships y GetDeletedRelationships.

Una vez que tenemos entradas de relación, debemos encontrarle sentido. Para esto, necesitas saber un poco de conocimiento interno. La propiedad CurrentValues ​​de una entrada de relación Added (o Unchanged) contiene dos valores que son los objetos EntityKey de las entidades al final de la relación. Del mismo modo, pero molestamente ligeramente diferente, la propiedad OriginalValues ​​de una entrada de relación eliminada contiene los objetos EntityKey para las entidades al final de la relación eliminada.

(Y, sí, esto es horrible. No me culpen, es mucho antes de mi tiempo).

La diferencia CurrentValues ​​/ OriginalValues ​​es la razón por la que pasamos un delegado al método privado GetRelationships.

Una vez que tenemos los objetos EntityKey, podemos usar GetObjectByKey para obtener las instancias de entidad reales. Devolvemos estos como tuplas y allí lo tienes.

Aquí hay algunas entidades, un contexto y un inicializador, solía probar esto. (La prueba de la nota no fue extensa).

public class Company { public int Id { get; set; } public string Name { get; set; } public virtual ICollection<Employee> Employees { get; set; } public override string ToString() { return "Company " + Name; } } public class Employee { public int Id { get; set; } public string Name { get; set; } public virtual ICollection<Company> Companies { get; set; } public override string ToString() { return "Employee " + Name; } } public class DataContext : DbContext { static DataContext() { Database.SetInitializer(new DataContextInitializer()); } public DbSet<Company> Companies { get; set; } public DbSet<Employee> Employees { get; set; } public override int SaveChanges() { foreach (var relationship in this.GetAddedRelationships()) { Console.WriteLine( "Relationship added between {0} and {1}", relationship.Item1, relationship.Item2); } foreach (var relationship in this.GetDeletedRelationships()) { Console.WriteLine( "Relationship removed between {0} and {1}", relationship.Item1, relationship.Item2); } return base.SaveChanges(); } } public class DataContextInitializer : DropCreateDatabaseAlways<DataContext> { protected override void Seed(DataContext context) { var newMonics = new Company { Name = "NewMonics", Employees = new List<Employee>() }; var microsoft = new Company { Name = "Microsoft", Employees = new List<Employee>() }; var jim = new Employee { Name = "Jim" }; var arthur = new Employee { Name = "Arthur" }; var rowan = new Employee { Name = "Rowan" }; newMonics.Employees.Add(jim); newMonics.Employees.Add(arthur); microsoft.Employees.Add(arthur); microsoft.Employees.Add(rowan); context.Companies.Add(newMonics); context.Companies.Add(microsoft); } }

Aquí hay un ejemplo de usarlo:

using (var context = new DataContext()) { var microsoft = context.Companies.Single(c => c.Name == "Microsoft"); microsoft.Employees.Add(context.Employees.Single(e => e.Name == "Jim")); var newMonics = context.Companies.Single(c => c.Name == "NewMonics"); newMonics.Employees.Remove(context.Employees.Single(e => e.Name == "Arthur")); context.SaveChanges(); }


No puedo darle el código exacto para su situación, pero puedo decirle que su situación se simplificará diez veces al tener una tabla de unión entre los empleados y la empresa solo para romper la relación de muchos a muchos.