320

I am trying to update a record using EF6. First finding the record, if it exists, update. Here is my 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;
        }
    }
}

Every time I try to update the record using the above code, I am getting this error:

{System.Data.Entity.Infrastructure.DbUpdateConcurrencyException: Store update, insert, or delete statement affected an unexpected number of rows (0). Entities may have been modified or deleted since entities were loaded. Refresh ObjectStateManager entries

Grigory Zhadko
  • 1,484
  • 1
  • 19
  • 33
user1327064
  • 4,187
  • 5
  • 20
  • 30
  • 9
    Side note: `catch (Exception ex){throw;}` is redundant and you can completely remove it. – Sriram Sakthivel Sep 17 '14 at 15:30
  • 1
    try catch block is just to figure out the reason of failing. But Still did not get it why this code is failing? – user1327064 Sep 17 '14 at 15:33
  • 2
    Am not expert in this topic, I can't answer this question. but without try catch also you can use [break when exception is thrown feature](http://msdn.microsoft.com/en-us/library/d14azbfh.aspx) to break the debugger when there is an exception. – Sriram Sakthivel Sep 17 '14 at 15:35
  • If you search the web or this site for that error, you get many hints. Which ones did you find and what did you try? What does your table look like? – CodeCaster Sep 17 '14 at 15:38
  • @CodeCaster: My table does not have any constraint of NOT NULL (except primary key, which is set to auto incremental). Checked in sql profiler too, If run same query manually, it works. – user1327064 Sep 17 '14 at 15:51
  • My Model type is Database-First. – user1327064 Sep 17 '14 at 15:53
  • 1
    You haven't changed anything. Playing with Entity state won't change the fact that the object hasn't actually been modified. – Jonathan Allen Sep 17 '14 at 19:14
  • 1
    Well, I did the same as you and didn't get the error. The Exception says DbUpdateConcurrencyException. How did you handle the concurrency? Did you use a timestamp, did you clone and then merge the objects again or did you use self-tracking entities? (3 most used approaches). If you didn't handle the concurrency, I guess that's the problem. – El Mac Dec 13 '16 at 18:47
  • How is it that none of the comments/answers here homes in on whether the `book` object has a valid primary key value? Because it hasn't. If it had, it couldn't have been attached after `result` was found. *That's* the cause of the `DbUpdateConcurrencyException`. *Maybe* [this answer](https://stackoverflow.com/a/41281769/861716) does a half-baked attempt addressing the flaw, but it doesn't really explain much. – Gert Arnold Feb 13 '19 at 20:59

20 Answers20

437

You're trying to update the record (which to me means "change a value on an existing record and save it back"). So you need to retrieve the object, make a change, and save it.

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.
  • 17,838
  • 6
  • 49
  • 82
  • 2
    W, how does this exactly work? how does assinging a value to "result", actually updates the database? – vvavepacket Mar 03 '15 at 22:29
  • 26
    Assigning the value doesn't update the database, calling `db.SaveChanges()` with modified objects in the context updates the database. – Craig W. Mar 03 '15 at 23:06
  • 11
    Still it fascinates me... so var result, actually becomes connected to the dbcontext... so this means that any variable that is instantiated by any dbcontext members will actually have that associaten to the database so that whatever changes is applied to that variable, it is also applied or persisted? – vvavepacket Mar 04 '15 at 14:30
  • 10
    Because the context generated the object the context can track the object, including changes to the object. When you call `SaveChanges` the context evaluates all the objects it is tracking to determine if they are added, changed, or deleted and issues the appropriate SQL to the connected database. – Craig W. Mar 04 '15 at 18:07
  • 3
    iam facing same issue - using EF6 , trying to update an entity. Attach + EntityState.Modified not working . Only thing working is - you need to retrieve the object, make desired changes, and save it via db.SaveChanges(); – Gurpreet Singh Jun 10 '15 at 10:06
  • 2
    Is this query same ? , `var result = db.Books.Where(b => b.BookNumber == bookNumber).SingleOrDefault();` and is there any [performance](http://stackoverflow.com/a/1745709/2218697) consideration between `FirstOrDefault` and `SingleOrDefault` ? – Shaiju T Oct 08 '16 at 09:38
  • 2
    @stom: Yes, the query is the same in the sense that it will return the same result. You'd have to test it to see if there's a performance difference in your specific situation. – Craig W. Oct 17 '16 at 14:27
  • 13
    You should NOT have to retrieve the object first in order to update it. I had the same problem until I realized I was trying to change one of the primary key values (composite key). As long as you provide a correct primary key, you can set the EntityState to Modified and SaveChanges() will work, provided you don't break some other integrity constraint defined on the table. – adrianz Oct 22 '17 at 12:50
  • @CraigW., I am doing a similar thing but instead of updating, its adding a new entry for me. Any ideas why? https://stackoverflow.com/questions/51508730/updating-entry-adds-a-new-entry-to-database – Maddy Jul 24 '18 at 23:17
  • The result is passed by reference, that's why this works :) – Storm Muller Oct 02 '18 at 13:14
  • When you pull the item using line 3 it automatically sets it up for change tracking. That is why this works. To do it the other way you need to not load it for change tracking ( omit line 3 ) attach, set state to Modified and then save changes.It's all based on knowing when something is set for change tracking. – Damon Drake Feb 07 '19 at 22:15
  • 1
    If you do anything to set context.Configuration.AutoDetectChangesEnabled to `false`, you'll need to set it back to `true` before this will work. I changed it to `false` for `BulkInsert` to work faster and I didn't see any updates using this method until I changed it back to `true` before making the changes. – computercarguy Oct 03 '19 at 23:22
235

I have been reviewing the source code of Entity Framework and found a way to actually update an entity if you know the Key property:

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

Otherwise, check the AddOrUpdate implementation for ideas.

Hope this help!

Miguel
  • 3,786
  • 2
  • 19
  • 32
  • 21
    Nice! No need to enumerate all properties. I assume `SaveChanges()` call is required after setting values. – Jan Zahradník Oct 16 '16 at 19:57
  • 6
    Yes, changes will be persisted on SaveChanges() – Miguel Oct 17 '16 at 20:23
  • 1
    Great answer, it was not too clear with IntelliSense that doing something like this would NOT work: _context.MyObj = newObj; then SaveChanges() or.... _context.MyObj.Update(newObj) then SaveChanges(); Your solution updates the whole object without having to loop through all the properties. – Adam Oct 13 '17 at 04:17
  • 13
    This complains to me that I'm trying to edit the ID field – Vasily Hall Nov 09 '17 at 20:57
  • @Miguel What references do you need to run this? I'm getting the following errors: T does not contain a definition for Id _collection does not exist in the current context – DarthVegan Jun 30 '18 at 14:01
  • T is a generic parameter, use your entity class instead: User, Order, ... – Miguel Jul 02 '18 at 14:13
  • 6
    @VasilyHall - this occurs if the ID fields (or whatever you have defined the Primary Key as) are different between models (including null / 0 in one of the models). Make sure the IDs match between the two models and it will update just fine. – Gavin Coates Aug 24 '18 at 13:07
  • 1
    Wow life changing. I did have to do the following before I set the values to keep the primary keys the same... entity.Id = existingEntity.Id; – Dblock247 Sep 18 '18 at 21:47
  • @Miguel - Can you update your answer with the ID comments here? Tripped me up for a while before I read them. – Trent Aug 09 '19 at 02:28
  • 1
    What is _collection here? – Aaron Oct 16 '19 at 20:53
  • 1
    @Aaron The _collection variable is a DbSet – Miguel Nov 01 '19 at 17:08
  • This code is trying to update all the values irrespective of the null entries. I want the values in the old object to be retained in a case where new values not supplied. IS: I want only the non-empty values to be updated ! – Thameem Nov 13 '19 at 07:28
  • Any help would be appreciated – Thameem Nov 13 '19 at 07:28
  • @Thameem I haven't done what you suggest but the whole idea of the method is that you have a valid entity to update from. If the entity you are passing has some null properties that aren't null in the DB your are getting out of sync or setting the properties to null explicit in your code – Miguel Nov 27 '19 at 18:51
  • @Toolkit Please provide a better approach to retrieve the current entity within the EF boundaries. This will trigger a select if your entity is not in memory. Must of the time it will be or you will reuse after that. Anyway, this select will have a where clause on the key (which is indexed) I can think of a faster way to retrieve the db value at the time. The fact you think is amateurish it is good, please enlighten us with the truth. – Miguel Jan 13 '21 at 16:53
  • This throws a `Member 'CurrentValues' cannot be called for the entity of type 'Entity' because the entity does not exist in the context. To add an entity to the context call the Add or Attach method of DbSet.` error on the 2nd to last line for me – emilaz Mar 08 '23 at 16:10
  • @emilaz did you modified the query line (var entity = _collection.Find(item.Id);) or the entity don't exists already? This is an update method meant to update an existing entity. – Miguel Apr 19 '23 at 18:37
52

You can use the AddOrUpdate method:

db.Books.AddOrUpdate(book); //requires using System.Data.Entity.Migrations;
db.SaveChanges();
El Mac
  • 3,256
  • 7
  • 38
  • 58
nicedev80
  • 1,575
  • 1
  • 13
  • 17
  • 1
    IMO best solution – Norgul Nov 30 '16 at 14:16
  • 153
    `.AddOrUpdate()` is used during database migration, it is highly discouraged to use this method outside of migrations, hence why it's in the `Entity.Migrations` namespace. – Adam Vincent Apr 05 '17 at 01:22
  • I did reverse engineering on this AddOrUpdate code and the results are my other answer. https://stackoverflow.com/questions/25894587/how-to-update-record-using-entity-framework-6/36684660#36684660 – Miguel Aug 22 '17 at 20:43
  • 2
    As @AdamVincent said, `AddOrUpdate()` method is intended for migrations and it's not suitable for situation when you need only to update existing row. In case when you don't have book with search reference (i.e. ID) it'll create new row and it can be an issue in come cases (for example, you have an API which needs to return you 404-NotFound response if you try to call PUT method for non-existing row). – Marko Oct 18 '17 at 13:12
  • 7
    Don;t use this unless you know what you are doing!!!!!!!!!!!!!!!! read: https://www.michaelgmccarthy.com/2016/08/24/entity-framework-addorupdate-is-a-destructive-operation/ – Yusha Apr 26 '18 at 18:38
  • 6
    I came back to this again today, can I just warn you all that this is *not* a good solution for the desired use case – Yusha Apr 26 '19 at 13:05
32

So you have an entity that is updated, and you want to update it in the database with the least amount of code...

Concurrency is always tricky, but I am assuming that you just want your updates to win. Here is how I did it for my same case and modified the names to mimic your classes. In other words, just change attach to add, and it works for 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();
    }
}
Lauren Rutledge
  • 1,195
  • 5
  • 18
  • 27
Duray Akar
  • 448
  • 4
  • 4
20

You should use the Entry() method in case you want to update all the fields in your object. Also keep in mind you cannot change the field id (key) therefore first set the Id to the same as you edit.

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
  • 307
  • 4
  • 12
  • 3
    You should at least try to answer the question, not just post the code –  Nov 17 '17 at 00:21
  • Please make some explanation to the question instead of just leaving a code snippet in order to help the question asker better. – feanor07 Nov 17 '17 at 04:55
19

Attaching an entity will set its tracking state to Unchanged. To update an existing entity, all you need to do is set the tracking state to Modified. According to the EF6 docs:

If you have an entity that you know already exists in the database but to which changes may have been made then you can tell the context to attach the entity and set its state to Modified. For example:

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
  • 2,793
  • 7
  • 34
  • 62
  • Thanks. That is the perfect solution for me as it can save a lots of line of code for updating the attributes of the object. And when Model updates, we also need to update the controller and that's not EF should work. – Zaheer Apr 14 '22 at 11:19
10

I found a way that works just fine.

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

        // Mark as Changed
        context.Entry(Update).State = System.Data.Entity.EntityState.Modified;
        context.SaveChanges();
Farhan
  • 1,445
  • 16
  • 24
9

This code is the result of a test to update only a set of columns without making a query to return the record first. It uses Entity Framework 7 code first.

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

Here is the complete 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);
    }
}
Lauren Rutledge
  • 1,195
  • 5
  • 18
  • 27
Juan
  • 1,352
  • 13
  • 20
9

For .net core

context.Customer.Add(customer);
context.Entry(customer).State = Microsoft.EntityFrameworkCore.EntityState.Modified;
context.SaveChanges();
Chris Rosete
  • 1,240
  • 15
  • 13
  • 1
    with this send a proper update or it will send all properties? Suppose I have a record with 10Mb text property. Will it be sending it to DB every time when I update another property? – Toolkit Jan 01 '21 at 18:48
5

Here is best solution for this issue: In View add all the ID (Keys). Consider having multiple tables named (First, Second and Third)

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

In 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);
}
H. Pauwelyn
  • 13,575
  • 26
  • 81
  • 144
Kumar R
  • 51
  • 1
  • 3
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();
}
Nikhil Dinesh
  • 3,359
  • 2
  • 38
  • 41
3

You should remove db.Books.Attach(book);

Petter Friberg
  • 21,252
  • 9
  • 60
  • 109
3

This if for Entity Framework 6.2.0.

If you have a specific DbSet and an item that needs to be either updated or created:

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

However this can also be used for a generic DbSet with a single primary key or a composite primary key.

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
  • 62,132
  • 37
  • 328
  • 418
1

Here's my post-RIA entity-update method (for the Ef6 time frame):

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

Note that FrameworkTypeUtility.SetProperties() is a tiny utility function I wrote long before AutoMapper on 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
  • 5,288
  • 2
  • 45
  • 60
  • Note: Works only if your properties are exactly the same in your model as your ViewModel object that is being saved into it. – vapcguy Oct 15 '18 at 14:55
1

Like Renat said, remove: db.Books.Attach(book);

Also, change your result query to use "AsNoTracking", because this query is throwing off entity framework's model state. It thinks "result" is the book to track now and you don't want that.

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

Try It....

UpdateModel(book);

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
  • 47
  • 4
1

I know it has been answered good few times already, but I like below way of doing this. I hope it will help someone.

//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
  • 1,856
  • 2
  • 16
  • 26
1

The easiest way to do it is like so.

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
        {
            // you can't attach book since it doesn't exist in the database yet
            // attach result instead
            db.Books.Attach(result);
            result = book; // this will update all the fields at once
            db.SaveChanges();
        }
        catch (Exception ex)
        {
            throw;
        }
    }
}
Jwdsoft
  • 431
  • 3
  • 9
0

I have the same problem when trying to update record using Attach() and then SaveChanges() combination, but I am using SQLite DB and its EF provider (the same code works in SQLServer DB without problem).

I found out, when your DB column has GUID (or UniqueIdentity) in SQLite and your model is nvarchar, SQLIte EF treats it as Binary(i.e., byte[]) by default. So when SQLite EF provider tries to convert GUID into the model (string in my case) it will fail as it will convert to byte[]. The fix is to tell the SQLite EF to treat GUID as TEXT (and therefore conversion is into strings, not byte[]) by defining "BinaryGUID=false;" in the connectionstring (or metadata, if you're using database first) like so:

  <connectionStrings>
    <add name="Entities" connectionString="metadata=res://savetyping...=System.Data.SQLite.EF6;provider connection string=&quot;data source=C:\...\db.sqlite3;Version=3;BinaryGUID=false;App=EntityFramework&quot;" providerName="System.Data.EntityClient" />
  </connectionStrings>

Link to the solution that worked for me: How does the SQLite Entity Framework 6 provider handle Guids?

k3nn
  • 131
  • 1
  • 6
0

Not related to this specific example, but I came across a challenge when trying to use EF and a DateTime field as the concurrency check field. It appears the EF concurrency code doesn't honor the precision setting from the metadata (edmx) i.e. Type="DateTime" Precision="3". The database datetime field will store a millisecond component within the field (i.e. 2020-10-18 15:49:02.123). Even if you set the original value of the Entity to a DateTime that includes the millisecond component, the SQL EF generates is this:

UPDATE [dbo].[People]
SET [dateUpdated] = @0
WHERE (([PeopleID] = @1) AND ([dateUpdated] = @2))
-- @0: '10/19/2020 1:07:00 AM' (Type = DateTime2)
-- @1: '3182' (Type = Int32)
-- @2: '10/19/2020 1:06:10 AM' (Type = DateTime2)

As you can see, @2 is a STRING representation without a millisecond component. This will cause your updates to fail.

Therefore, if you're going to use a DateTime field as a concurrency key, you must STRIP off the milliseconds/Ticks from the database field when retrieving the record and only pass/update the field with a similar stripped DateTime.

    //strip milliseconds due to EF concurrency handling
    PeopleModel p = db.people.Where(x => x.PeopleID = id);
    if (p.dateUpdated.Millisecond > 0)
    {
        DateTime d = new DateTime(p.dateUpdated.Ticks / 10000000 * 10000000);
        object[] b = {p.PeopleID, d};
        int upd = db.Database.ExecuteSqlCommand("Update People set dateUpdated=@p1 where peopleId=@p0", b);
        if (upd == 1)
            p.dateUpdated = d;
        else
            return InternalServerError(new Exception("Unable to update dateUpdated"));
    }
return Ok(p);

And when updating the field with a new value, strip the milliseconds also

(param)int id, PeopleModel person;
People tbl = db.People.Where(x => x.PeopleID == id).FirstOrDefault();
db.Entry(tbl).OriginalValues["dateUpdated"] = person.dateUpdated;
//strip milliseconds from dateUpdated since EF doesn't preserve them
tbl.dateUpdated = new DateTime(DateTime.Now.Ticks / 10000000 * 10000000);
Derek Wade
  • 697
  • 8
  • 11