tsimmons
tsimmons

Reputation: 21

Attaching an entity of type 'X' failed because another entity of the same type already has the same primary key value. error

Hello I am quite new to MVC programming and have come across a rather strange error I cannot seem to wrap my head around. I am creating an MVC 5 application, and When I try and use the edit option from my controller, the parent entity and child entities all seem to load correctly, however when I try and save the changes back I receive the error "Attaching an entity of type 'X.Models.ApplicationUser' failed because another entity of the same type already has the same primary key value". I have the below ViewModel/Models:

    public class EditMatchesViewModel
{
    public int ID { get; set; }
    [Required]
    public DateTime Date { get; set; }
    [Required]
    public string Division { get; set; }
    [Required]
    public Team HomeTeam { get; set; }
    [Required]
    public Team AwayTeam { get; set; }
    public List<Game> Games { get; set; }
    public MatchStatus Status { get; set; }

}

    public class Game
{
    public int ID { get; set; }
    public GameType GameType { get; set; }
    public virtual ApplicationUser AwayPlayer1 { get; set; }
    public virtual ApplicationUser AwayPlayer2 { get; set; }
    public virtual ApplicationUser HomePlayer1 { get; set; }
    public virtual ApplicationUser HomePlayer2 { get; set; } 
    public int AwayScore1 { get; set; }
    public int AwayScore2 { get; set; }
    public int HomeScore1 { get; set; }
    public int HomeScore2 { get; set; }

}

public class ApplicationUser : IdentityUser
{
    public async Task<ClaimsIdentity> GenerateUserIdentityAsync(UserManager<ApplicationUser> manager)
    {
        // Note the authenticationType must match the one defined in CookieAuthenticationOptions.AuthenticationType
        var userIdentity = await manager.CreateIdentityAsync(this, DefaultAuthenticationTypes.ApplicationCookie);
        // Add custom user claims here
        return userIdentity;
    }
    public string UserRole { get; set; }
    public bool Active { get; set; }
    public Team Team { get; set; }
}

    public class Team
{
    public int ID { get; set; }
    public string TeamName { get; set; }
    public bool Active { get; set; }
    public virtual ICollection<ApplicationUser> Players { get; set; }
}

And the following controller:

public async Task<ActionResult> Edit(int? id)
    {
        if (id == null)
        {
            return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
        }
        Match matchData = await db.Matches.
            Where(m => m.ID == id).
            Include(t => t.AwayTeam).
            Include(t2 => t2.HomeTeam).
            Include(x => x.Games.Select(g => g.GameType)).               
            FirstOrDefaultAsync();

        EditMatchesViewModel model = new EditMatchesViewModel
        {
            Date = matchData.Date,
            Division = matchData.Division,
            AwayTeam = matchData.AwayTeam,
            HomeTeam = matchData.HomeTeam,
            ID = matchData.ID,
            Status = matchData.Status,
            Games = matchData.Games.ToList()
        };

        ViewBag.teams = new SelectList(db.Teams.Where(a => a.Active == true).ToList(), "ID", "TeamName");
        ViewBag.players = db.Users.AsNoTracking().Where(a => a.Active == true).ToList();
        ViewBag.gametypelist = db.GameType.Where(a => a.Active == true).AsNoTracking().ToList();
        if (model == null)
        {
            return HttpNotFound();
        }
        return View(model);
    }

    // POST: Matches/Edit/5
    // To protect from overposting attacks, please enable the specific properties you want to bind to, for 
    // more details see https://go.microsoft.com/fwlink/?LinkId=317598.
    [HttpPost]
    [ValidateAntiForgeryToken]
    public async Task<ActionResult> Edit([Bind(Include = "ID,Date,Division,HomeTeam,AwayTeam,Games")] Match match)
    {
        if (ModelState.IsValid)
        {

            db.Entry(match).State = EntityState.Modified;
            //db.Set<Match>().AddOrUpdate(match);
            await db.SaveChangesAsync();
            return RedirectToAction("Index");
        }
        return View(match);
    }

And finally the following view:

@model TennisClub.Models.EditMatchesViewModel

@{
ViewBag.Title = "Update match and game information.";
}

<h2>Edit</h2>


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

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

    <div class="form-group">
        @Html.LabelFor(model => model.Date, htmlAttributes: new { @class = 
"control-label col-md-2" })
        <div class="col-md-10">
            @Html.EditorFor(model => model.Date, new { htmlAttributes = new { @class = "form-control" } })
            @Html.ValidationMessageFor(model => model.Date, "", new { @class = "text-danger" })
        </div>
    </div>

    <div class="form-group">
        @Html.LabelFor(model => model.Division, htmlAttributes: new { @class = "control-label col-md-2" })
        <div class="col-md-10">
            @Html.EditorFor(model => model.Division, new { htmlAttributes = new { @class = "form-control" } })
            @Html.ValidationMessageFor(model => model.Division, "", new { @class = "text-danger" })
        </div>
    </div>

    <div class="form-group">
        @Html.LabelFor(model => model.HomeTeam, htmlAttributes: new { @class = "control-label col-md-2" })
        <div class="col-md-10">
            @Html.DropDownListFor(model => model.HomeTeam.ID, (IEnumerable<SelectListItem>)ViewBag.Teams, "Please Select Home Team", new { @class = "form-control" })
            @Html.ValidationMessageFor(model => model.HomeTeam, "", new { @class = "text-danger" })
        </div>
    </div>

    <div class="form-group">
        @Html.LabelFor(model => model.AwayTeam, htmlAttributes: new { @class = "control-label col-md-2" })
        <div class="col-md-10">
            @Html.DropDownListFor(model => model.AwayTeam.ID, (IEnumerable<SelectListItem>)ViewBag.Teams, "Please Select Away Team", new { @class = "form-control" })
            @Html.ValidationMessageFor(model => model.AwayTeam, "", new { @class = "text-danger" })
        </div>
    </div>


    <table class="table table-striped">
        <thead>
            <tr>
                <th>Game Type</th>
                <th>Away Team</th>
                <th>Home Team</th>
                <th>Away Score</th>
                <th>Home Score</th>
            </tr>
        </thead>
        <tbody>
            @for (var i = 0; i < Model.Games.Count; i++)
            {

                <tr>
                    @Html.HiddenFor(model => Model.Games[i].ID)
                    <td>@Html.LabelFor(model => 
Model.Games[i].GameType.Name, htmlAttributes: new { @class = "control-label 
col-md-2" })<br />@Html.DropDownListFor(model => Model.Games[i].GameType.ID, 
new SelectList(ViewBag.gametypelist, "ID", "Name", 
Model.Games[i].GameType.ID), null, new { @class = "form-control" })</td>
                    <td>
                        @Html.LabelFor(model => 
Model.Games[i].AwayPlayer1.UserName, htmlAttributes: new { @class = 
"control-label col-md-2" })<br />@Html.DropDownListFor(model => 
Model.Games[i].AwayPlayer1.Id,
Model.Games[i].AwayPlayer1 != null ? new SelectList(ViewBag.players, "Id", 
"UserName", Model.Games[i].AwayPlayer1.Id) :
new SelectList(ViewBag.players, "Id", "UserName"), "Please select away 
player 1", new { @class = "form-control" })
                        <br />
                        @Html.LabelFor(model => 
Model.Games[i].AwayPlayer2.UserName, htmlAttributes: new { @class = 
"control-label col-md-2" })<br />@Html.DropDownListFor(model => 
Model.Games[i].AwayPlayer2.Id,
Model.Games[i].AwayPlayer2 != null ? new SelectList(ViewBag.players, "Id", 
"UserName", Model.Games[i].AwayPlayer2.Id) :
new SelectList(ViewBag.players, "Id", "UserName"), "Please select away 
player 2", new { @class = "form-control" })
                    </td>
                    <td>
                        @Html.LabelFor(model => 
Model.Games[i].HomePlayer1.UserName, htmlAttributes: new { @class = 
"control-label col-md-2" })<br />@Html.DropDownListFor(model => 
Model.Games[i].HomePlayer1.Id,
Model.Games[i].HomePlayer1 != null ? new SelectList(ViewBag.players, "Id", 
"UserName", Model.Games[i].HomePlayer1.Id) :
new SelectList(ViewBag.players, "Id", "UserName"), "Please select home 
player 1", new { @class = "form-control" })
                        <br />
                        @Html.LabelFor(model => 
Model.Games[i].HomePlayer2.UserName, htmlAttributes: new { @class = 
"control-label col-md-2" })<br />@Html.DropDownListFor(model => 
Model.Games[i].HomePlayer2.Id,
Model.Games[i].HomePlayer2 != null ? new SelectList(ViewBag.players, "Id", 
"UserName", Model.Games[i].HomePlayer2.Id) :
new SelectList(ViewBag.players, "Id", "UserName"), "Please select home 
player 2", new { @class = "form-control" })
                    </td>
                    <td>
                        @Html.LabelFor(model => Model.Games[i].AwayScore1, 
htmlAttributes: new { @class = "control-label col-md-2" })<br 
/>@Html.EditorFor(model => Model.Games[i].AwayScore1, new { htmlAttributes = 
new { @class = "form-control" } })<br />
                        @Html.LabelFor(model => Model.Games[i].AwayScore2, 
htmlAttributes: new { @class = "control-label col-md-2" })<br />@Html.EditorFor(model => Model.Games[i].AwayScore2, new { htmlAttributes = new { @class = "form-control" } })
                    </td>
                    <td>
                        @Html.LabelFor(model => Model.Games[i].HomeScore1, htmlAttributes: new { @class = "control-label col-md-2" })<br />@Html.EditorFor(model => Model.Games[i].HomeScore1, new { htmlAttributes = new { @class = "form-control" } })<br />
                        @Html.LabelFor(model => Model.Games[i].HomeScore2, htmlAttributes: new { @class = "control-label col-md-2" })<br />@Html.EditorFor(model => Model.Games[i].HomeScore2, new { htmlAttributes = new { @class = "form-control" } })
                    </td>
                </tr>
            }
        </tbody>
    </table>


    <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>
}

<div>
@Html.ActionLink("Back to List", "Index")
</div>

@section Scripts {
@Scripts.Render("~/bundles/jqueryval")
}

I have tried some of the things from some of the other posts with no luck, it seems that the ApplicationUser is always being loaded regardless of whether I say to include it or not. It also seems like it always includes the team Id which then includes the players again and on and on. Any help or direction would be greatly appreciated.

Upvotes: 2

Views: 1472

Answers (2)

tsimmons
tsimmons

Reputation: 21

The first three solutions did not work. What I was ultimately forced to do to get a solution was follow along with your side note (at least I think that is what you were getting at). The issue was arising because the includes of the GameType and AppplicationUsers' were trying to create new entities of their respective objects instead of finding the information in the database and setting it to modified. Below is the updated controller code that has gotten it working:

[HttpPost]
    [ValidateAntiForgeryToken]
    public async Task<ActionResult> Edit(EditMatchesViewModel match)
    {

        if (ModelState.IsValid)
        {
            var updatedMatch = db.Matches.Find(match.ID);
            updatedMatch.Date = match.Date;
            updatedMatch.AwayTeam = match.AwayTeam;
            updatedMatch.HomeTeam = match.HomeTeam;
            updatedMatch.Division = match.Division;
            updatedMatch.Games = new List<Game>();

            foreach (var game in match.Games)
            {
                if (game.ID > 0)
                {
                    var updatedGame = db.Games.Find(game.ID);
                    updatedGame.GameType = db.GameType.Find(game.GameType.ID);
                    updatedGame.AwayPlayer1 = db.Users.Find(game.AwayPlayer1.Id);
                    updatedGame.AwayPlayer2 = db.Users.Find(game.AwayPlayer2.Id);
                    updatedGame.HomePlayer1 = db.Users.Find(game.HomePlayer1.Id);
                    updatedGame.HomePlayer2 = db.Users.Find(game.HomePlayer2.Id);
                    updatedGame.AwayScore1 = game.AwayScore1;
                    updatedGame.AwayScore2 = game.AwayScore2;
                    updatedGame.HomeScore1 = game.HomeScore1;
                    updatedGame.HomeScore2 = game.HomeScore2;

                    updatedMatch.Games.Add(updatedGame);
                }
            }
            db.Matches.Attach(updatedMatch);
            db.Entry(updatedMatch).State = EntityState.Modified;
            await db.SaveChangesAsync();

            return RedirectToAction("Index");
        }
        return View(match);
    }

Upvotes: 0

Tetsuya Yamamoto
Tetsuya Yamamoto

Reputation: 24957

The problem occurs in this line because another Match entity set still loaded somewhere in memory with exactly same primary key as Match viewmodel in POST method has, and you can't have more than one entity with same primary key in memory when saving changes:

db.Entry(match).State = EntityState.Modified;

Instead of directly setting Match entity state like that, try using Attach() method before doing SaveChangesAsync():

db.Matches.Attach(match);
await db.SaveChangesAsync();

Or use AsNoTracking() to disable entity tracking for Match in GET action method which just used to retrieve data:

Match matchData = await db.Matches.AsNoTracking()
      .Where(m => m.ID == id)
      .Include(t => t.AwayTeam)
      .Include(t2 => t2.HomeTeam)
      .Include(x => x.Games.Select(g => g.GameType))           
      .FirstOrDefaultAsync();

If both possible solutions above doesn't work, set Match entity state to EntityState.Detached after retrieved query results in GET action method:

if (matchData != null)
{
    context.Entry(matchData).State = EntityState.Detached;
}

As a side note, better to load Match entity by using its primary key in POST action method (with Find() method) or update property values based from existing entity loaded in memory rather than detaching and reattaching it again.

Similar issues:

ASP.NET MVC - Attaching an entity of type 'MODELNAME' failed because another entity of the same type already has the same primary key value

Error attaching entity because of same primary key when trying to save an update

Upvotes: 1

Related Questions