7

I have a model:

public class VR
{
    [Key]
    public int ID { get; set; }
    public string FullName { get; set; }
    public string CreatedBy { get; set; }
    public DateTime? Created { get; set; }
    public string ModifiedBy { get; set; }
    public DateTime? Modified { get; set; }
}

My controller's Edit function:

    // POST: VRs/Edit/5
    [HttpPost]
    [ValidateAntiForgeryToken]
    public IActionResult Edit(VR vR)
    {
        if (ModelState.IsValid)
        {
            var Result = (from c in _context.MyVR.Where(c => c.ID == vR.ID) select c).Single();

            vR.Created = Result.Created;
            vR.CreatedBy = Result.CreatedBy;
            vR.ModifiedBy = User.Identity.Name;
            vR.Modified = DateTime.Now;
        
            _context.Update(vR);
            _context.SaveChanges();
            return RedirectToAction("Index");
        }
        return View(vR);
    }

and I get the error below:

The instance of entity type 'UNTest.ViewModels.VR' cannot be tracked because another instance of this type with the same key is already being tracked. For new entities consider using an IIdentityGenerator to generate unique key values.

Amal K
  • 4,359
  • 2
  • 22
  • 44
MJ X
  • 8,506
  • 12
  • 74
  • 99
  • 1
    You have it the wrong way around - use `Result.someProperty = vr.someProperty;` etc and `.Update(Result);` –  Mar 13 '16 at 04:43
  • I want that when someone update this page the person who created the Item and the created date should not change, I want only the Modified person and Modified date change can you give a detail example – MJ X Mar 13 '16 at 04:49
  • Does this answer your question? [How to update not every fields of an object using Entity Framework and EntityState.Modified](https://stackoverflow.com/questions/10257360/how-to-update-not-every-fields-of-an-object-using-entity-framework-and-entitysta) – Michael Freidgeim Jan 29 '23 at 11:53

2 Answers2

20

You can Attach the entity to avoid loading it from DB (save performance) and update only the fields you want.

This also avoids the problem of your code when you load an instance from the DB (Result) and track another instance with the same Id (vR) resulting in an exception.

// POST: VRs/Edit/5
    [HttpPost]
    [ValidateAntiForgeryToken]
    public IActionResult Edit(VR vR)
    {
        if (ModelState.IsValid)
        {
            //Attach the instance so that we don't need to load it from the DB
            _context.MyVR.Attach(vR);

            vR.ModifiedBy = User.Identity.Name;
            vR.Modified = DateTime.Now;

            //Specify the fields that should be updated.
            _context.Entry(vR).Property(x => x.ModifiedBy).IsModified = true;
            _context.Entry(vR).Property(x => x.Modified).IsModified = true;

            _context.SaveChanges();
            return RedirectToAction("Index");
        }
        return View(vR);
    }

The other way to specify fields that should not be updated.

// POST: VRs/Edit/5
        [HttpPost]
        [ValidateAntiForgeryToken]
        public IActionResult Edit(VR vR)
        {
            if (ModelState.IsValid)
            {
                //Attach the instance so that we don't need to load it from the DB
                _context.Entry(vR).State = EntityState.Modified; 

                vR.ModifiedBy = User.Identity.Name;
                vR.Modified = DateTime.Now;

                //Specify the fields that should not be updated.
                _context.Entry(vR).Property(x => x.Created).IsModified = false;
                _context.Entry(vR).Property(x => x.CreatedBy).IsModified = false;

                _context.SaveChanges();
                return RedirectToAction("Index");
            }
            return View(vR);
        }

In case you use a view model, you can use new operator to create your data model and copy the fields you want to update:

// POST: VRs/Edit/5
            [HttpPost]
            [ValidateAntiForgeryToken]
            public IActionResult Edit(VRViewModel vRVM)
            {
                if (ModelState.IsValid)
                {
                    VR vR = new VR();
                    //Attach the instance so that we don't need to load it from the DB
                    _context.MyVR.Attach(vR);

                    //Set the Id for your model.
                    vR.Id = vRVM.Id;
                    //Let's say you also want to update this field from the VM
                    vR.FullName = vRVM.FullName;

                    vR.ModifiedBy = User.Identity.Name;
                    vR.Modified = DateTime.Now;

                    //Specify the fields that should be updated.
                    _context.Entry(vR).Property(x => x.ModifiedBy).IsModified = true;
                    _context.Entry(vR).Property(x => x.Modified).IsModified = true;
                    _context.Entry(vR).Property(x => x.FullName).IsModified = true;

                    _context.SaveChanges();
                    return RedirectToAction("Index");
                }
                //create your new view model and return it. For demonstration purpose, I return the same view model, in your real code, you can adjust it.
                return View(vRVM);
            }
Khanh TO
  • 48,509
  • 13
  • 99
  • 115
  • When I do this it makes the Created field and CreatedBy field null – MJ X Mar 13 '16 at 05:22
  • 1
    @Mehdi Jalal: Try removing `_context.Update(vR)` – Khanh TO Mar 13 '16 at 05:23
  • This is also more efficient as it updates only the fields we want instead of updating all the fields which can also run into concurrency problems if other threads also update the other fields. – Khanh TO Mar 13 '16 at 05:25
  • @Mehdi Jalal: Did you try removing `_context.Update(vR);`? – Khanh TO Mar 13 '16 at 05:29
  • I did removed but it does not update my database other fields, should I retype each field the same as you did for ModifiedBy and Modified – MJ X Mar 13 '16 at 05:34
  • @Mehdi Jalal: yes, you should. Because in the question you said that you wanted to update only `ModifiedBy` and `Modified`, that's why I only specifiy 2 fields. – Khanh TO Mar 13 '16 at 05:36
  • @Mehdi Jalal: Check out my second approach if you have many fields to update but only few fields to exclude. – Khanh TO Mar 13 '16 at 05:42
  • Thanks this is perfect :) – MJ X Mar 13 '16 at 05:53
  • You might want to try the code in your edit :) - apart from the exception that would be thrown by `return View(vR);`, you have created a new object (no ID value set) and the existing `Created` and `CreatedBy` properties are not set. –  Mar 13 '16 at 07:43
  • @Stephen Muecke: that's just the mistakes, it's easily corrected. I will correct it now. The important thing is the approach. – Khanh TO Mar 13 '16 at 07:45
  • @Stephen Muecke: the code is updated. We don't need to set `Created` and `CreatedBy` because we don't want to update these fields. – Khanh TO Mar 13 '16 at 07:48
  • @KhanhTO - I want to do like this: `_context.Entry(vR).Property(x => x.CommonFields.CreatedBy).IsModified = false;`. But, its not working. Any idea how can I achieve it? – Gautam G Mar 10 '19 at 00:57
  • @GG Arora: It should be `IsModified = true`. Is it a mistake? – Khanh TO Mar 16 '19 at 02:57
  • @KhanhTO: you probably didn't get my point. I want to prevent `CreatedBy` from getting modified. And, between `x` and `CreatedBy`, there's another object named `CommonFields`. Just try doing it and let me know the results. – Gautam G Mar 21 '19 at 20:04
  • @GG Arora: i think you can get the entry associated with `CommonFields` and do it similarly: `_context.Entry(vR.CommonFields).Property(x => x.CreatedBy).IsModified = false;` (read too fast and missed the navigation property) – Khanh TO Mar 26 '19 at 13:25
4

You have retrieved the data entity from the context so its being tracked. You cannot then track another entity with the same ID in the same context.

Change the POST method to update the data model from the view model, and save the data model

[HttpPost]
[ValidateAntiForgeryToken]
public IActionResult Edit(VR vR)
{
    if (ModelState.IsValid)
    {
        var result = (from c in _context.MyVR.Where(c => c.ID == vR.ID) select c)
           .FirstOrDefault(); // use FirstOrDefault() to prevent an exception if the user changes you input for the ID.
        if (result != null)
        {
           result.FullName = vR.FullName;
           result.ModifiedBy = User.Identity.Name;
           result.Modified = DateTime.Now;
           _context.Update(result);
           _context.SaveChanges();
            return RedirectToAction("Index");
        }
    return View(vR);
}
  • Once you get serious about using MVC and start using [view models](http://stackoverflow.com/questions/11064316/what-is-viewmodel-in-mvc) you will realize that the code in the other answer is going to be pointless :) –  Mar 13 '16 at 06:20
  • @Mehdi Jalal: It's not pointless. First, I don't need to load the entity from the DB, I can use `new` operator to create a new `VR` object and attach it to the context (even my view model is not VR). Second, View Model may contain other properties not related to model, but to view only (that's why it's named view model), we really don't want to save properties related to view. We have to work with models eventually. – Khanh TO Mar 13 '16 at 06:57
  • @Mehdi Jalal: It's `much better` to update only the fields we want without loading the entity from the db. First, there is a performance advantage. Second, we don't run into concurrency problem when we update the other fields loaded from DB. When these unwanted fields are loaded and updated, we think that we update with the same value loaded from db, but it's not, it could override updates from other users on those fields if at the same time, the other fields are being updated on other threads. – Khanh TO Mar 13 '16 at 07:06
  • @Mehdi Jalal: This code has a concurrency problem. Let's say, your `VR` object has another `unchanged` fields (e.x `FirstName`). When the entity is loaded, the FirstName = "A", but during that time, another update could happen in between on another thread and update the FirstName = "B". When you SaveChanges, you accidentally override the value and set it back to "A". Furthermore, updating those fields also slows down performance unnecessarily. – Khanh TO Mar 13 '16 at 07:13
  • @KhanhTO, I assume you do not develop in asp.net.mvc. When you create views for editing objects, you use a view model (not the the data model) and you post back the view model. You then get the data model, update its properties based on the view model and save the data model. Your solution works fine only if you adopt the bad practice of using a data model in the view. –  Mar 13 '16 at 07:17
  • @Stephen Muecke: It's no problem to use view model. We just need to use `new` to create the data model and copy the fields over from the view model, then attach the data model and save the model. It's just a little detail. The main point is `we have to work with data model anyway if we want to update the db`. Remember that a view model may contain information from multiple models (read the link you posted), so that we have to copy the information from view model anyway. – Khanh TO Mar 13 '16 at 07:21
  • @Mehdi Jalal: Checkout my answer with example code when you use a view model. It's just the idea, please correct the details if there is any mistake with the code. – Khanh TO Mar 13 '16 at 07:37