2012-07-26 28 views
14

Tengo problemas con la detección de cambios de una propiedad de navegación:Entity Framework no detectará los cambios de las propiedades de navegación

Mi modelo de prueba tiene el siguiente aspecto:

public class Person 
{ 
    public int Id { get; set; } 

    public string Name { get; set; } 

    public virtual Address Address { get; set; } 
} 

public class Address 
{ 
    public int Id { get; set; } 

    public string Name { get; set; } 
} 

que he creado y guardado un objeto de tipo Persona con propiedades de Nombre y Dirección asignadas. Mi problema es que si recupero el objeto Persona de la base de datos y cambio la propiedad Dirección (por ejemplo, a Nulo), entonces la e.f. no detecta el cambio! Mi código es la siguiente:

using (var ctx = new EFContext()) 
{ 
    Person p = ctx.People.First(); 
    //p.Address IS NOT NULL! 
    p.Address = null; 
    var entry = ctx.Entry(p); 
} 

¿Por qué es entry.StateSin cambios?

Edit: Si llamo a SaveChanges, el registro se guarda correctamente (¡la dirección se vuelve nula)!

Edición 2: He creado la propiedad de clave externa como sugirió Billy y si inspecciono el objeto Persona en el estudio visual del estado se modifica .. si no se detienen con el depurador inspeccionar los valores del objeto de las el estado no ha cambiado!

Editar 3: Cargando el objeto Persona usando ctx.People.Include (x => x.Address) .First(); resuelve el problema ¿Hay alguna forma de evitar llamar a Include y continuar modificando la propiedad Address en lugar de AddressId?

+0

¿Qué ocurre si llama a ctx.DetectChanges()? – Maarten

+0

Nada. ¡¡El resultado es el mismo!! – Mones

Respuesta

24

en primer lugar: Debe seguir el consejo de Billy @ usar Include. Su comentario "p.Address NO ES NULO!" solo es cierto porque está viendo p.Address en el depurador y desencadena la carga diferida en el depurador, por lo que se detecta el cambio de configuración de la dirección a null. En el modo de lanzamiento o cuando no inspecciona las propiedades en el depurador, su código no funcionaría y no se guardarían cambios.

Por lo tanto, la respuesta a su Edición 3 es: Nº

Segundo: var entry = ctx.Entry(p) sólo se devuelve entidad establecen y no cambió un estado de entidad pero en su lugar un estado relación, o más precisamente, se borrado una relación.No se puede inspeccionar los estados de relación con la API DbContext pero sólo con la ObjectContext API:

Person p = ctx.People.Include(x => x.Address).First(); 
p.Address = null; 
var objCtx = ((IObjectContextAdapter)ctx).ObjectContext; 
var objentr = objCtx.ObjectStateManager.GetObjectStateEntries(EntityState.Deleted); 

objentr tendrá una entrada del tipo RelationshipEntry ahora:

enter image description here

EF tendrá en cuenta esta entrada relación junto con las entradas del estado de la entidad cuando llame al SaveChanges() y elimine la relación, es decir, configure la columna clave externa Address del Person en la base de datos en NULL.

Acerca de Edit 2: Cambiar una propiedad de clave foránea (que es una propiedad escalar en su modelo) es un cambio de la propia entidad, por lo que el estado de la entidad será Modified en este caso.

+0

¡Guau! gracias realmente buena respuesta! –

+0

Buena respuesta, pero tengo una duda. Cómo identifico que una entrada específica sufrió estos cambios y no otra, porque esta es una consulta al contexto específico. –

+0

¿Cómo se hace esto en EF Core, porque no hay 'ObjectContext'? – grokky

5

Debe incluir la Dirección de navegación. apuntalar. en su consulta, de lo contrario de EF no considerará cambios en él cuando se guarda:

using (var ctx = new EFContext()) 
{ 
    Person p = ctx.People.Include(x => x.Address).First(); 
    //p.Address IS NOT NULL! 
    p.Address = null; 
    var entry = ctx.Entry(p); 
} 

También es posible usar las claves externas en su modelo, que me gusta mucho:

public class Person 
{ 
    public int Id { get; set; } 

    public string Name { get; set; } 

    public virtual Address Address { get; set; } 

    public int? AddressId {get; set;} 
} 

...

using (var ctx = new EFContext()) 
{ 
    Person p = ctx.People.First(); 
    p.AddressId = null; 
    var entry = ctx.Entry(p); 
} 
+0

He probado la solución con DetectChanges, pero de ninguna manera ... todavía tengo el estado Sin cambios! Si uso el int? clave externa Supongo que funcionará bien porque voy a cambiar el valor de una propiedad "simple" y no de la propiedad de navegación directamente. De hecho, si cambio la propiedad Name del objeto Person todo está bien. – Mones

2

En mi aplicación, antes de que se solicite una recarga o el usuario abandona el elemento/vista, realizo algunas comprobaciones para asegurarme de que no haya cambios no guardados.

Esto se está ejecutando básicamente en la respuesta actualmente aceptada, pero quería proporcionar una implementación y llamar la atención que debe llamar al Context.ChangeTracker.DetectChanges() antes de que el ObjectContext.ObjectStateManager pueda detectar cambios en las relaciones. Pasé bastante tiempo depurando esto, ¡tonto!

_EagleContext.ChangeTracker.DetectChanges(); 

var objectContext = ((IObjectContextAdapter)_EagleContext).ObjectContext; 
var changedEntities = objectContext.ObjectStateManager.GetObjectStateEntries(EntityState.Added | EntityState.Deleted | EntityState.Modified); 

if (_EagleContext.ChangeTracker.Entries().Any(e => e.State == EntityState.Modified) 
    || changedEntities.Count() != 0) 
{ 
    var dialogResult = MessageBox.Show("There are changes to save, are you sure you want to reload?", "Warning", MessageBoxButton.YesNo); 
    if (dialogResult == MessageBoxResult.No) 
    { 
     return; 
    } 
} 

// Continue with reloading... 
Cuestiones relacionadas