Jak zaktualizować rekord za pomocą Entity Framework 6?

245

Próbuję zaktualizować rekord przy użyciu EF6. Najpierw znajdź rekord, jeśli istnieje, zaktualizuj go. Oto mój kod: -

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

Za każdym razem, gdy próbuję zaktualizować rekord przy użyciu powyższego kodu, pojawia się ten błąd: -

{System.Data.Entity.Infrastructure.DbUpdateConcurrencyException: Przechowywanie instrukcji aktualizacji, wstawiania lub usuwania wpłynęło na nieoczekiwaną liczbę wierszy (0). Elementy mogły zostać zmodyfikowane lub usunięte od czasu załadowania elementów. Odśwież element ObjectStateManager

użytkownik1327064
źródło
7
Uwaga dodatkowa: catch (Exception ex){throw;}jest zbędna i można ją całkowicie usunąć.
Sriram Sakthivel,
spróbuj złapać blok, aby dowiedzieć się, dlaczego nie powiodło się. Ale nadal nie rozumiem, dlaczego ten kod zawodzi?
user1327064,
2
Nie jestem ekspertem w tym temacie, nie mogę odpowiedzieć na to pytanie. ale bez próby złapania można również użyć opcji break, gdy wyjątek jest zgłaszany, aby przerwać debugger, gdy występuje wyjątek.
Sriram Sakthivel,
1
Nic nie zmieniłeś. Gra ze stanem Entity nie zmieni faktu, że obiekt nie został faktycznie zmodyfikowany.
Jonathan Allen
1
Zrobiłem to samo co ty i nie dostałem błędu. Wyjątek mówi DbUpdateConcurrencyException. Jak poradziłeś sobie ze współbieżnością? Czy użyłeś znacznika czasu, sklonowałeś, a następnie scaliłeś obiekty ponownie, czy używałeś jednostek śledzących? (3 najczęściej stosowane podejścia). Jeśli nie obsłużyłeś współbieżności, to chyba problem.
El Mac

Odpowiedzi:

345

Próbujesz zaktualizować rekord (co dla mnie oznacza „zmień wartość istniejącego rekordu i zapisz go z powrotem”). Musisz więc pobrać obiekt, wprowadzić zmiany i zapisać go.

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.
źródło
16
Przypisanie wartości nie powoduje aktualizacji bazy danych, wywoływanie db.SaveChanges()zmodyfikowanych obiektów w kontekście aktualizuje bazę danych.
Craig W.
6
Wciąż mnie fascynuje ... więc var var, faktycznie łączy się z dbcontext ... więc oznacza to, że każda zmienna utworzona przez dowolnych członków dbcontext będzie faktycznie miała powiązanie z bazą danych, tak więc wszelkie zmiany zostaną zastosowane do tej zmiennej , jest również stosowany lub utrwalony?
WantIt
6
Ponieważ kontekst wygenerował obiekt, kontekst może śledzić obiekt, w tym zmiany w obiekcie. Po wywołaniu SaveChangeskontekst ocenia wszystkie śledzone obiekty w celu ustalenia, czy są one dodane, zmienione lub usunięte i wydaje odpowiedni kod SQL do podłączonej bazy danych.
Craig W.
3
Jestem w obliczu tego samego problemu - używając EF6, próbując zaktualizować encję. Attach + EntityState.Modified nie działa. Jedyne, co działa - musisz pobrać obiekt, wprowadzić pożądane zmiany i zapisać go za pomocą db.SaveChanges ();
Gurpreet Singh
7
NIE należy najpierw pobierać obiektu, aby go zaktualizować. Miałem ten sam problem, dopóki nie zdałem sobie sprawy, że próbuję zmienić jedną z wartości klucza podstawowego (klucz złożony). Tak długo, jak podasz poprawny klucz podstawowy, możesz ustawić EntityState na Modified i SaveChanges () będzie działać, pod warunkiem, że nie złamiesz innego ograniczenia integralności zdefiniowanego w tabeli.
adrianz
166

Przeglądałem kod źródłowy Entity Framework i znalazłem sposób na faktyczną aktualizację encji, jeśli znasz właściwość 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);
}

W przeciwnym razie sprawdź implementację AddOrUpdate pod kątem pomysłów.

Mam nadzieję, że to pomoże!

Miguel
źródło
12
Miły! Nie ma potrzeby wyliczania wszystkich właściwości. Zakładam, że SaveChanges()połączenie jest wymagane po ustawieniu wartości.
Jan Zahradník
3
Tak, zmiany zostaną zachowane w SaveChanges ()
Miguel,
1
Świetna odpowiedź, z IntelliSense nie było zbyt jasne, że zrobienie czegoś takiego NIE zadziałałoby: _context.MyObj = newObj; następnie SaveChanges () lub .... _context.MyObj.Update (newObj), a następnie SaveChanges (); Twoje rozwiązanie aktualizuje cały obiekt bez konieczności przechodzenia przez wszystkie właściwości.
Adam
7
To mi narzeka, że ​​próbuję edytować pole identyfikatora
Vasily Hall
3
@VasilyHall - dzieje się tak, jeśli pola ID (lub cokolwiek zdefiniowałeś Podstawowy klucz as) różnią się między modelami (w tym null / 0 w jednym z modeli). Upewnij się, że identyfikatory są zgodne między dwoma modelami, a aktualizacja będzie w porządku.
Gavin Coates
51

Możesz użyć AddOrUpdatemetody:

db.Books.AddOrUpdate(book); //requires using System.Data.Entity.Migrations;
db.SaveChanges();
nicedev80
źródło
1
Najlepsze rozwiązanie IMO
Norgul
112
.AddOrUpdate()jest używany podczas migracji bazy danych, odradza się korzystanie z tej metody poza migracjami, dlatego znajduje się w Entity.Migrationsprzestrzeni nazw.
Adam Vincent
1
Jak powiedział @AdamVincent, AddOrUpdate()metoda jest przeznaczona do migracji i nie nadaje się do sytuacji, gdy trzeba tylko zaktualizować istniejący wiersz. W przypadku, gdy nie masz książki z odniesieniem do wyszukiwania (tj. Identyfikatorem), utworzy nowy wiersz i może być problemem w nadchodzących przypadkach (na przykład, masz interfejs API, który musi zwrócić ci odpowiedź 404 NotFound, jeśli spróbuj wywołać metodę PUT dla nieistniejącego wiersza).
Marko
4
Nie używaj tego, chyba że wiesz, co robisz !!!!!!!!!!!!!!!! przeczytaj: michaelgmccarthy.com/2016/08/24/…
Yusha
4
Wróciłem do tego ponownie dzisiaj, czy mogę was ostrzec, że nie jest to dobre rozwiązanie dla pożądanego przypadku użycia
Yusha
23

Więc masz encję, która jest aktualizowana i chcesz zaktualizować ją w bazie danych przy użyciu najmniejszej ilości kodu ...

Współbieżność jest zawsze trudna, ale zakładam, że chcesz, aby Twoje aktualizacje wygrywały. Oto, jak to zrobiłem w tym samym przypadku i zmodyfikowałem nazwy, aby naśladować twoje zajęcia. Innymi słowy, po prostu zmień attachna addi działa dla mnie:

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
źródło
10

Powinieneś użyć metody Entry () na wypadek, gdybyś chciał zaktualizować wszystkie pola w swoim obiekcie. Pamiętaj również, że nie możesz zmienić identyfikatora pola (klucza), dlatego najpierw ustaw identyfikator na taki sam, jak podczas edycji.

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
źródło
2
Powinieneś przynajmniej spróbować odpowiedzieć na pytanie, a nie tylko opublikować kod
StaceyGirl
Proszę wyjaśnić pytanie, zamiast pozostawiać fragment kodu, aby lepiej zadać pytanie.
feanor07
9

Ten kod jest wynikiem testu polegającego na aktualizacji tylko zestawu kolumn bez zapytania o zwrócenie rekordu w pierwszej kolejności. Najpierw używa kodu 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;
}

Oto pełny kod:

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
źródło
7

Dla rdzenia .net

context.Customer.Add(customer);
context.Entry(customer).State = Microsoft.EntityFrameworkCore.EntityState.Modified;
context.SaveChanges();
Chris Rosete
źródło
5

Oto najlepsze rozwiązanie tego problemu: W widoku dodaj wszystkie identyfikatory (klucze). Rozważ nazwanie wielu tabel (pierwsza, druga i trzecia)

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

W kodzie 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);
}
Kumar R.
źródło
5

Attachwprowadzenie jednostki ustawi stan śledzenia na Unchanged. Aby zaktualizować istniejącą jednostkę, wystarczy ustawić stan śledzenia na Modified. Według dokumentów EF6 :

Jeśli masz encję, o której wiesz, że już istnieje w bazie danych, ale do której mogły zostać wprowadzone zmiany, możesz powiedzieć kontekstowi, aby dołączyć encję i ustawić jej stan na Zmodyfikowany. Na przykład:

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
źródło
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
źródło
4

Znalazłem sposób, który działa dobrze.

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

        // Mark as Changed
        context.Entry(Update).State = System.Data.Entity.EntityState.Modified;
        context.SaveChanges();
Farhan
źródło
3

Powinieneś usunąć db.Books.Attach(book);

Renat Seyfetdinov
źródło
1

Oto moja metoda aktualizacji encji po RIA (dla przedziału czasu 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();
}

Zauważ, że FrameworkTypeUtility.SetProperties()to mała funkcja narzędzia, którą napisałem na długo przed AutoMapper na 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);
        }
    }
}
rasx
źródło
Uwaga: Działa tylko wtedy, gdy właściwości są dokładnie takie same w modelu, jak zapisywany w nim obiekt ViewModel.
vapcguy
1

Jak powiedział Renat, usuń: db.Books.Attach(book);

Zmień również zapytanie wynikowe, aby używało „AsNoTracking”, ponieważ zapytanie to odrzuca stan modelu struktury encji. Uważa, że ​​„wynik” jest teraz książką do śledzenia, a ty tego nie chcesz.

var result = db.Books.AsNoTracking().SingleOrDefault(b => b.BookNumber == bookNumber);
Nez
źródło
1

Spróbuj....

UpdateModel (książka);

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
źródło
1

Wiem, że już kilkakrotnie otrzymałem dobrą odpowiedź, ale podoba mi się poniższy sposób robienia tego. Mam nadzieję, że to komuś pomoże.

//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();
Paweł Czapski
źródło
1

Ma to miejsce w przypadku Entity Framework 6.2.0.

Jeśli masz konkretny DbSetelement, który należy zaktualizować lub utworzyć:

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

Można to jednak również wykorzystać w przypadku DbSetklucza ogólnego z pojedynczym kluczem podstawowym lub złożonym kluczem podstawowym.

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
źródło