附加类型为“X”的实体失败,因为同一类型的另一个实体已具有相同的主键值。错误

时间:2021-12-31 21:32:23

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:

您好我对MVC编程很新,并且遇到了一个相当奇怪的错误,我似乎无法解决这个问题。我正在创建一个MVC 5应用程序,当我尝试使用来自我的控制器的编辑选项时,父实体和子实体似乎都正确加载,但是当我尝试将更改保存回来时,我收到错误“附加实体类型'X.Models.ApplicationUser'失败,因为同一类型的另一个实体已经具有相同的主键值“。我有以下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.

我从其他一些帖子中尝试了一些没有运气的东西,似乎ApplicationUser总是被加载,无论我是否说要包括它。它似乎总是包括团队ID,然后再次包括玩家和不断。任何帮助或方向将不胜感激。

2 个解决方案

#1


0  

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:

此问题发生在此行中,因为另一个Match实体集仍然在内存中的某个位置加载与POST方法中的Match viewmodel完全相同的主键,并且在保存更改时,内存中不能有多个具有相同主键的实体:

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

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

不要像这样直接设置Match实体状态,在执行SaveChangesAsync()之前尝试使用Attach()方法:

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:

或者使用AsNoTracking()来禁用仅用于检索数据的GET中的匹配操作方法的实体跟踪:

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:

如果上述两种可能的解决方案都不起作用,请在GET操作方法中检索到的查询结果后将Match实体状态设置为EntityState.Detached:

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.

作为旁注,最好通过在POST操作方法中使用其主键来加载Match实体(使用Find()方法),或者根据在内存中加载的现有实体更新属性值,而不是再次分离和重新附加它。

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

ASP.NET MVC - 附加“MODELNAME”类型的实体失败,因为同一类型的另一个实体已具有相同的主键值

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

尝试保存更新时,由于相同的主键而附加实体时出错

#2


0  

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:

前三个解决方案不起作用。我最终*做出的解决方案是跟随你的旁注(至少我认为这就是你所得到的)。之所以出现这个问题,是因为GameType和AppplicationUsers的包含试图创建各自对象的新实体,而不是在数据库中查找信息并将其设置为修改。下面是更新后的控制器代码:

[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);
    }

#1


0  

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:

此问题发生在此行中,因为另一个Match实体集仍然在内存中的某个位置加载与POST方法中的Match viewmodel完全相同的主键,并且在保存更改时,内存中不能有多个具有相同主键的实体:

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

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

不要像这样直接设置Match实体状态,在执行SaveChangesAsync()之前尝试使用Attach()方法:

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:

或者使用AsNoTracking()来禁用仅用于检索数据的GET中的匹配操作方法的实体跟踪:

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:

如果上述两种可能的解决方案都不起作用,请在GET操作方法中检索到的查询结果后将Match实体状态设置为EntityState.Detached:

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.

作为旁注,最好通过在POST操作方法中使用其主键来加载Match实体(使用Find()方法),或者根据在内存中加载的现有实体更新属性值,而不是再次分离和重新附加它。

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

ASP.NET MVC - 附加“MODELNAME”类型的实体失败,因为同一类型的另一个实体已具有相同的主键值

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

尝试保存更新时,由于相同的主键而附加实体时出错

#2


0  

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:

前三个解决方案不起作用。我最终*做出的解决方案是跟随你的旁注(至少我认为这就是你所得到的)。之所以出现这个问题,是因为GameType和AppplicationUsers的包含试图创建各自对象的新实体,而不是在数据库中查找信息并将其设置为修改。下面是更新后的控制器代码:

[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);
    }