3

I've checked this question and it seems to be related to what I need, but does not answer it exactly.

I have an entity (Sql Compact using EF Code First via MVC3- if that wasn't clear from the title) for an "Issue" (generic issue tracking, just for my own education understanding of how MVC3 works). The Issue class has a CreatedBy property (Int reference to a User who Created the Issue) and a CreatedDate property (DateTime). When I use the scaffolded code to update (modified only to prevent some updated date fields from being modified by the user):

        if (ModelState.IsValid)
        {
            issue.LastActivity = (DateTime?)DateTime.Now.Date;
            if (issue.ClosedBy != null) issue.ClosedDate = (DateTime?)DateTime.Now.Date;
            startingIssue = null;
            db.Entry(issue).State = EntityState.Modified;
            db.SaveChanges();
            return RedirectToAction("Index");
        }

I receive the error mentioned in the linked question (conversion of a datetime2 data type to a datetime data type etc., etc.,)

When I step through the code, it appears my CreatedBy and CreatedDate properties are not contained in the instance of issue that the controller is passing around. When I try to fix that by grabbing another copy of the issue from the db, and updating those to values:

        var startingIssue = db.Issues.Find(issue.IssueId);
        if (ModelState.IsValid)
        {
            if (issue.CreatedBy != startingIssue.CreatedBy) issue.CreatedBy = startingIssue.CreatedBy;
            if (issue.CreatedDate != startingIssue.CreatedDate) issue.CreatedDate = startingIssue.CreatedDate;
            issue.LastActivity = (DateTime?)DateTime.Now.Date;
            if (issue.ClosedBy != null) issue.ClosedDate = (DateTime?)DateTime.Now.Date;
            startingIssue = null;
            db.Entry(issue).State = EntityState.Modified;
            db.SaveChanges();
            return RedirectToAction("Index");
        }

I get the concurrency violation: An object with the same key already exists in the ObjectStateManager. The ObjectStateManager cannot track multiple objects with the same key.

So, how do I get EF to see the date which is already set in the DB (so it doesn't try to update the CreatedDate to 1/1/0001) without violating concurrency?

Edit Okay... I found it. I was, apparently, looking for @Html.HiddenFor(model => model.[property]) and adding the editor to the view anyway. That seems a little silly and round-about to me, but it does work without having to add custom code to detach one object and substitute an updated one.

Community
  • 1
  • 1
AllenG
  • 7,946
  • 26
  • 38

2 Answers2

15

The short answer is that you've already loaded the entity into the context with the Find and you cannot later attach another one.

You are left with two options:

  • Detach the first instance, then attach the second
  • Copy the fields from the second instance to the first

I'll share code for the first option. First, add a Detach method to your DbContext implementation:

public void Detach(object entity)
{
    var objectContext = ((IObjectContextAdapter)this).ObjectContext;
    objectContext.Detach(entity);
}

Then call Detach instead of setting the variable to null

var startingIssue = db.Issues.Find(issue.IssueId);
if (ModelState.IsValid)
{
    if (issue.CreatedBy != startingIssue.CreatedBy) issue.CreatedBy = startingIssue.CreatedBy;
    if (issue.CreatedDate != startingIssue.CreatedDate) issue.CreatedDate = startingIssue.CreatedDate;
    issue.LastActivity = (DateTime?)DateTime.Now.Date;
    if (issue.ClosedBy != null) issue.ClosedDate = (DateTime?)DateTime.Now.Date;

    // startingIssue = null;
    db.Detach(startingIssue);

    db.Entry(issue).State = EntityState.Modified;
    db.SaveChanges();
    return RedirectToAction("Index");
}
Ed Chapel
  • 6,752
  • 3
  • 25
  • 43
  • That works. Thanks. I'll wait to see if anyone else happens to provide a more elegant solution, but if not, I'll choose yours as the accepted answer. – AllenG May 16 '11 at 19:53
  • 1
    I had a similar problem. I have audit fields on my tables (CreatedBy, CreatedOn) that I need to set when I create a new record but I don't want to pass them around to edit pages on my MVC site. So when a new record is being edited I have to read the current audit values from the DB and then update the incoming object before saving it int he DBContext. To do this I need to .Find(id), grab the current value and then Detach. My question/answer is here http://stackoverflow.com/questions/8145635/what-is-the-best-way-to-maintain-an-entitys-original-properties-when-they-are-n/8154045#8154045 – kingdango Nov 16 '11 at 15:27
  • @Ed: I would like to give a +100k .. I've lost hours on that problem! Thank you!! – Davide Jan 31 '13 at 20:14
0

If the CreateDate and CreatedBy fields are not in the edit form, the update action object will not have the db values.

The additional call to the db and resetting, as Ed's answer describes, can be avoided if you include those fields in the edit form. Then the normal model binding should pick them up and give them back to you on the update.

Steve Mallory
  • 4,059
  • 1
  • 25
  • 31
  • 1
    Indeed they could, but then they would also be editable. I do display the values already, just as text. If I add the editor-field for the values, I'd have to go to effort in other ways to prevent a user from deciding that their issue was actually created 10 days ago instead of just earlier today. – AllenG May 16 '11 at 19:55
  • 1
    Yes, making them hidden fields would make Binding work but it would expose those fields to manipulation which is not good if the fields need to stay constant. – kingdango Nov 16 '11 at 15:29