2011-03-31 10 views
27

Estoy trabajando en un proyecto de muestra pequeño utilizando Entity Framework 4.1 (código primero). Mis clases se ven así:Código Entity Framework Primero: ¿Por qué no puedo actualizar propiedades complejas de esta manera?

public class Context : DbContext 
{ 
    public IDbSet<Person> People { get; set; } 
    public IDbSet<EmployeeType> EmployeeTypes { get; set; } 
} 

public class Person 
{ 
    [Key] 
    public int Key { get; set; } 
    public string FirstName { get; set; } 
    public string LastName { get; set; } 

    virtual public EmployeeType EmployeeType { get; set; } 
} 

public class EmployeeType 
{ 
    [Key] 
    public int Key { get; set; } 
    public string Text { get; set; } 

    virtual public ICollection<Person> People { get; set; } 
} 

he ahorrado un par de EmployeeTypes ("primero", "segundo") a la base de datos, y me han ahorrado una persona que tiene el primer tipo. Ahora quiero modificar la Persona. Sé que puedo hacer esto cargando la Persona, cambiando las propiedades y luego guardando. Pero lo que quiero hacer en su lugar, lo que me parece que se debe trabajar, es la siguiente:

var c = new Context(); 
var e = c.EmployeeTypes.Single(x => x.Text.Equals("second")); 
var p = new Person { 
      Key = originalKey,  // same key 
      FirstName = "NewFirst", // new first name 
      LastName = "NewLast", // new last name 
      EmployeeType = e };  // new employee type 
c.Entry(p).State = EntityState.Modified; 
c.SaveChanges(); 

Curiosamente, esto cambia nombre y apellido, pero no EmployeeType. Si obtengo un nuevo contexto y solicito esta persona, EmployeeType permanece configurado en "primero" como estaba antes de que se ejecutara este código.

¿Qué debo hacer para actualizar las propiedades de navegación, y no solo las propiedades escalares? (Esto es especialmente desconcertante porque para EmployeeType, lo único que realmente necesita cambiar es la clave externa en la tabla Person, y esa clave es una propiedad escalar.)

(Por cierto, sé que puedo hacer esto recuperando primero a la persona, luego cambiando las propiedades una a una, pero como estoy usando el enlace de modelo en ASP.NET MVC, parece que de esta manera sería más fácil porque tendré el objeto de persona actualizado ya en mi método POST)

Respuesta

19

se puede probar de forma diferente:.

var c = new Context(); 
var e = c.EmployeeTypes.Single(x => x.Text.Equals("second")); 
var p = new Person { 
      Key = originalKey,  // same key 
      FirstName = "NewFirst", // new first name 
      LastName = "NewLast"}; // new last name 
c.People.Attach(p); // Attach person first so that changes are tracked 
c.Entry(p).Reference(e => e.EmployeeType).Load();    
p.EmployeeType = e; // Now context should know about the change 
c.Entry(p).State = EntityState.Modified; 
c.SaveChanges(); 

Otro enfoque es la exposición de clave externa i n su entidad Person como:

public class Person 
{ 
    [Key] 
    public int Key { get; set; } 
    public string FirstName { get; set; } 
    public string LastName { get; set; } 
    [ForeignKey("EmployeeType")] 
    public int EmployeeTypeKey { get; set; } 
    public virtual EmployeeType EmployeeType { get; set; } 
} 

Esto cambiará el tipo de relación entre Person y EmployeeType de la asociación independiente a la asociación de clave externa. En lugar de asignar la propiedad de navegación, asigne la propiedad de la clave foránea. Esto le permitirá modificar la relación por su código actual.

El problema es que las asociaciones independientes (aquellas que no usan la propiedad de clave externa) se manejan como un objeto separado en el administrador de estado/change tracker. Entonces su modificación de la persona no afectó el estado de la relación existente ni estableció la nueva relación. I asked on MSDN cómo hacerlo con DbContext API, pero solo es posible si emite DbContext a ObjectContext y usa ObjectStateManager y ChangeRelationshipState.

+0

Ugh, ¿puede ser esa la única manera? Prefiero marcar EmployeeType como Modified y la Person.Aunque cuando lo intenté, tampoco funcionó. –

+0

No funcionará debido a la asociación independiente, como dije, es un objeto separado. Probablemente haya otra forma. Voy a modificar mi respuesta. –

+0

Lo probé de "otra manera": "DbUpdateException: Ocurrió un error al guardar entidades que no exponen propiedades de claves foráneas para sus relaciones. La propiedad EntityEntries devolverá null porque no se puede identificar una sola entidad como el origen de la excepción. El manejo de excepciones al guardar puede hacerse más fácil al exponer propiedades de clave externa en los tipos de entidad. Consulte la InnerException para obtener más información. –

2

Después de probar una docena de maneras diferentes de hacerlo de la manera EF, llegué a la conclusión de que no hay un código de EF razonable. La primera forma de hacer lo que estoy tratando de hacer. Entonces usé la reflexión. He creado este método para mi clase que hereda de DbContext:

public void UpdateFrom<T>(T updatedItem) where T : KeyedItem 
{ 
    var originalItem = Set<T>().Find(updatedItem.Key); 
    var props = updatedItem.GetType().GetProperties(
     BindingFlags.Public | BindingFlags.Instance); 
    foreach (var prop in props) 
    { 
     var value = prop.GetValue(updatedItem, null); 
     prop.SetValue(originalItem, value, null); 
    } 
} 

Todos mis objetos heredar de una clase abstracta y tienen una propiedad clave primaria en común, por lo que este se encuentra el objeto existente con la misma clave que la aprobada in, y luego actualiza el objeto existente desde el nuevo. SaveChanges necesita ser llamado después.

+2

Incidentalmente, después de algunos problemas más de este tipo (particularmente con carga "diferida" (y ahora entiendo el cambio de nombre, ya que la carga diferida de Microsoft no tiene nada en común con la carga lenta)), me di por vencido y cambié a Castle ActiveRecord, con el que he trabajado antes y que hace lo que necesito que haga sin requerir soluciones extrañas. –

+0

Eso en lo que estoy pensando en los últimos días –

1

Esto funciona para colecciones, aunque siento que tiene que haber una manera mejor.


var properties = typeof(TEntity).GetProperties(); 
foreach (var property in properties) 
{ 
    if (property.GetCustomAttributes(typeof(OneToManyAttribute), false).Length > 0) 
    { 
     dynamic collection = db.Entry(e).Collection(property.Name).CurrentValue; 
     foreach (var item in collection) 
     { 
      if(item.GetType().IsSubclassOf(typeof(Entity))) 
      { 
       if (item.Id == 0) 
       { 
        db.Entry(item).State = EntityState.Added; 
       } 
       else 
       { 
        db.Entry(item).State = EntityState.Modified; 
       } 
      } 
     } 
    } 
} 
db.Entry(e).State = EntityState.Modified;    
db.SaveChanges(); 

Cuestiones relacionadas