8

I am trying to save an update to an existing database entry but when I do I get the error:

Attaching an entity of type 'FFInfo.DAL.Location' 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.

This is my controller's code. The save method I am using is the same I use in a few other areas to update data with no problems.

[HttpPost, ValidateAntiForgeryToken]
public ActionResult EditLocation(AddEditLocationVM model, HttpPostedFileBase MapFile)
{
    try
    {
        using (var db = new GeographyContext())
        {
            model.Sections = new SelectList(db.Sections.Where(s => s.ID > 1).OrderBy(s => s.Title), "ID", "Title").ToList();
            model.GeographyTypes = new SelectList(db.GeographyTypes.Where(gt => gt.SectionID == model.Section).OrderBy(gt => gt.Name), "ID", "Name").ToList();
            model.ParentLocations = new SelectList(db.Locations.Where(l => l.SectionID == model.Section).OrderBy(l => l.Name), "ID", "Name").ToList();


            if (MapFile != null)
            {
                if (FileHelper.IsNotValidImage(MapFile))
                {
                    ModelState.AddModelError("Invaalid File Type", "Images must be JPG, GIF, or PNG files.");
                }
            }

            if (ModelState.IsValid)
            {
                if (MapFile != null)
                {
                    var SectionRoute = db.Sections.Where(s => s.ID == model.Section).Select(s => s.Route).First();
                    model.MapFileID = FileHelper.UploadFile("Images/" + SectionRoute + "/Maps/" + MapFile.FileName.ToList(), "site", MapFile);
                }

                if (model.ParentLocation == 0)
                {
                    model.ParentLocation = null;
                }

                var UpdatedLocation = new Location()
                {
                    Description = model.Description,
                    GeographyTypeID = model.GeographyType,
                    ID = model.ID,
                    MapFileID = model.MapFileID,
                    Name = model.Name,
                    ParentLocationID = model.ParentLocation,
                    SectionID = model.Section
                };

                db.Entry(UpdatedLocation).State = EntityState.Modified;
                db.SaveChanges();
                ViewBag.Results = "Location information updated.";
            }

            return View(model);
        }
    }
    catch (Exception ex)
    {
        ErrorSignal.FromCurrentContext().Raise(ex);
        model.Sections = Enumerable.Empty<SelectListItem>();
        ViewBag.Results = "Error updating location informaiton, please try again later.";
        return View(model);
    }
}

This is my Location Entity code:

public class Location
{
    [DatabaseGenerated(DatabaseGeneratedOption.Identity)]
    public int ID { get; set; }

    [Required, Index("IX_Location", 1, IsUnique = true)]
    public string Name { get; set; }

    [Index("IX_Location", 2, IsUnique = true)]
    public Int16 SectionID { get; set; }

    [Column(TypeName = "varchar(MAX)")]
    public string Description { get; set; }

    public Int16 GeographyTypeID { get; set; }
    public int? MapFileID { get; set; }
    public int? ParentLocationID { get; set; }

    [ForeignKey("SectionID")]
    public Section Section { get; set; }

    [ForeignKey("GeographyTypeID")]
    public GeographyType GeographyType { get; set; }

    [ForeignKey("MapFileID")]
    public File Map { get; set; }

    [ForeignKey("ParentLocationID")]
    public Location ParentLocation { get; set; }

    public ICollection<LocationTransitionPoint> TransitionPoints { get; set; }
}

This is my first time trying to update a more complex entity like this but from what I have found on the web I can not see anything wrong.

Hamid Pourjam
  • 20,441
  • 9
  • 58
  • 74
Matthew Verstraete
  • 6,335
  • 22
  • 67
  • 123

3 Answers3

16

You can not have two entities (same type) with same primary keys in memory in Entity Framework.

The problem is

model.ParentLocations = new SelectList(db.Locations.Where(l => l.SectionID == model.Section).OrderBy(l => l.Name), "ID", "Name").ToList();

in above line you somehow have loaded the Location which its ID is model.ID

then in

var UpdatedLocation = new Location()
{
    Description = model.Description,
    GeographyTypeID = model.GeographyType,
    ID = model.ID,
    MapFileID = model.MapFileID,
    Name = model.Name,
    ParentLocationID = model.ParentLocation,
    SectionID = model.Section
};
db.Entry(UpdatedLocation).State = EntityState.Modified;

You are creating a new Location and trying to attach it to context (by setting it's state as modified), but you have loaded another Location entity with exact primary key as UpdatedLocation into memory somewhere and this cause the exception.

Try fetching the the location and then change the roperties.

var UpdateLocation = db.Locations.First(l => l.ID == model.ID);
// var UpdateLocation = db.Locations.Find(model.ID); maybe a better option
UpdatedLocation.Description = model.Description;
UpdatedLocation.GeographyTypeID = model.GeographyType;
UpdatedLocation.MapFileID = model.MapFileID;
UpdatedLocation.Name = model.Name;
UpdatedLocation.ParentLocationID = model.ParentLocation;
UpdatedLocation.SectionID = model.Section;
Hamid Pourjam
  • 20,441
  • 9
  • 58
  • 74
0

Try just getting the entity by id rather than creating a new with fixed id:

var UpdateLocation = db.Locations.FirstOrDefault( l => l.ID == model.ID );

UpdateLocation.Description = model.Description;
...

The reason you see your exception is because your

model.ParentLocations = ...

materializes entities that most probably include the one you try to modify. Thus, the entity is already in the first level cache.

But then you try to pretend yet another entity of the same id exists.

Wiktor Zychla
  • 47,367
  • 6
  • 74
  • 106
  • I am sorry but I don't understand what your trying to say the problem is. I think your saying is something to do with `model.ParentLocations =` but I don't understand what your saying the problem with that line is. – Matthew Verstraete Sep 12 '15 at 16:52
  • This particular line asks the db context to materialize (fetch) some entities and this set includes the entity your model contains. The db context already contains the entity with specified ID. And then you present a newly created object and pretend this is the entity of that ID. EF doesn't allow this, it can't have two entities of the same ID under the same instance of db context. – Wiktor Zychla Sep 12 '15 at 16:55
  • You should not use `Where` when you are trying to fetch a single entity. – Hamid Pourjam Sep 12 '15 at 16:57
  • @doctor: sure, you could have edited that obvious typo :) – Wiktor Zychla Sep 12 '15 at 17:01
  • dv was just a sign! :D +1 – Hamid Pourjam Sep 12 '15 at 17:01
  • I have two different lines with `model.ParentLocations = ` based off @dotctor post I presume it is the fetch line your talking about and not the reassignment line? – Matthew Verstraete Sep 12 '15 at 17:01
  • Yes, the one that materializes entities. – Wiktor Zychla Sep 12 '15 at 17:03
  • @MatthewVerstraete I'm an engineer not **doctor** (dotctor is correct). and yes the problem is in the line which fetches the locations. – Hamid Pourjam Sep 12 '15 at 17:04
  • 2
    My apologies, fat fingered your name while typing and eating lunch at the same time. – Matthew Verstraete Sep 12 '15 at 17:07
0

i just created a new instance of the contextEntity and the code worked fine

hamza felix
  • 39
  • 1
  • 4