Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

EF 6 SaveChanges with multiple references to same (changed) object

I have a class with two references to the same class. When updating the main class, I may also update the referenced class. When I have two references to the same (modified) object, I get an InvalidOperationException:

Attaching an entity of type 'ns.entity' failed because another entity of the same type already has the same primary key value. This can happen when using the 'Attach' method or setting the state of an entity to 'Unchanged' or 'Modified' if any entities in the graph have conflicting key values. This may be because some entities are new and have not yet received database-generated key values. In this case use the 'Add' method or the 'Added' entity state to track the graph and then set the state of non-new entities to 'Unchanged' or 'Modified' as appropriate.

Simple example:

public class Example {
    public int OldestFriendId {get; set;}
    public int BestFriendId {get; set;}
    public virtual Friend Oldest {get; set; }
    public virtual Friend Best {get; set; }

 }

If while updating Example, I want to update the Middle name of my Oldest/Best friend, it works as long as they aren't the same. But if they are the same, then I get the above exception.

I can't figure out how to get this to work. I've tried setting references to null, saving them independently of the parent class, setting all references in them to null (EF is automatically creating two list of Examples in Friend).

How can I save an object that has changed when there are multiple references to it?

UPDATE: not yet working the way I want, but I have had some progress after removing the list of Examples from Friend. Also, the update is the result of a POST. Still investigating...

As sample code was asked for...this is from a post on a web app, no change was actually made

public ActionResult SaveEdit(int id, [Bind(Include = "OldestFriendId, BestFrinedId, Oldest, Best")] Example example)
{
    if (ModelState.IsValid)
        {
            using (((WindowsIdentity)ControllerContext.HttpContext.User.Identity).Impersonate())
            {
                using (var _db = new exampleEntities())
                {
                  //example.Best= example.Oldest; // this line would allow the update to work.

                  //next line is where the exception occurs
                   _db.Entry(example).State = EntityState.Modified;
                   _db.SaveChanges();
                 }
            }
        }
}

The EditorFor template:

@model Testing.Friend

<div class="col-md-10">
    @Html.HiddenFor(model => model.FriendId)
    @Html.EditorFor(model => model.FirstName)
    @Html.EditorFor(model => model.LastName)
</div>

The Edit view for Example

@model Testing.Example

@using (Html.BeginForm())
{
@Html.AntiForgeryToken()

<div class="form-horizontal">
    <h4>Example</h4>
    <hr />
    @Html.ValidationSummary(true, "", new { @class = "text-danger" })
    @Html.HiddenFor(model => model.ExampleId)

    <div class="form-group">
        @Html.LabelFor(model => model.OldestFriendId, "OldestFriendId", htmlAttributes: new { @class = "control-label col-md-2" })
        @Html.HiddenFor(model => model.OldestFriendId)
        @Html.EditorFor(model => model.Oldest)
    </div>

    <div class="form-group">
        @Html.LabelFor(model => model.BestFriendId, "BestFriendId", htmlAttributes: new { @class = "control-label col-md-2" })
        @Html.HiddenFor(model => model.BestFriendId)
        @Html.EditorFor(model=> model.Best)
    </div>

    <div class="form-group">
        <div class="col-md-offset-2 col-md-10">
            <input type="submit" value="Save" class="btn btn-default" />
        </div>
    </div>
</div>

}

like image 263
jmoreno Avatar asked Apr 12 '26 13:04

jmoreno


1 Answers

EDIT

The most likely cause is because when you retrieve the object back, it deserializes the 2 friends as 2 completely different objects (even when they are the same). Same problem as below, but rather than EF deserializing into 2 objects, ASP.NET MVC is doing it.

What you will have to do is something like the following:

  1. Check if the 2 Friend ID's are the same (as ID is the PK). If not continue as normal
  2. If they have the same ID, check if the 2 friend objects are the same.
  3. If they are the same go to step 5.
  4. Combine the changes together, however you want to deal with conflicts.
  5. Set one of the Freinds to the same as the other Friend reference, e.g. Best = Oldest
  6. SaveChanges()

Original Answer

My guess is that this is the classic problem of Include when you are retrieving the data.

When you do

 Context.Examples.Include(x => x.Oldest).Include(x => x.Best).ToList()

What is happening is EF will create TWO objects of friend(Oldest and Best), even if they point to the same record. This is a known problem with include.

So when you go to save after update, EF sees them as 2 separate entities with the same key (and data) and complains.

If this is the case you have a couple of options:

  • Retrieve a list of all Friends for the current example and then the Example without the include
  • Let EF use LazyLoading and load the Friends when and as you need them.
like image 133
Michal Ciechan Avatar answered Apr 18 '26 00:04

Michal Ciechan



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!