2009-05-22 4 views
6

Devo impostare EntityKey di EntityObject. Conosco il suo tipo e il suo valore di identificazione. Non voglio interrogare il database inutilmente.EntityKey e ApplyPropertyChanges()

Questo funziona ...

// 
// POST: /Department/Edit/5 

[AcceptVerbs(HttpVerbs.Post)] 
public ActionResult Edit(Guid id, Department Model) 
{ 
    Model.EntityKey = (from Department d in db.Department 
         where d.Id == id 
         select d).FirstOrDefault().EntityKey; 
    db.ApplyPropertyChanges(Model.EntityKey.EntitySetName, Model); 
    db.SaveChanges(); 
    return RedirectToAction("Index"); 
} 

Questo fallisce ...

// 
// POST: /Department/Edit/5 

[AcceptVerbs(HttpVerbs.Post)] 
public ActionResult Edit(Guid id, Department Model) 
{ 
    String EntitySetName = db.DefaultContainerName + "." + Model.GetType().Name; 
    Model.EntityKey = new System.Data.EntityKey(EntitySetName, "Id", Model.Id); 
    db.ApplyPropertyChanges(Model.EntityKey.EntitySetName, Model); 
    db.SaveChanges(); 
    return RedirectToAction("Index"); 
} 

I ApplyPropertyChanges() linea non riesce con questa eccezione:

L'ObjectStateManager non lo fa contengono un oggetto ObjectStateEntry con unRiferimentoa un oggetto di tipo "Sample.Models.Department".

Le due EntityKeys sono uguali. Perché il secondo blocco di codice fallisce? Come posso ripararlo?

risposta

9

Il motivo per cui il secondo blocco di codice non riesce è perché EF non riesce a trovare l'oggetto nell'ObjectStateManager, ovvero quando estrae gli oggetti dal db li inserisce nello stato manager in modo da poterli tenere traccia - questo è simile a il modello Identity Map. Nonostante abbia EntityKey, il tuo oggetto non è nel gestore dello stato, quindi EF non è in grado di mantenere le modifiche. Puoi aggirare questo mettendo l'oggetto nello stato manager da solo, ma sei un po 'subdolo.

Questo funziona:

[AcceptVerbs(HttpVerbs.Post)] 
public ActionResult Edit(Guid id, Department model) 
{ 
    var entitySetName = db.DefaultContainerName + "." + model.GetType().Name; 
    var entityKey = new System.Data.EntityKey(entitySetName, "Id", model.Id); 

    db.Attach(new Department{Id = id, EntityKey = entityKey}); 
    db.AcceptAllChanges(); 

    db.ApplyPropertyChanges(entitySetName, model); 
    db.SaveChanges(); 
} 

... ma non è molto pulito. In pratica si tratta di allegare un oggetto 'vuoto' con solo una chiave di entità, accettando tutte le modifiche e quindi chiamando ApplyPropertyChanges con i valori effettivi aggiornati reali.

Ecco la stessa cosa racchiusa in un metodo di estensione: questo dovrebbe funzionare per tutto ciò che utilizza una singola colonna db per la chiave primaria. L'unica parte interessante della chiamata al metodo è che è necessario per dirgli come trovare la proprietà chiave tramite un delegato come secondo argomento al metodo di estensione:

[AcceptVerbs(HttpVerbs.Post)] 
public ActionResult Edit(Guid id, Department model) 
{ 
    db.ApplyDetachedPropertyChanges(model, x => x.Id); 
    db.SaveChanges(); 
} 

ed il metodo di estensione:

public static class EfExtensions 
{ 
    public static void ApplyDetachedPropertyChanges<T>(this ObjectContext db, T entity, Func<T, int> getIdDelegate) 
    where T : EntityObject 
    { 
    var entitySetName = db.DefaultContainerName + "." + entity.GetType().Name; 
    var id = getIdDelegate(entity); 
    var entityKey = new EntityKey(entitySetName, "Id", id); 

    db.Attach(new Department {Id = id, EntityKey = entityKey}); 
    db.AcceptAllChanges(); 

    db.ApplyPropertyChanges(entitySetName, entity); 
    } 
} 

Poiché il metodo di estensione chiama AcceptAllChanges, è necessario fare attenzione a chiamare questo se si eseguono aggiornamenti su più entità contemporaneamente: si potrebbero facilmente "perdere" gli aggiornamenti se non si presta attenzione. Quindi questo approccio è veramente adatto solo per scenari di aggiornamento semplici - ad es. molti metodi di azione MVC :)

+0

+1 per il metodo di estensione. Era carino! =) Come dovrebbe cambiare il metodo se la tabella usa un PK a colonne multiple? –

+0

È necessario eseguire un sovraccarico del metodo di estensione che ha richiesto a IEnumerable di KeyValuePair di specificare la chiave, quindi chiamare il sovraccarico del costruttore corrispondente su EntityKey. Per mantenere la sicurezza dello stype ed evitare il passaggio di stringhe nel KeyValuePair, è possibile passare una serie di delegati di "ricerca di chiavi" insieme ai valori da inserire nelle chiavi. –

+1

Sulla base di questa ottima risposta, l'ho migliorata un po 'e ho creato il mio piccolo metodo, che ho anche postato come risposta. Funziona per qualsiasi tipo di oggetto entità e non utilizza delegati. –

4
public static class EfExtensions 
{ 
    public static void ApplyDetachedPropertyChanges<T>(this ObjectContext db, T entity, Func<T, int> getIdDelegate) 
    where T : EntityObject 
    { 
     var entitySetName = db.DefaultContainerName + "." + entity.GetType().Name; 

     T newEntity = Activator.CreateInstance<T>(); 
     newEntity.EntityKey = db.CreateEntityKey(entitySetName, entity); 

     Type t = typeof(T); 
     foreach(EntityKeyMember keyMember in newEntity.EntityKey.EntityKeyValues) { 
      PropertyInfo p = t.GetProperty(keyMember.Key); 
      p.SetValue(newEntity, keyMember.Value, null); 
     } 

     db.Attach(newEntity); 
     //db.AcceptAllChanges(); 

     db.ApplyPropertyChanges(entitySetName, entity); 
    } 
} 
+0

Grazie, stavo cercando qualcosa di simile, e tu hai fornito la necessità! +1 – camainc

+1

Ho dovuto aggiungere un metodo di estensione stringa "ToPlural()" a "entity.GetType(). Name" perché i miei set di entità sono pluralizzati dal generatore di codice. Oltre a questo, il tuo codice ha funzionato perfettamente. – camainc

2

Prova ad utilizzare sotto il codice e fammi sapere se funziona per voi.

[AcceptVerbs(HttpVerbs.Post)] 
public ActionResult Edit(Guid id, Department Model) 
{ 
    using (var context = new EntityContext()) 
    { 
     try 
     { 
      Object entity = null; 
      IEnumerable<KeyValuePair<string, object>> entityKeyValues = 
       new KeyValuePair<string, object>[] { 
        new KeyValuePair<string, object>("DepartmentID", id) }; 

      // Create the key for a specific SalesOrderHeader object. 
      EntityKey key = new EntityKey("EntityContext.Deparment", 
                    entityKeyValues); 

      // Get the object from the context or the persisted store by its key. 
      if (context.TryGetObjectByKey(key, out entity)) 
      { 
       context.ApplyPropertyChanges(key.EntitySetName, Model); 
       context.SaveChanges(); 
      } 
      else 
      { 
       // log message if we need 
       //"An object with this key could not be found." 
      }     
     } 
     catch (EntitySqlException ex) 
     { 
      // log message 
     } 
    } 
}  
1

Ho avuto lo stesso problema, mentre stava lavorando su altre pagine. Non so se questo commento sarà utile ... Ma alla fine ho scoperto che nel mio oggetto (l'equivalente del tuo dipartimento), il mio "Id" non compariva nella forma ...

Solo il fatto di aggiungilo (mentre lo avevo rimosso ...) sul mio modulo ha risolto il mio problema. (con lo style = "visibility: hidden", per non vederlo ...)

2

Migliorando la grande implementazione di Steve Willcock, ecco il mio suggerimento.

Utilizza Reflection (una parte di .NET) molto più dell'esempio originale, per risparmiare un po 'di codice. Supporta inoltre automaticamente qualsiasi tipo di classe di entità e non solo un "Dipartimento".

Inoltre, elimina il metodo obsoleto ApplyPropertyChanges e utilizza il nuovo metodo ApplyCurrentValues.

Il metodo

Il metodo fondamentalmente solo utilizza la reflection per ottenere il valore della proprietà "Id" in modo dinamico, e l'impostazione troppo. Questo salva tutte le seccature con un delegato.

public static void ApplyDetachedPropertyChanges<T>(this ObjectContext db, T entity) where T : EntityObject 
{ 
    PropertyInfo idProperty = typeof(T).GetProperty("Id"); 

    var entitySetName = db.DefaultContainerName + "." + entity.GetType().Name; 
    var id = idProperty.GetValue(entity, null); 
    var entityKey = new EntityKey(entitySetName, "Id", id); 

    Type type = entity.GetType(); 
    EntityObject obj = (EntityObject)Activator.CreateInstance(type); 

    idProperty.SetValue(obj, id, null); 
    obj.EntityKey = entityKey; 

    db.Attach(obj); 
    db.AcceptAllChanges(); 

    db.ApplyCurrentValues(entitySetName, entity); 
} 

Uso

suo utilizzo è abbastanza semplice così.

[AcceptVerbs(HttpVerbs.Post)] 
public ActionResult Edit(Guid id, Department Model) 
{ 
    db.ApplyDetachedPropertyChanges(Model); 
    db.SaveChanges(); 
} 
Problemi correlati