Entity Framework 5 Aggiornamento di un record


870

Ho esplorato diversi metodi di modifica / aggiornamento di un record all'interno di Entity Framework 5 in un ambiente ASP.NET MVC3, ma finora nessuno di essi ha spuntato tutte le caselle di cui ho bisogno. Spiegherò perché.

Ho trovato tre metodi ai quali citerò i pro e i contro:

Metodo 1: carica il record originale, aggiorna ogni proprietà

var original = db.Users.Find(updatedUser.UserId);

if (original != null)
{
    original.BusinessEntityId = updatedUser.BusinessEntityId;
    original.Email = updatedUser.Email;
    original.EmployeeId = updatedUser.EmployeeId;
    original.Forename = updatedUser.Forename;
    original.Surname = updatedUser.Surname;
    original.Telephone = updatedUser.Telephone;
    original.Title = updatedUser.Title;
    original.Fax = updatedUser.Fax;
    original.ASPNetUserId = updatedUser.ASPNetUserId;
    db.SaveChanges();
}    

Professionisti

  • Può specificare quali proprietà cambiano
  • Le viste non devono contenere tutte le proprietà

Contro

  • 2 query sul database per caricare l'originale, quindi aggiornarlo

Metodo 2: carica il record originale, imposta i valori modificati

var original = db.Users.Find(updatedUser.UserId);

if (original != null)
{
    db.Entry(original).CurrentValues.SetValues(updatedUser);
    db.SaveChanges();
}

Professionisti

  • Solo le proprietà modificate vengono inviate al database

Contro

  • Le viste devono contenere ogni proprietà
  • 2 query sul database per caricare l'originale, quindi aggiornarlo

Metodo 3: collegare il record aggiornato e impostare lo stato su EntityState.Modified

db.Users.Attach(updatedUser);
db.Entry(updatedUser).State = EntityState.Modified;
db.SaveChanges();

Professionisti

  • 1 x query sul database da aggiornare

Contro

  • Impossibile specificare quali proprietà cambiano
  • Le viste devono contenere ogni proprietà

Domanda

La mia domanda a voi ragazzi; esiste un modo chiaro per raggiungere questo insieme di obiettivi?

  • Può specificare quali proprietà cambiano
  • Le viste non devono contenere tutte le proprietà (come la password!)
  • 1 x query sul database da aggiornare

Capisco che è una cosa abbastanza piccola da sottolineare, ma potrei mancare una soluzione semplice a questo. Altrimenti prevarrà il metodo uno ;-)


13
Usa ViewModels e un buon motore di mappatura? Ottieni solo "proprietà da aggiornare" per popolare la tua vista (e quindi per aggiornare). Ci saranno ancora 2 domande per l'aggiornamento (ottieni originale + aggiornalo), ma non lo definirei un "Con". Se questo è il tuo unico problema di prestazione, sei un uomo felice;)
Raphaël Althaus

Grazie @ RaphaëlAlthaus, punto molto valido. Potrei farlo, ma devo creare un'operazione CRUD per un numero di tabelle, quindi sto cercando un metodo che possa lavorare direttamente con il modello per salvarmi creando il ViewModel n-1 per ciascun modello.
Uscito l'

3
Bene, nel mio attuale progetto (anche molte entità) abbiamo iniziato a lavorare su Modelli, pensando che avremmo perso tempo lavorando con ViewModels. Ora andiamo a ViewModels e con l'infrastruttura (non trascurabile) che funziona all'inizio, è molto, molto, molto più chiaro e più facile da mantenere ora. E più sicuro (non c'è bisogno di temere "campi nascosti" dannosi o cose del genere)
Raphaël Althaus

1
E non più (terribili) ViewBags per popolare le tue DropDownLists (abbiamo almeno una DropDownList su quasi tutte le nostre visualizzazioni CRU (D) ...)
Raphaël Althaus

Penso che tu abbia ragione, il mio male per aver cercato di trascurare ViewModels. Sì, a volte ViewBag sembra un po 'sporco. Di solito faccio un ulteriore passo avanti nel blog di Dino Esposito e creo anche InputModels, un po 'di cintura e bretelle ma funziona abbastanza bene. Significa solo 2 modelli extra per modello - doh ;-)
Stokedout

Risposte:


681

Stai cercando:

db.Users.Attach(updatedUser);
var entry = db.Entry(updatedUser);
entry.Property(e => e.Email).IsModified = true;
// other changed properties
db.SaveChanges();

59
ciao @Ladislav Mrnka, se voglio aggiornare tutte le proprietà contemporaneamente, posso usare il codice qui sotto? db.Departments.Attach (dipartimento); db.Entry (dipartimento) .State = EntityState.Modified; db.SaveChanges ();
Foyzul Karim,

23
@Foysal: Sì, puoi.
Ladislav Mrnka,

5
Uno dei problemi con questo approccio è che non puoi deridere db.Entry (), che è una PITA seria. EF ha una storia beffarda ragionevolmente buona altrove - è abbastanza fastidioso che (per quanto posso dire) non ne abbiano una qui.
Ken Smith,

23
@Foysal Doing context.Entry (entity) .State = EntityState.Modified da solo non è necessario, non è necessario eseguire l'allegato. Verrà automaticamente allegato come modificato ...
HelloWorld

4
@ Sandman4, ciò significa che ogni altra proprietà deve essere presente ed essere impostata sul valore corrente. In alcuni progetti applicativi, questo non è fattibile.
Dan Esparza,

176

Mi piace molto la risposta accettata. Credo che ci sia ancora un altro modo di affrontarlo. Supponiamo che tu abbia un elenco molto breve di proprietà che non vorresti mai includere in una vista, quindi quando aggiorni l'entità, queste verrebbero omesse. Diciamo che quei due campi sono Password e SSN.

db.Users.Attach(updatedUser);

var entry = db.Entry(updatedUser);
entry.State = EntityState.Modified;

entry.Property(e => e.Password).IsModified = false;
entry.Property(e => e.SSN).IsModified = false;   

db.SaveChanges();   

Questo esempio consente essenzialmente di lasciare sola la logica aziendale dopo aver aggiunto un nuovo campo alla tabella Utenti e alla vista.


Riceverò comunque un errore se non specifico un valore per la proprietà SSN, anche se ho impostato IsModified su false, convalido comunque la proprietà rispetto alle regole del modello. Quindi, se la proprietà è contrassegnata come NOT NULL, fallirà se non imposto alcun valore diverso da null.
RolandoCC,

Non riceverai un errore perché quei campi non saranno nel tuo modulo. Escludete i campi che sicuramente non aggiornerete, prendete la voce dal database usando il modulo restituito allegandolo e dite alla voce che quei campi non vengono modificati. La convalida del modello è controllata in ModelState, non nel contesto. Questo esempio fa riferimento a un utente esistente, quindi "updatedUser". Se il tuo SSN è un campo obbligatorio, sarebbe stato lì quando è stato creato per la prima volta.
smd

4
Se ho capito bene, "updatedUser" è un'istanza di un oggetto già popolato con un FirstOrDefault () o simile, quindi sto aggiornando solo le proprietà che ho modificato e impostando gli altri su ISModified = false. Funziona benissimo. Ma quello che sto cercando di fare è aggiornare un oggetto senza popolarlo per primo, senza fare alcun FirstOrDefault () prima dell'aggiornamento. Questo è quando ricevo un errore se non specifico un valore per tutti i campi richiesti, anche se ho impostato ISModified = false su quelle proprietà. entry.Property (e => e.columnA) .IsModified = false; Senza questa riga, la colonna A non funzionerà.
RolandoCC,

Quello che stai descrivendo è la creazione di una nuova entità. Questo vale solo per l'aggiornamento.
smd

1
RolandoCC, inserisci db.Configuration.ValidateOnSaveEnabled = false; prima di db.SaveChanges ();
Wilky,

28
foreach(PropertyInfo propertyInfo in original.GetType().GetProperties()) {
    if (propertyInfo.GetValue(updatedUser, null) == null)
        propertyInfo.SetValue(updatedUser, propertyInfo.GetValue(original, null), null);
}
db.Entry(original).CurrentValues.SetValues(updatedUser);
db.SaveChanges();

Questa sembra una soluzione davvero piacevole - niente confusione o confusione; non è necessario specificare manualmente le proprietà e tiene conto di tutti i punti elenco dei PO - c'è qualche motivo per cui questo non ha più voti?
nocarrier

Non lo fa però. Ha uno dei più grandi "contro", più di un hit nel database. Dovresti comunque caricare l'originale con questa risposta.
smd

1
@smd perché dici che colpisce il database più di una volta? Non vedo che ciò accada a meno che l'uso di SetValues ​​() abbia quell'effetto ma ciò non sembra che sarebbe vero.
parlamento

@Parlamento Penso di aver dormito quando l'ho scritto. Scuse. Il problema reale è l'override di un valore nullo previsto. Se l'utente aggiornato non fa più riferimento a qualcosa, non sarebbe corretto sostituirlo con il valore originale se si intendesse cancellarlo.
smd

22

Ho aggiunto un metodo di aggiornamento extra alla mia classe base di repository simile al metodo di aggiornamento generato da Scaffolding. Invece di impostare "modificato" l'intero oggetto, imposta una serie di singole proprietà. (T è un parametro generico di classe.)

public void Update(T obj, params Expression<Func<T, object>>[] propertiesToUpdate)
{
    Context.Set<T>().Attach(obj);

    foreach (var p in propertiesToUpdate)
    {
        Context.Entry(obj).Property(p).IsModified = true;
    }
}

E quindi chiamare, ad esempio:

public void UpdatePasswordAndEmail(long userId, string password, string email)
{
    var user = new User {UserId = userId, Password = password, Email = email};

    Update(user, u => u.Password, u => u.Email);

    Save();
}

Mi piace un viaggio nel database. È probabilmente meglio farlo con i modelli di visualizzazione, tuttavia, al fine di evitare la ripetizione di insiemi di proprietà. Non l'ho ancora fatto perché non so come evitare di inserire i messaggi di convalida sui validatori del mio modello di vista nel mio progetto di dominio.


Aha ... progetto separato per i modelli di vista e progetto separato per i repository che funzionano con i modelli di vista.
Ian Warburton,

11
public interface IRepository
{
    void Update<T>(T obj, params Expression<Func<T, object>>[] propertiesToUpdate) where T : class;
}

public class Repository : DbContext, IRepository
{
    public void Update<T>(T obj, params Expression<Func<T, object>>[] propertiesToUpdate) where T : class
    {
        Set<T>().Attach(obj);
        propertiesToUpdate.ToList().ForEach(p => Entry(obj).Property(p).IsModified = true);
        SaveChanges();
    }
}

Perché non solo DbContext.Attach (obj); DbContext.Entry (obj) .State = EntityState.Modified;
Nelsonson,

Questo controlla la setparte dell'istruzione di aggiornamento.
Tanveer Badar,

4

Solo per aggiungere all'elenco di opzioni. Puoi anche prendere l'oggetto dal database e utilizzare uno strumento di mappatura automatica come Auto Mapper per aggiornare le parti del record che desideri modificare.


3

A seconda del caso d'uso, si applicano tutte le soluzioni di cui sopra. Questo è come lo faccio di solito però:

Per il codice lato server (ad esempio un processo batch) di solito carico le entità e lavoro con proxy dinamici. Di solito nei processi batch è necessario caricare comunque i dati al momento dell'esecuzione del servizio. Provo a caricare in batch i dati invece di utilizzare il metodo find per risparmiare tempo. A seconda del processo che utilizzo il controllo della concorrenza ottimista o pessimistico (utilizzo sempre l'ottimismo, tranne per gli scenari di esecuzione parallela in cui ho bisogno di bloccare alcuni record con semplici istruzioni sql, questo è raro però). A seconda del codice e dello scenario, l'impatto può essere ridotto a quasi zero.

Per gli scenari lato client, hai alcune opzioni

  1. Usa i modelli di visualizzazione. I modelli dovrebbero avere una proprietà UpdateStatus (non modificata, inserita, aggiornata, eliminata). È responsabilità del cliente impostare il valore corretto su questa colonna in base alle azioni dell'utente (insert-update-delete). Il server può eseguire una query nel db per i valori originali oppure il client deve inviare i valori originali al server insieme alle righe modificate. Il server deve associare i valori originali e utilizzare la colonna UpdateStatus per ogni riga per decidere come gestire i nuovi valori. In questo scenario utilizzo sempre una concorrenza ottimistica. Questo farà solo le istruzioni insert - update - delete e non alcuna selezione, ma potrebbe essere necessario un codice intelligente per percorrere il grafico e aggiornare le entità (dipende dallo scenario - dall'applicazione). Un mapper può aiutare ma non gestisce la logica CRUD

  2. Usa una libreria come breeze.js che nasconde la maggior parte di questa complessità (come descritto in 1) e prova ad adattarla al tuo caso d'uso.

Spero che sia d'aiuto

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.