Come aggiornare il record utilizzando Entity Framework 6?


245

Sto cercando di aggiornare il record utilizzando EF6. Prima di tutto, trova il record, se esiste, aggiornalo. Ecco il mio codice: -

var book = new Model.Book
{
    BookNumber =  _book.BookNumber,
    BookName = _book.BookName,
    BookTitle = _book.BookTitle,
};
using (var db = new MyContextDB())
{
    var result = db.Books.SingleOrDefault(b => b.BookNumber == bookNumber);
    if (result != null)
    {
        try
        {
            db.Books.Attach(book);
            db.Entry(book).State = EntityState.Modified;
            db.SaveChanges();
        }
        catch (Exception ex)
        {
            throw;
        }
    }
}

Ogni volta che provo ad aggiornare il record usando il codice sopra, ricevo questo errore: -

{System.Data.Entity.Infrastructure.DbUpdateConcurrencyException: l'istruzione di aggiornamento, inserimento o eliminazione dello store ha interessato un numero imprevisto di righe (0). Le entità potrebbero essere state modificate o eliminate da quando le entità sono state caricate. Aggiorna entrie ObjectStateManager


7
Nota a margine: catch (Exception ex){throw;}è ridondante e puoi rimuoverlo completamente.
Sriram Sakthivel,

provare a catturare il blocco è solo per capire il motivo del fallimento. Ma non l'hai ancora capito perché questo codice non riesce?
user1327064,

2
Non sono esperto in questo argomento, non posso rispondere a questa domanda. ma senza provare a catturare è anche possibile usare break quando viene generata un'eccezione per interrompere il debugger in presenza di un'eccezione.
Sriram Sakthivel,

1
Non hai cambiato nulla. Giocare con lo stato Entità non cambierà il fatto che l'oggetto non è stato effettivamente modificato.
Jonathan Allen,

1
Bene, ho fatto lo stesso di te e non ho riscontrato l'errore. L'eccezione dice DbUpdateConcurrencyException. Come hai gestito la concorrenza? Hai usato un timestamp, hai clonato e poi unito nuovamente gli oggetti o hai usato entità con auto-tracking? (3 approcci più utilizzati). Se non hai gestito la concorrenza, immagino che sia questo il problema.
El Mac,

Risposte:


344

Stai tentando di aggiornare il record (che per me significa "modificare un valore su un record esistente e salvarlo di nuovo"). Quindi è necessario recuperare l'oggetto, apportare una modifica e salvarlo.

using (var db = new MyContextDB())
{
    var result = db.Books.SingleOrDefault(b => b.BookNumber == bookNumber);
    if (result != null)
    {
        result.SomeValue = "Some new value";
        db.SaveChanges();
    }
}

16
L'assegnazione del valore non aggiorna il database, la chiamata db.SaveChanges()con oggetti modificati nel contesto aggiorna il database.
Craig W.

6
Tuttavia mi affascina ... quindi il risultato var, in realtà viene collegato al dbcontext ... quindi questo significa che qualsiasi variabile che viene istanziata da qualsiasi membro dbcontext avrà effettivamente quella associazione al database in modo che qualsiasi modifica venga applicata a quella variabile , viene anche applicato o persistito?
WantIt

6
Poiché il contesto ha generato l'oggetto, il contesto può tenere traccia dell'oggetto, comprese le modifiche all'oggetto. Quando si chiama, SaveChangesil contesto valuta tutti gli oggetti che sta monitorando per determinare se vengono aggiunti, modificati o eliminati e invia l'SQL appropriato al database connesso.
Craig W.

3
iam affrontando lo stesso problema - usando EF6, cercando di aggiornare un'entità. Attach + EntityState.Modified non funzionante. L'unica cosa che funziona è: è necessario recuperare l'oggetto, apportare le modifiche desiderate e salvarlo tramite db.SaveChanges ();
Gurpreet Singh,

7
NON è necessario recuperare prima l'oggetto per aggiornarlo. Ho avuto lo stesso problema fino a quando ho capito che stavo cercando di cambiare uno dei valori della chiave primaria (chiave composita). Finché si fornisce una chiave primaria corretta, è possibile impostare EntityState su Modified e SaveChanges () funzionerà, a condizione che non si rompano altri vincoli di integrità definiti nella tabella.
Adrianz,

165

Ho esaminato il codice sorgente di Entity Framework e ho trovato un modo per aggiornare effettivamente un'entità se conosci la proprietà Key:

public void Update<T>(T item) where T: Entity
{
    // assume Entity base class have an Id property for all items
    var entity = _collection.Find(item.Id);
    if (entity == null)
    {
        return;
    }

    _context.Entry(entity).CurrentValues.SetValues(item);
}

Altrimenti, controlla l' implementazione di AddOrUpdate per idee.

Spero che questo aiuto!


12
Bello! Non è necessario enumerare tutte le proprietà. Presumo SaveChanges()che sia richiesta una chiamata dopo aver impostato i valori.
Jan Zahradník,

3
Sì, le modifiche verranno mantenute su SaveChanges ()
Miguel,

1
Ottima risposta, con IntelliSense non era troppo chiaro che fare qualcosa del genere NON avrebbe funzionato: _context.MyObj = newObj; quindi SaveChanges () o .... _context.MyObj.Update (newObj) quindi SaveChanges (); La tua soluzione aggiorna l'intero oggetto senza dover scorrere tutte le proprietà.
Adam,

7
Questo mi lamenta che sto cercando di modificare il campo ID
Vasily Hall il

3
@VasilyHall - questo si verifica se i campi ID (o qualunque cosa tu abbia definito la chiave primaria come) sono diversi tra i modelli (incluso null / 0 in uno dei modelli). Assicurati che gli ID corrispondano tra i due modelli e si aggiornerà bene.
Gavin Coates,

51

Puoi usare il AddOrUpdatemetodo:

db.Books.AddOrUpdate(book); //requires using System.Data.Entity.Migrations;
db.SaveChanges();

1
Migliore soluzione IMO
Norgul,

112
.AddOrUpdate()viene utilizzato durante la migrazione del database, è altamente sconsigliato utilizzare questo metodo al di fuori delle migrazioni, quindi perché si trova nello Entity.Migrationsspazio dei nomi.
Adam Vincent,

1
Come diceva @AdamVincent, il AddOrUpdate()metodo è destinato alle migrazioni e non è adatto alla situazione in cui è necessario solo aggiornare la riga esistente. Nel caso in cui non si disponga di un libro con riferimento di ricerca (ad es. ID), verrà creata una nuova riga e potrebbe essere un problema nei casi seguenti (ad esempio, si dispone di un'API che deve restituire la risposta 404-NotFound se si prova a chiamare il metodo PUT per riga inesistente).
Marko,

4
Non usarlo a meno che tu non sappia cosa stai facendo !!!!!!!!!!!!!!!! leggi: michaelgmccarthy.com/2016/08/24/…
Yusha,

4
Sono tornato di nuovo oggi, posso solo avvertirti che questa non è una buona soluzione per il caso d'uso desiderato
Yusha,

23

Quindi hai un'entità che viene aggiornata e vuoi aggiornarla nel database con la minima quantità di codice ...

La concorrenza è sempre difficile, ma presumo che tu voglia solo che i tuoi aggiornamenti vincano. Ecco come l'ho fatto per il mio stesso caso e modificato i nomi per imitare le tue lezioni. In altre parole, basta cambiare attachin add, e funziona per me:

public static void SaveBook(Model.Book myBook)
{
    using (var ctx = new BookDBContext())
    {
        ctx.Books.Add(myBook);
        ctx.Entry(myBook).State = System.Data.Entity.EntityState.Modified;
        ctx.SaveChanges();
    }
}

10

È necessario utilizzare il metodo Entry () nel caso in cui si desideri aggiornare tutti i campi nel proprio oggetto. Inoltre, tieni presente che non puoi modificare l'ID campo (chiave), quindi prima imposta l'Id sullo stesso che modifichi.

using(var context = new ...())
{
    var EditedObj = context
        .Obj
        .Where(x => x. ....)
        .First();

    NewObj.Id = EditedObj.Id; //This is important when we first create an object (NewObj), in which the default Id = 0. We can not change an existing key.

    context.Entry(EditedObj).CurrentValues.SetValues(NewObj);

    context.SaveChanges();
}

2
Dovresti almeno provare a rispondere alla domanda, non solo pubblicare il codice
StaceyGirl

Fornisci una spiegazione alla domanda invece di lasciare semplicemente uno snippet di codice per aiutare la domanda a rispondere meglio.
feanor07,

9

Questo codice è il risultato di un test per aggiornare solo un set di colonne senza effettuare una query per restituire prima il record. Utilizza prima il codice Entity Framework 7.

// This function receives an object type that can be a view model or an anonymous 
// object with the properties you want to change. 
// This is part of a repository for a Contacts object.

public int Update(object entity)
{
    var entityProperties =  entity.GetType().GetProperties();   
    Contacts con = ToType(entity, typeof(Contacts)) as Contacts;

    if (con != null)
    {
        _context.Entry(con).State = EntityState.Modified;
        _context.Contacts.Attach(con);

        foreach (var ep in entityProperties)
        {
            // If the property is named Id, don't add it in the update. 
            // It can be refactored to look in the annotations for a key 
            // or any part named Id.

            if(ep.Name != "Id")
                _context.Entry(con).Property(ep.Name).IsModified = true;
        }
    }

    return _context.SaveChanges();
}

public static object ToType<T>(object obj, T type)
{
    // Create an instance of T type object
    object tmp = Activator.CreateInstance(Type.GetType(type.ToString()));

    // Loop through the properties of the object you want to convert
    foreach (PropertyInfo pi in obj.GetType().GetProperties())
    {
        try
        {
            // Get the value of the property and try to assign it to the property of T type object
            tmp.GetType().GetProperty(pi.Name).SetValue(tmp, pi.GetValue(obj, null), null);
        }
        catch (Exception ex)
        {
            // Logging.Log.Error(ex);
        }
    }
    // Return the T type object:         
    return tmp;
}

Ecco il codice completo:

public interface IContactRepository
{
    IEnumerable<Contacts> GetAllContats();
    IEnumerable<Contacts> GetAllContactsWithAddress();
    int Update(object c);
}

public class ContactRepository : IContactRepository
{
    private ContactContext _context;

    public ContactRepository(ContactContext context)
    {
        _context = context;
    }

    public IEnumerable<Contacts> GetAllContats()
    {
        return _context.Contacts.OrderBy(c => c.FirstName).ToList();
    }

    public IEnumerable<Contacts> GetAllContactsWithAddress()
    {
        return _context.Contacts
            .Include(c => c.Address)
            .OrderBy(c => c.FirstName).ToList();
    }   

    //TODO Change properties to lambda expression
    public int Update(object entity)
    {
        var entityProperties = entity.GetType().GetProperties();

        Contacts con = ToType(entity, typeof(Contacts)) as Contacts;

        if (con != null)
        {
            _context.Entry(con).State = EntityState.Modified;
            _context.Contacts.Attach(con);

            foreach (var ep in entityProperties)
            {
                if(ep.Name != "Id")
                    _context.Entry(con).Property(ep.Name).IsModified = true;
            }
        }

        return _context.SaveChanges();
    }

    public static object ToType<T>(object obj, T type)
    {
        // Create an instance of T type object
        object tmp = Activator.CreateInstance(Type.GetType(type.ToString()));

        // Loop through the properties of the object you want to convert
        foreach (PropertyInfo pi in obj.GetType().GetProperties())
        {
            try
            {
                // Get the value of the property and try to assign it to the property of T type object
                tmp.GetType().GetProperty(pi.Name).SetValue(tmp, pi.GetValue(obj, null), null);
            }
            catch (Exception ex)
            {
                // Logging.Log.Error(ex);
            }
        }
        // Return the T type object
        return tmp;
    }
}    

public class Contacts
{
    public int Id { get; set; }
    public string FirstName { get; set; }
    public string LastName { get; set; }
    public string Email { get; set; }
    public string Company { get; set; }
    public string Title { get; set; }
    public Addresses Address { get; set; }    
}

public class Addresses
{
    [Key]
    public int Id { get; set; }
    public string AddressType { get; set; }
    public string StreetAddress { get; set; }
    public string City { get; set; }
    public State State { get; set; }
    public string PostalCode { get; set; }  
}

public class ContactContext : DbContext
{
    public DbSet<Addresses> Address { get; set; } 
    public DbSet<Contacts> Contacts { get; set; } 
    public DbSet<State> States { get; set; }

    protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
    {
        var connString = "Server=YourServer;Database=ContactsDb;Trusted_Connection=True;MultipleActiveResultSets=true;";
        optionsBuilder.UseSqlServer(connString);
        base.OnConfiguring(optionsBuilder);
    }
}

7

Per core .net

context.Customer.Add(customer);
context.Entry(customer).State = Microsoft.EntityFrameworkCore.EntityState.Modified;
context.SaveChanges();

5

Ecco la soluzione migliore per questo problema: in Visualizza aggiungi tutti gli ID (chiavi). Considera di avere più tabelle denominate (Primo, Secondo e Terzo)

@Html.HiddenFor(model=>model.FirstID)
@Html.HiddenFor(model=>model.SecondID)
@Html.HiddenFor(model=>model.Second.SecondID)
@Html.HiddenFor(model=>model.Second.ThirdID)
@Html.HiddenFor(model=>model.Second.Third.ThirdID)

Nel codice C #,

[HttpPost]
public ActionResult Edit(First first)
{
  if (ModelState.Isvalid)
  {
    if (first.FirstID > 0)
    {
      datacontext.Entry(first).State = EntityState.Modified;
      datacontext.Entry(first.Second).State = EntityState.Modified;
      datacontext.Entry(first.Second.Third).State = EntityState.Modified;
    }
    else
    {
      datacontext.First.Add(first);
    }
    datacontext.SaveChanges();
    Return RedirectToAction("Index");
  }

 return View(first);
}

5

Attaching un'entità imposterà il suo stato di tracciamento su Unchanged. Per aggiornare un'entità esistente, è sufficiente impostare lo stato di tracciamento su Modified. Secondo i documenti EF6 :

Se si dispone di un'entità che si conosce già esiste nel database ma a cui potrebbero essere state apportate modifiche, è possibile indicare al contesto di collegare l'entità e impostarne lo stato su Modificato. Per esempio:

var existingBlog = new Blog { BlogId = 1, Name = "ADO.NET Blog" };

using (var context = new BloggingContext())
{
    context.Entry(existingBlog).State = EntityState.Modified;

    // Do some more work...  

    context.SaveChanges();
}

4
using(var myDb = new MyDbEntities())
{

    user user = new user();
    user.username = "me";
    user.email = "me@me.com";

    myDb.Users.Add(user);
    myDb.users.Attach(user);
    myDb.Entry(user).State = EntityState.Modified;//this is for modiying/update existing entry
    myDb.SaveChanges();
}

4

Ho trovato un modo che funziona perfettamente.

 var Update = context.UpdateTables.Find(id);
        Update.Title = title;

        // Mark as Changed
        context.Entry(Update).State = System.Data.Entity.EntityState.Modified;
        context.SaveChanges();


1

Ecco il mio metodo di aggiornamento entità post-RIA (per il periodo di tempo Ef6):

public static void UpdateSegment(ISegment data)
{
    if (data == null) throw new ArgumentNullException("The expected Segment data is not here.");

    var context = GetContext();

    var originalData = context.Segments.SingleOrDefault(i => i.SegmentId == data.SegmentId);
    if (originalData == null) throw new NullReferenceException("The expected original Segment data is not here.");

    FrameworkTypeUtility.SetProperties(data, originalData);

    context.SaveChanges();
}

Nota che FrameworkTypeUtility.SetProperties()è una minuscola funzione di utilità che ho scritto molto prima di AutoMapper su NuGet:

public static void SetProperties<TIn, TOut>(TIn input, TOut output, ICollection<string> includedProperties)
    where TIn : class
    where TOut : class
{
    if ((input == null) || (output == null)) return;
    Type inType = input.GetType();
    Type outType = output.GetType();
    foreach (PropertyInfo info in inType.GetProperties())
    {
        PropertyInfo outfo = ((info != null) && info.CanRead)
            ? outType.GetProperty(info.Name, info.PropertyType)
            : null;
        if (outfo != null && outfo.CanWrite
            && (outfo.PropertyType.Equals(info.PropertyType)))
        {
            if ((includedProperties != null) && includedProperties.Contains(info.Name))
                outfo.SetValue(output, info.GetValue(input, null), null);
            else if (includedProperties == null)
                outfo.SetValue(output, info.GetValue(input, null), null);
        }
    }
}

Nota: funziona solo se le proprietà sono esattamente le stesse nel modello dell'oggetto ViewModel che viene salvato in esso.
vapcguy,

1

Come ha detto Renat, rimuovere: db.Books.Attach(book);

Inoltre, modifica la query dei risultati per utilizzare "AsNoTracking", poiché questa query sta eliminando lo stato del modello del framework dell'entità. Pensa che "risultato" sia il libro da tracciare ora e tu non lo vuoi.

var result = db.Books.AsNoTracking().SingleOrDefault(b => b.BookNumber == bookNumber);

1

Provalo....

UpdateModel (libro);

var book = new Model.Book
{
    BookNumber =  _book.BookNumber,
    BookName = _book.BookName,
    BookTitle = _book.BookTitle,
};
using (var db = new MyContextDB())
{
    var result = db.Books.SingleOrDefault(b => b.BookNumber == bookNumber);
    if (result != null)
    {
        try
        {
            UpdateModel(book);
            db.Books.Attach(book);
            db.Entry(book).State = EntityState.Modified;
            db.SaveChanges();
        }
        catch (Exception ex)
        {
            throw;
        }
    }
}

1

So che ha già ricevuto una buona risposta già poche volte, ma mi piace di seguito. Spero che possa aiutare qualcuno.

//attach object (search for row)
TableName tn = _context.TableNames.Attach(new TableName { PK_COLUMN = YOUR_VALUE});
// set new value
tn.COLUMN_NAME_TO_UPDATE = NEW_COLUMN_VALUE;
// set column as modified
_context.Entry<TableName>(tn).Property(tnp => tnp.COLUMN_NAME_TO_UPDATE).IsModified = true;
// save change
_context.SaveChanges();

1

Questo se per Entity Framework 6.2.0.

Se si dispone di uno specifico DbSete un elemento che deve essere aggiornato o creato:

var name = getNameFromService();

var current = _dbContext.Names.Find(name.BusinessSystemId, name.NameNo);
if (current == null)
{
    _dbContext.Names.Add(name);
}
else
{
    _dbContext.Entry(current).CurrentValues.SetValues(name);
}
_dbContext.SaveChanges();

Tuttavia, questo può essere utilizzato anche per un generico DbSetcon una chiave primaria singola o una chiave primaria composita.

var allNames = NameApiService.GetAllNames();
GenericAddOrUpdate(allNames, "BusinessSystemId", "NameNo");

public virtual void GenericAddOrUpdate<T>(IEnumerable<T> values, params string[] keyValues) where T : class
{
    foreach (var value in values)
    {
        try
        {
            var keyList = new List<object>();

            //Get key values from T entity based on keyValues property
            foreach (var keyValue in keyValues)
            {
                var propertyInfo = value.GetType().GetProperty(keyValue);
                var propertyValue = propertyInfo.GetValue(value);
                keyList.Add(propertyValue);
            }

            GenericAddOrUpdateDbSet(keyList, value);
            //Only use this when debugging to catch save exceptions
            //_dbContext.SaveChanges();
        }
        catch
        {
            throw;
        }
    }
    _dbContext.SaveChanges();
}

public virtual void GenericAddOrUpdateDbSet<T>(List<object> keyList, T value) where T : class
{
    //Get a DbSet of T type
    var someDbSet = Set(typeof(T));

    //Check if any value exists with the key values
    var current = someDbSet.Find(keyList.ToArray());
    if (current == null)
    {
        someDbSet.Add(value);
    }
    else
    {
        Entry(current).CurrentValues.SetValues(value);
    }
}
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.