c# - Entity Framework Code First-Perché non posso aggiornare le proprietà complesse in questo modo?




entity-framework ef-code-first entity-framework-4.1 (4)

Sto lavorando su un piccolo progetto di esempio utilizzando Entity Framework 4.1 (prima il codice). Le mie lezioni assomigliano a questo:

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; }
}

Ho salvato un paio di EmployeeTypes ("prima", "seconda") nel database e ho salvato una persona che ha il primo tipo. Ora voglio modificare la persona. So che posso farlo caricando la persona, modificando le proprietà e quindi salvando. Ma quello che voglio invece, che mi sembra debba funzionare, è questo:

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();

Stranamente, questo cambia FirstName e LastName ma non EmployeeType. Se ottengo un nuovo contesto e richiedo questa persona, EmployeeType rimane impostato su "prima" come era prima dell'esecuzione di questo codice.

Cosa devo fare per aggiornare le proprietà di navigazione e non solo le proprietà scalari? (Questo è particolarmente sconcertante perché per EmployeeType, l'unica cosa che deve effettivamente cambiare è la chiave esterna nella tabella Persona, e quella chiave è una proprietà scalare.)

(A proposito, so che posso farlo recuperando prima la Persona, poi cambiando le proprietà una alla volta, ma visto che sto usando l'associazione del modello in ASP.NET MVC, sembra che in questo modo sarebbe più facile perché io avrò già l'oggetto persona aggiornato nel mio metodo POST.)


Answers

Puoi provarlo in modo diverso:

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();

Un altro approccio consiste nell'esporre la chiave esterna nell'entità Person come:

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; }
}

Ciò cambierà il tipo di relazione tra Person e EmployeeType da Associazione indipendente a Associazione chiave esterna. Invece di assegnare la proprietà di navigazione, assegnare la proprietà della chiave esterna. Questo ti permetterà di modificare la relazione con il tuo codice attuale.

Il problema è che le associazioni indipendenti (quelle che non usano la proprietà della chiave esterna) sono gestite come oggetto separato in State Manager / Change Tracker. Quindi la tua modifica della persona non ha influenzato lo stato della relazione esistente né ha impostato la nuova relazione. Ho chiesto su MSDN come farlo con l'API di DbContext ma è possibile solo se lanci DbContext su ObjectContext e usi ObjectStateManager e ChangeRelationshipState .


Questo funziona per le collezioni, anche se mi sento come se ci fosse un modo migliore.


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();

Dopo aver provato una dozzina di modi diversi per farlo in modo EF, ho concluso che non esiste un codice EF ragionevole. Il primo modo per fare ciò che sto cercando di fare. Quindi ho usato la riflessione. Ho creato questo metodo per la mia classe che eredita da 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);
    }
}

Tutti i miei oggetti ereditano da una classe astratta e hanno una proprietà di chiave primaria in comune, quindi questo trova l'oggetto esistente con la stessa chiave di quella passata e quindi aggiorna gli oggetti esistenti da quello nuovo. SaveChanges deve essere chiamato in seguito.






c# entity-framework ef-code-first entity-framework-4.1