Wie aktualisiere ich einen Datensatz mit Entity Framework 6?

245

Ich versuche, den Datensatz mit EF6 zu aktualisieren. Suchen Sie zuerst den Datensatz, falls vorhanden, und aktualisieren Sie ihn. Hier ist mein Code: -

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

Jedes Mal, wenn ich versuche, den Datensatz mit dem obigen Code zu aktualisieren, wird folgende Fehlermeldung angezeigt: -

{System.Data.Entity.Infrastructure.DbUpdateConcurrencyException: Die Anweisung zum Speichern, Einfügen oder Löschen des Speichers hat eine unerwartete Anzahl von Zeilen (0) beeinflusst. Entitäten wurden möglicherweise geändert oder gelöscht, seit Entitäten geladen wurden. Aktualisieren Sie den ObjectStateManager-Eintrag

user1327064
quelle
7
Randnotiz: catch (Exception ex){throw;}ist redundant und Sie können es vollständig entfernen.
Sriram Sakthivel
try catch block dient nur dazu, den Grund für den Fehler herauszufinden. Aber immer noch nicht verstanden, warum dieser Code fehlschlägt?
user1327064
2
Ich bin kein Experte in diesem Thema, ich kann diese Frage nicht beantworten. Aber ohne try catch können Sie auch die Funktion break, wenn eine Ausnahme ausgelöst wird, verwenden , um den Debugger zu brechen, wenn eine Ausnahme vorliegt.
Sriram Sakthivel
1
Du hast nichts geändert. Das Spielen mit dem Entitätsstatus ändert nichts an der Tatsache, dass das Objekt nicht tatsächlich geändert wurde.
Jonathan Allen
1
Nun, ich habe das Gleiche getan wie Sie und den Fehler nicht erhalten. Die Ausnahme lautet DbUpdateConcurrencyException. Wie sind Sie mit der Parallelität umgegangen? Haben Sie einen Zeitstempel verwendet, die Objekte geklont und dann erneut zusammengeführt oder haben Sie Self-Tracking-Entitäten verwendet? (3 am häufigsten verwendete Ansätze). Wenn Sie die Parallelität nicht bewältigt haben, ist das wohl das Problem.
El Mac

Antworten:

344

Sie versuchen, den Datensatz zu aktualisieren (was für mich bedeutet, "einen Wert für einen vorhandenen Datensatz zu ändern und ihn wieder zu speichern"). Sie müssen also das Objekt abrufen, eine Änderung vornehmen und es speichern.

using (var db = new MyContextDB())
{
    var result = db.Books.SingleOrDefault(b => b.BookNumber == bookNumber);
    if (result != null)
    {
        result.SomeValue = "Some new value";
        db.SaveChanges();
    }
}
Craig W.
quelle
16
Durch das Zuweisen des Werts wird die Datenbank nicht aktualisiert. Durch Aufrufen db.SaveChanges()mit geänderten Objekten im Kontext wird die Datenbank aktualisiert.
Craig W.
6
Trotzdem fasziniert es mich ... so dass var result tatsächlich mit dem dbcontext verbunden wird ... das bedeutet, dass jede Variable, die von einem dbcontext-Mitglied instanziiert wird, tatsächlich diese Zuordnung zur Datenbank hat, so dass alle Änderungen auf diese Variable angewendet werden wird es auch angewendet oder bleibt bestehen?
WantIt
6
Da der Kontext das Objekt generiert hat, kann der Kontext das Objekt verfolgen, einschließlich Änderungen am Objekt. Wenn Sie SaveChangesden Kontext aufrufen, werden alle Objekte ausgewertet, die verfolgt werden, um festzustellen, ob sie hinzugefügt, geändert oder gelöscht wurden, und die entsprechende SQL an die verbundene Datenbank ausgegeben.
Craig W.
3
Ich bin mit dem gleichen Problem konfrontiert - mit EF6 versuche ich, eine Entität zu aktualisieren. Attach + EntityState.Modified funktioniert nicht. Es funktioniert nur: Sie müssen das Objekt abrufen, die gewünschten Änderungen vornehmen und es über db.SaveChanges () speichern.
Gurpreet Singh
7
Sie sollten das Objekt NICHT zuerst abrufen müssen, um es zu aktualisieren. Ich hatte das gleiche Problem, bis mir klar wurde, dass ich versuchte, einen der Primärschlüsselwerte (zusammengesetzten Schlüssel) zu ändern. Solange Sie einen korrekten Primärschlüssel angeben, können Sie den EntityState auf Modified setzen und SaveChanges () funktioniert, sofern Sie keine andere in der Tabelle definierte Integritätsbeschränkung aufheben.
Adrianz
165

Ich habe den Quellcode von Entity Framework überprüft und eine Möglichkeit gefunden, eine Entität tatsächlich zu aktualisieren, wenn Sie die Key-Eigenschaft kennen:

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

Überprüfen Sie andernfalls die AddOrUpdate- Implementierung auf Ideen.

Ich hoffe das hilft!

Miguel
quelle
12
Nett! Es müssen nicht alle Eigenschaften aufgelistet werden. Ich gehe davon aus SaveChanges(), dass nach dem Einstellen der Werte ein Anruf erforderlich ist.
Jan Zahradník
3
Ja, Änderungen werden auf SaveChanges ()
Miguel
1
Tolle Antwort, mit IntelliSense war nicht klar, dass so etwas NICHT funktionieren würde: _context.MyObj = newObj; dann SaveChanges () oder .... _context.MyObj.Update (newObj) dann SaveChanges (); Ihre Lösung aktualisiert das gesamte Objekt, ohne alle Eigenschaften durchlaufen zu müssen.
Adam
7
Dies beschwert sich bei mir, dass ich versuche, das ID-Feld zu bearbeiten
Vasily Hall
3
@VasilyHall - Dies tritt auf, wenn die ID-Felder (oder wie auch immer Sie den Primärschlüssel definiert haben) zwischen den Modellen unterschiedlich sind (einschließlich null / 0 in einem der Modelle). Stellen Sie sicher, dass die IDs zwischen den beiden Modellen übereinstimmen und die Aktualisierung einwandfrei funktioniert.
Gavin Coates
51

Sie können die AddOrUpdateMethode verwenden:

db.Books.AddOrUpdate(book); //requires using System.Data.Entity.Migrations;
db.SaveChanges();
nicedev80
quelle
1
IMO beste Lösung
Norgul
112
.AddOrUpdate()Wird während der Datenbankmigration verwendet, wird dringend davon abgeraten, diese Methode außerhalb von Migrationen zu verwenden, weshalb sie sich im Entity.MigrationsNamespace befindet.
Adam Vincent
1
Wie @AdamVincent sagte, AddOrUpdate()ist die Methode für Migrationen gedacht und nicht für Situationen geeignet, in denen Sie nur vorhandene Zeilen aktualisieren müssen. Wenn Sie kein Buch mit Suchreferenz (dh ID) haben, wird eine neue Zeile erstellt, und dies kann in einigen Fällen ein Problem sein (z. B. haben Sie eine API, die Ihnen die Antwort 404-NotFound zurückgeben muss, wenn Sie dies tun Versuchen Sie, die PUT-Methode für nicht vorhandene Zeilen aufzurufen.
Marko
4
Verwenden Sie dies nur, wenn Sie wissen, was Sie tun !!!!!!!!!!!!!!!! lesen: michaelgmccarthy.com/2016/08/24/...
Yusha
4
Ich bin heute wieder darauf zurückgekommen, kann ich Sie alle nur warnen, dass dies keine gute Lösung für den gewünschten Anwendungsfall ist
Yusha
23

Sie haben also eine Entität, die aktualisiert wird, und Sie möchten sie in der Datenbank mit der geringsten Menge an Code aktualisieren ...

Parallelität ist immer schwierig, aber ich gehe davon aus, dass Sie nur möchten, dass Ihre Updates gewinnen. Hier ist, wie ich es für denselben Fall gemacht und die Namen geändert habe, um Ihre Klassen nachzuahmen. Mit anderen Worten, wechseln Sie einfach attachzu addund es funktioniert für mich:

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();
    }
}
Duray Akar
quelle
10

Sie sollten die Entry () -Methode verwenden, wenn Sie alle Felder in Ihrem Objekt aktualisieren möchten. Denken Sie auch daran, dass Sie die Feld-ID (Schlüssel) nicht ändern können. Stellen Sie daher zuerst die ID so ein, wie Sie sie bearbeiten.

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();
}
Jarek
quelle
2
Sie sollten zumindest versuchen, die Frage zu beantworten, und nicht nur den Code
posten
Bitte erläutern Sie die Frage, anstatt nur einen Code-Ausschnitt zu hinterlassen, um dem Fragesteller besser zu helfen.
Feanor07
9

Dieser Code ist das Ergebnis eines Tests, bei dem nur eine Reihe von Spalten aktualisiert wurde, ohne dass eine Abfrage durchgeführt wurde, um den Datensatz zuerst zurückzugeben. Es wird zuerst Entity Framework 7-Code verwendet.

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

Hier ist der vollständige Code:

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);
    }
}
Juan
quelle
7

Für .net Kern

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

Hier ist die beste Lösung für dieses Problem: Fügen Sie in der Ansicht alle IDs (Schlüssel) hinzu. Erwägen Sie, mehrere Tabellen zu benennen (Erste, Zweite und Dritte).

@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)

Im C # -Code

[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);
}
Kumar R.
quelle
5

AttachWenn eine Entität ihren Verfolgungsstatus auf setzt Unchanged. Um eine vorhandene Entität zu aktualisieren, müssen Sie lediglich den Verfolgungsstatus auf setzen Modified. Laut den EF6-Dokumenten :

Wenn Sie eine Entität haben, von der Sie wissen, dass sie bereits in der Datenbank vorhanden ist, an der jedoch möglicherweise Änderungen vorgenommen wurden, können Sie den Kontext anweisen, die Entität anzuhängen und ihren Status auf Geändert zu setzen. Beispielsweise:

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();
}
Bondolin
quelle
4
using(var myDb = new MyDbEntities())
{

    user user = new user();
    user.username = "me";
    user.email = "[email protected]";

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

Ich habe einen Weg gefunden, der gut funktioniert.

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

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

Sie sollten entfernen db.Books.Attach(book);

Renat Seyfetdinov
quelle
1

Hier ist meine Methode zur Aktualisierung der Entität nach RIA (für den Ef6-Zeitrahmen):

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

Beachten Sie, dass dies FrameworkTypeUtility.SetProperties()eine winzige Dienstprogrammfunktion ist, die ich lange vor AutoMapper auf NuGet geschrieben habe:

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);
        }
    }
}
rasx
quelle
Hinweis: Funktioniert nur, wenn Ihre Eigenschaften in Ihrem Modell genau mit denen Ihres ViewModel-Objekts übereinstimmen, das darin gespeichert wird.
Vapcguy
1

Wie Renat sagte, entfernen Sie: db.Books.Attach(book);

Ändern Sie außerdem Ihre Ergebnisabfrage in "AsNoTracking", da diese Abfrage den Modellstatus des Entity Frameworks beeinträchtigt. Es denkt, "Ergebnis" ist das Buch, das jetzt verfolgt werden muss, und das wollen Sie nicht.

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

Versuch es....

UpdateModel (Buch);

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;
        }
    }
}
Karan
quelle
1

Ich weiß, dass es schon einige Male gut beantwortet wurde, aber ich mag es unten, dies zu tun. Ich hoffe es wird jemandem helfen.

//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();
Pawel Czapski
quelle
1

Dies gilt für Entity Framework 6.2.0.

Wenn Sie ein bestimmtes DbSetElement haben, das entweder aktualisiert oder erstellt werden muss:

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

Dies kann jedoch auch für ein Generikum DbSetmit einem einzelnen Primärschlüssel oder einem zusammengesetzten Primärschlüssel verwendet werden.

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);
    }
}
Ogglas
quelle