Як оновити запис за допомогою 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


7
Бічна примітка: catch (Exception ex){throw;}зайва, і ви можете повністю її видалити.
Шрірам Сактівель

спробувати блок лову - це лише з'ясувати причину невдачі. Але все ж не зрозуміли, чому цей код виходить з ладу?
user1327064

2
Я не знаю цю тему, я не можу відповісти на це питання. але без спроби лову також можна використовувати функцію break, коли викинуто виняток, щоб зламати налагоджувач, коли є виняток.
Sriram Sakthivel

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 результат, насправді стає підключеним до dbcontext ... тому це означає, що будь-яка змінна, яка інстанціюється будь-якими членами dbcontext, насправді матиме цей асоційований з базою даних, так що будь-які зміни застосовуються до цієї змінної , воно також застосовується чи зберігається?
Хочу,

6
Оскільки контекст генерував об'єкт, контекст може відслідковувати об'єкт, включаючи зміни в об'єкті. Під час виклику SaveChangesконтекст оцінює всі об'єкти, які він відстежує, щоб визначити, чи вони додані, змінені чи видалені та видає відповідний SQL підключеній базі даних.
Крейг В.

3
Я зіткнувся з тією ж проблемою - використовуючи EF6, намагаючись оновити об'єкт. Attach + EntityState. Модифікований не працює. Єдине, що працює - вам потрібно отримати об’єкт, внести потрібні зміни та зберегти його через 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 на ідеї.

Сподіваюся, що це допоможе!


12
Приємно! Не потрібно перераховувати всі властивості. Я припускаю, що SaveChanges()дзвінок потрібен після встановлення значень.
Jan Zahradník

3
Так, зміни зберігатимуться на SaveChanges ()
Мігель

1
Чудова відповідь, з IntelliSense було не надто зрозуміло, що робити щось подібне НЕ буде працювати: _context.MyObj = newObj; потім SaveChanges () або .... _context.MyObj.Update (newObj), тоді SaveChanges (); Ваше рішення оновлює весь об'єкт без необхідності переглядати всі властивості.
Адам

7
Мені це скаржиться, що я намагаюся редагувати поле посвідчення особи
Василь Холл

3
@VasilyHall - це відбувається, якщо поля ідентифікаторів (або все, що ви визначили Первинний ключ як) відрізняються між моделями (включаючи null / 0 в одній з моделей). Переконайтесь, що ідентифікатори збігаються між двома моделями, і вони будуть оновлюватися просто чудово.
Гевін Коутс

51

Можна скористатися AddOrUpdateметодом:

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

1
Найкраще рішення ІМО
Норгул

112
.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 () у випадку, якщо ви хочете оновити всі поля у вашому об’єкті. Також майте на увазі, що ви не можете змінити ідентифікатор поля (ключ), тому спочатку встановіть ІД на той самий, який ви редагували.

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

7

Для ядра .net

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

5

Ось найкраще рішення цієї проблеми: У Перегляд додайте всі ідентифікатори (ключі). Поміркуйте з назвою декількох таблиць (Перша, Друга та Третя)

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

Якщо у вас є сутність, для якої ви знаєте, що вже існує в базі даних, але до якої могли бути внесені зміни, ви можете сказати контексту, щоб приєднати об'єкт, і встановити його стан "Змінено". Наприклад:

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

Я знайшов спосіб, який працює просто чудово.

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

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


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

Примітка. Працює лише в тому випадку, якщо ваші властивості у вашій моделі точно такі самі, як об’єкт ViewModel, який зберігається в ній.
vapcguy

1

Як сказав Ренат, видаліть: db.Books.Attach(book);

Крім того, змініть запит на результат, щоб він використовував "AsNoTracking", оскільки цей запит викидає стан моделі структури сутності. Він вважає, що "результат" - це книга, яку слід відстежувати зараз, і ви цього не хочете.

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

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);
    }
}
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.