Как обновить запись с помощью Entity Framework 6?

245

Я пытаюсь обновить запись с помощью EF6. Сначала найдя запись, если она существует, обновите ее. Вот мой код: -

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

Каждый раз, когда я пытаюсь обновить запись, используя приведенный выше код, я получаю эту ошибку: -

{System.Data.Entity.Infrastructure.DbUpdateConcurrencyException: оператор обновления, вставки или удаления магазина затронул непредвиденное количество строк (0). Объекты могут быть изменены или удалены с момента загрузки объектов. Обновить объект ObjectStateManager

user1327064
источник
7
Примечание: catch (Exception ex){throw;}избыточно, и вы можете полностью удалить его.
Шрирам Шактивель
попробуйте перехватить блок, просто чтобы выяснить причину сбоя. Но все же не понял, почему этот код не работает?
user1327064
2
Я не эксперт в этой теме, я не могу ответить на этот вопрос. но без try catch также вы можете использовать функцию break, когда выбрасывается исключение, для прерывания отладчика, когда есть исключение.
Шрирам Шактивель
1
Вы ничего не изменили. Игра с состоянием Entity не изменит тот факт, что объект на самом деле не был изменен.
Джонатан Аллен
1
Ну, я сделал то же самое, что и ты, и не получил ошибку. Исключение говорит DbUpdateConcurrencyException. Как вы справились с параллелизмом? Использовали ли вы временную метку, клонировали, а затем снова объединили объекты или использовали объекты самоконтроля? (3 наиболее часто используемых подхода). Если вы не справились с параллелизмом, я думаю, в этом проблема.
El Mac

Ответы:

345

Вы пытаетесь обновить запись (что для меня означает «изменить значение в существующей записи и сохранить его обратно»). Таким образом, вам нужно извлечь объект, внести изменения и сохранить его.

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
Присвоение значения не обновляет базу данных, вызов db.SaveChanges()с измененными объектами в контексте обновляет базу данных.
Крейг В.
6
Тем не менее, это очаровывает меня ... так что var result на самом деле становится подключенным к dbcontext ... так что это означает, что любая переменная, которая создается экземплярами любых членов dbcontext, будет фактически иметь эту ассоциацию с базой данных, так что любые изменения будут применены к этой переменной это также применяется или сохраняется?
Хочу
6
Поскольку контекст породил объект, контекст может отслеживать объект, включая изменения объекта. Когда вы вызываете SaveChangesконтекст, оценивает все объекты, которые он отслеживает, чтобы определить, были ли они добавлены, изменены или удалены, и выдает соответствующий SQL для подключенной базы данных.
Крейг В.
3
Я сталкиваюсь с той же проблемой - используя EF6, пытаясь обновить сущность. Attach + EntityState.Modified не работает. Единственное, что работает, - вам нужно получить объект, внести нужные изменения и сохранить его с помощью db.SaveChanges ();
Gurpreet Singh
7
Вам НЕ нужно сначала извлекать объект, чтобы обновить его. У меня была такая же проблема, пока я не понял, что пытаюсь изменить одно из значений первичного ключа (составной ключ). Пока вы предоставляете правильный первичный ключ, вы можете установить для EntityState значение Modified и SaveChanges () будет работать при условии, что вы не нарушите некоторые другие ограничения целостности, определенные в таблице.
Адрианц
166

Я просматривал исходный код Entity Framework и нашел способ действительно обновить сущность, если вы знаете свойство 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);
}

В противном случае проверьте реализацию AddOrUpdate для идей.

Надеюсь, это поможет!

Miguel
источник
12
Ницца! Не нужно перечислять все свойства. Я предполагаю, что SaveChanges()вызов требуется после установки значений.
Ян Zahradník
3
Да, изменения будут сохранены в SaveChanges ()
Miguel
1
Отличный ответ, с IntelliSense было не слишком ясно, что что-то подобное НЕ будет работать: _context.MyObj = newObj; затем SaveChanges () или .... _context.MyObj.Update (newObj), затем SaveChanges (); Ваше решение обновляет весь объект без необходимости перебирать все свойства.
Адам
7
Это жалуется мне на то, что я пытаюсь отредактировать поле идентификатора
Василий Холл
3
@VasilyHall - это происходит, если поля идентификатора (или как вы определили первичный ключ как) отличаются между моделями (включая ноль / 0 в одной из моделей). Удостоверьтесь, что идентификаторы совпадают между двумя моделями, и он будет обновляться очень хорошо.
Гэвин Коутс
51

Вы можете использовать AddOrUpdateметод:

db.Books.AddOrUpdate(book); //requires using System.Data.Entity.Migrations;
db.SaveChanges();
nicedev80
источник
1
ИМО лучшее решение
Норгул
113
.AddOrUpdate()используется во время миграции базы данных, поэтому не рекомендуется использовать этот метод вне миграций, поэтому он находится в Entity.Migrationsпространстве имен.
Адам Винсент
1
Как сказал @AdamVincent, AddOrUpdate()метод предназначен для миграций и не подходит для ситуаций, когда вам нужно только обновить существующую строку. В случае, если у вас нет книги с поисковой ссылкой (т.е. идентификатором), она создаст новую строку, и это может быть проблемой в некоторых случаях (например, у вас есть API, который должен вернуть вам ответ 404-NotFound, если вы попробуйте вызвать метод PUT для несуществующей строки).
Марко
4
Не используйте это, если вы не знаете, что делаете !!!!!!!!!!!!!!!! Читайте: michaelgmccarthy.com/2016/08/24/…
Юша
4
Сегодня я снова вернулся к этому, могу ли я просто предупредить вас, что это не очень хорошее решение для желаемого
Юша
23

Итак, у вас есть объект, который обновляется, и вы хотите обновить его в базе данных с наименьшим количеством кода ...

Параллелизм всегда сложен, но я предполагаю, что вы просто хотите, чтобы ваши обновления выигрывали. Вот как я сделал это для моего же случая и изменил имена, чтобы имитировать ваши классы. Другими словами, просто измените attachна add, и это работает для меня:

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

Вам следует использовать метод Entry (), если вы хотите обновить все поля в вашем объекте. Также имейте в виду, что вы не можете изменить идентификатор поля (ключ), поэтому сначала установите Id на то же, что и при редактировании.

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
Вы должны хотя бы попытаться ответить на вопрос, а не просто опубликовать код
StaceyGirl
Пожалуйста, дайте некоторое объяснение этому вопросу вместо того, чтобы просто оставить фрагмент кода, чтобы помочь задающему вопрос лучше.
feanor07
9

Этот код является результатом теста для обновления только набора столбцов без выполнения запроса на возврат записи в первую очередь. Сначала он использует код 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;
}

Вот полный код:

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
источник
7

Для ядра .net

context.Customer.Add(customer);
context.Entry(customer).State = Microsoft.EntityFrameworkCore.EntityState.Modified;
context.SaveChanges();
Крис Розете
источник
5

Вот лучшее решение для этой проблемы: Во View добавьте все идентификаторы (ключи). Подумайте о наличии нескольких таблиц с именами (первая, вторая и третья)

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

В коде 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

AttachПри создании объекта его состояние отслеживания устанавливается равным Unchanged. Чтобы обновить существующую сущность, все, что вам нужно сделать, это установить состояние отслеживания в Modified. Согласно документам EF6 :

Если у вас есть объект, который, как вы знаете, уже существует в базе данных, но в который, возможно, были внесены изменения, вы можете указать контексту присоединить объект и установить для него состояние Modified. Например:

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
источник
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

Я нашел способ, который работает просто отлично.

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

        // Mark as Changed
        context.Entry(Update).State = System.Data.Entity.EntityState.Modified;
        context.SaveChanges();
Фархан
источник
3

Вы должны удалить db.Books.Attach(book);

Ренат Сейфетдинов
источник
1

Вот мой метод обновления сущностей после RIA (для периода времени 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();
}

Обратите внимание, что FrameworkTypeUtility.SetProperties()это крошечная служебная функция, которую я написал задолго до AutoMapper на 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
источник
Примечание. Работает только в том случае, если ваши свойства в модели точно такие же, как и в объекте ViewModel, который в нем сохраняется.
vapcguy
1

Как сказал Ренат, удалите: db.Books.Attach(book);

Кроме того, измените свой результат запроса, чтобы использовать «AsNoTracking», потому что этот запрос отбрасывает состояние модели структуры сущности. Он думает, что «результат» - это книга, которую нужно отслеживать, а вы этого не хотите.

var result = db.Books.AsNoTracking().SingleOrDefault(b => b.BookNumber == bookNumber);
Nez
источник
1

Попытайся....

UpdateModel (книга);

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

Я знаю, что на это уже отвечали несколько раз, но мне нравится ниже способ сделать это. Надеюсь, это кому-нибудь поможет.

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

Это если для Entity Framework 6.2.0.

Если у вас есть конкретный DbSetпредмет и предмет, который необходимо обновить или создать:

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

Однако это также может быть использовано для универсального DbSetс одним первичным ключом или составным первичным ключом.

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
источник