Detect state change of model property, in controller, using Entity Framework

21,481

Solution 1

I believe you can compare the edited entity with the original one read from the database.

Something like:

public ActionResult Edit(Project project)
{
    if (ModelState.IsValid)
    {
        var original = db.Find(project.ID);
        bool changed = original.StartDate != project.StartDate || original.Duration != project.Duration;
        if (changed)
        {
            original.StartDate = project.StartDate;
            original.Duration = project.Duration;
            doSomething();
            db.Entry(original).CurrentValues.SetValues(project);
            db.SaveChanges();
        }
    }
    return View(project);
}

Solution 2

You can solve it by carrying old values via ViewBag.

In action:

public ActionResult Edit(int? id)
{
    //...Your Code
    ViewBag.OrigStartDate = project.StartDate;
    ViewBag.OrigDuration = project.Duration;
    return View(project);
}

Add hidden elements to View

...
@Html.Hidden("OrigStartDate", (DateTime)ViewBag.OrigStartDate)
@Html.Hidden("OrigDuration", (int)ViewBag.OrigDuration)
...

Add these parameters to the post method and check them for changes

[HttpPost]
public ActionResult Edit(DateTime OrigStartDate, int OrigDuration)
{
    if (ModelState.IsValid)
    {
        if (OrigStartDate != project.StartDate || OrigDuration != project.Duration)
            doSomething();

        db.Entry(project).State = EntityState.Modified;
        db.SaveChanges();
        return RedirectToAction("Index");
    }
    ViewBag.FileTypeId = new SelectList(db.FileTypes, "Id", "TypeName", dbinfo.FileTypeId);
    return View(project);
}

Solution 3

Use .AsNoTracking().FirstOrDefault to retrieve the original object for comparison:

public ActionResult Edit(Project project)
{
    if (ModelState.IsValid)
    {
        Project original = db.AsNoTracking().FirstOrDefault( p => p.ID == project.ID);
        if (original.StartDate != project.StartDate || original.Duration != project.Duration)
            doSomething();

        db.Entry(project).State = EntityState.Modified;
        db.SaveChanges();
   }
   return View(project);
}

Solution 4

If you want to do it without querying your persistance layer I guess adding the old values as fields in your model and then keep them in the page as hidden fields is the easiest way to solve this.

So add CurrentStartDate and CurrentDuration in your model:

public class Project {
  public int ID { get; set; }
  //... some more properties

  public DateTime StartDate { get; set; }
  public int Duration { get; set; }

  public DateTime CurrentStartDate { get; set; }
  public int CurrentDuration { get; set; }
}

and then add the hidden fields with the old values in your view:

@Html.HiddenFor(model => model.CurrentStartDate )
@Html.HiddenFor(model => model.CurrentDuration )

This will give you something to compare the selected values with in your controller action.

Share:
21,481

Related videos on Youtube

Chopo87
Author by

Chopo87

Updated on June 16, 2020

Comments

  • Chopo87
    Chopo87 almost 4 years

    I have a more or less standard looking model:

    public class Project {
      public int ID { get; set; }
      //... some more properties
    
      public DateTime StartDate { get; set; }
      public int Duration { get; set; }
    }
    

    If the user modifies StartDate or project Duration, I have to call a function to update a simulation. In order to achieve this I'd like to detect the state change of the fields StartDate and Duration within a controller.

    Something like that:

    if(project.StartDate.stateChange() || project.Duration.stateChange())
    

    Here is an example of what the Controller Method would look like:

    [HttpPost]
    public ActionResult Edit(Project project)
    {
        if (ModelState.IsValid)
        {
            if(project.StartDate.stateChange() || project.Duration.stateChange())
                doSomething();
    
            db.Entry(project).State = EntityState.Modified;
            db.SaveChanges();
            return RedirectToAction("Index");
        }
        return View(project);
    }
    

    Any idea, how can I achieve this?

  • Chopo87
    Chopo87 almost 11 years
    Actually I would prefer to do all the checking within the controller if possible, keeps the code cleaner. Could you expand on how to querying your persistence layer, that sounds like what I'm trying to achieve.
  • Per Kastman
    Per Kastman almost 11 years
    Then you have to fetch the current project from your db and check the old values and compare to the values you are getting from the client.
  • Chopo87
    Chopo87 almost 11 years
    I alreadey tried this. I get the following error: ` An object with the same key already exists in the ObjectStateManager. The ObjectStateManager cannot track multiple objects with the same key. Description: An unhandled exception occurred during the execution of the current web request. Please review the stack trace for more information about the error and where it originated in the code. Exception Details: System.InvalidOperationException: An object with the same key already exists in the ObjectStateManager. The ObjectStateManager cannot track multiple objects with the same key.`
  • Chopo87
    Chopo87 almost 11 years
    I already tried that: I get the following exception An object with the same key already exists in the ObjectStateManager. The ObjectStateManager cannot track multiple objects with the same key.
  • Stefan Szasz
    Stefan Szasz almost 11 years
    With or without 'db.Entry(original).State = EntityState.Modified;'?
  • Chopo87
    Chopo87 almost 11 years
    Ok, I stand corrected, that seems to work, however having to manualy assign original.value= project.value; can get a bit messy, this can be solved by replacing db.Entry(original).State = EntityState.Modified; with db.Entry(original).CurrentValues.SetValues(project);
  • trejder
    trejder almost 10 years
    @Chopo87 Please, respect other users of this site. If you find any kind of mistake in answer, please edit and correct it. Don't just leave "updates" in comments, because comments are second-class citizens here and may be easily missed. Always try to keep answers as accurate as possible. Thank you.
  • Shaiju T
    Shaiju T over 8 years
    can we use auto mapper with viewmodel and make it easy instead of writing multiple || ?
  • Stefan Szasz
    Stefan Szasz over 8 years
    @stom, sure you can. Although you can achieve the same thing using a read-only property on the project entity that encapsulates those conditions if I understand correctly...
  • Shaiju T
    Shaiju T over 8 years
    i have done this by using bool changed and || operator as you mentioned , thanks for that, so to make it clear actually i am getting shipping address details from user using a form generated with View Model, i want to check if postedFormValues != existing shipping address details in database, if true then update modified shipping details in database, can we use auto mapper ? if yes, can you show an example or reference link ?
  • Doctor Jones
    Doctor Jones about 7 years
    I prefer this solution, as it does not involve an extra roundtrip to the database