附加“Quiz.DAL.Answer”类型的实体失败,因为同一类型的另一个实体已具有相同的主键值

时间:2017-09-27 18:29:59

标签: c# asp.net asp.net-mvc entity-framework

我有问题,我无法解决。这就是场景。 我有两个模型,第一个是问题,第二个是答案。此外,我有DAL,BusinessEntities(视图模型)和BusinessServices类库与UnitOfWork和GenericRepository模式。 问题与编辑动作有关,当我编辑(更新)一个模型时它工作但当我尝试更新有3个答案作为列表属性的问题模型我得到这个例外

//异常开始 附加“Quiz.DAL.Answer”类型的实体失败,因为同一类型的另一个实体已具有相同的主键值。如果图中的任何实体具有冲突的键值,则在使用“附加”方法或将实体的状态设置为“未更改”或“已修改”时,可能会发生这种情况。这可能是因为某些实体是新的并且尚未收到数据库生成的键值。在这种情况下,使用“添加”方法或“已添加”实体状态来跟踪图形,然后根据需要将非新实体的状态设置为“未更改”或“已修改”。 //例外结束

GenericRepository类中发生

异常。

if(BUILD_SHARED_LIBS)
  install (
      FILES           "$<TARGET_PDB_FILE:${library}>"
      DESTINATION     "${GDCM_INSTALL_BIN_DIR}"
      COMPONENT       DebugDevel
      CONFIGURATIONS  Debug RelWithDebInfo
  )
endif()

我已经检查过是否所有数据都是从一个视图发布到另一个动作的。

这是从编辑操作

调用的方法
public class GenericRepository<TEntity> where TEntity : class
{
    internal QuizContext Context;
    internal DbSet<TEntity> DbSet;

    /// <summary>
    /// Public Constructor,initializes privately declared local variables.
    /// </summary>
    /// <param name="context"></param>
    public GenericRepository(QuizContext context)
    {
        this.Context = context;
        this.DbSet = context.Set<TEntity>();
    }
    public virtual void Update(TEntity entityToUpdate)
    {
        DbSet.Attach(entityToUpdate); //exception is invoked here
        Context.Entry(entityToUpdate).State = EntityState.Modified;
    } 
 }

编辑视图

public class QuestionServices : IQuestionServices
{
    private UnitOfWork _unitOfWork;
    public void UpdateQuestion(QuestionEntity questionEntity)
    {
        var questionDb = new Question
        {
            Id = questionEntity.Id,
            Name = questionEntity.Name
        };

        var answersDb = _unitOfWork.AnswerRepository.GetMany(a => 
        a.QuestionId == questionDb.Id).ToList();
        foreach (var a in questionEntity.Answers)
        {
            answersDb.Add(new Answer()
            {
                Id = a.Id,
                Name = a.Name,
                IsTrue = a.IsTrue,
                QuestionId = a.QuestionId,
                Question = questionDb
            });
        }

        unitOfWork.QuestionRepository.Update(questionDb);
        foreach (var answer in answersDb)
        {
            _unitOfWork.AnswerRepository.Update(answer);
        }
        _unitOfWork.Save();
}

answerEntity的编辑器模板

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

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

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

        @for (int i=0; i<Model.Answers.Count; i++)
        {
            @Html.EditorFor(m=>m.Answers[i])
        }

        <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 class="form-horizontal"/>
@Html.HiddenFor(model=>model.Id)
@Html.HiddenFor(model=>model.QuestionId)
@Html.ValidationSummary(true, "", new { @class = "text-danger" })
<div class="form-group">
    @Html.LabelFor(model => model.Name, htmlAttributes: new { @class = "control-label col-md-2" })
    <div class="col-md-10">
        @Html.EditorFor(model => model.Name, new { htmlAttributes = new { @class = "form-control" } })
        @Html.ValidationMessageFor(model => model.Name, "", new { @class = "text-danger" })
    </div>
</div>
<div class="form-group">
    @Html.LabelFor(model => model.IsTrue, htmlAttributes: new { @class = "control-label col-md-2" })
    <div class="col-md-10">
        <div class="checkbox">
            @Html.EditorFor(model => model.IsTrue)
            @Html.ValidationMessageFor(model => model.IsTrue, "", new { @class = "text-danger" })
        </div>
    </div>
</div>

我相信这个错误正在弹出因为我必须告诉我,我想更新答案,但是ef认为他们是新的,他们应该创建不更新女巫导致相同的主键错误,或者, 我在内存中有一个以上的答案副本,这让人感到困惑。

感谢任何帮助。

EDIT https://github.com/StefanIvovic/Quiz检查出来

1 个答案:

答案 0 :(得分:2)

这根本没有任何违法行为,因为老实说,这可能不是你的错。那里有太多糟糕的信息。尽管如此,这段代码绝对是悲剧性的。你有三个主要问题。

  1. Don't use Bind。像往常一样。停下来。马上。昨天,如果可能的话。如果您需要排除发布的属性使用视图模型,它基本上只是一个专门为视图定制的类,它只包含视图所需的内容。在这种情况下,这看起来像:

    public class QuestionViewModel
    {
        public string Name { get; set; }
        public List<Answer> Answers { get; set; }
    }
    

    请注意:1)Id不包括在内。您永远不会允许发布ID。 id应该来自URI,因为它是什么使它成为&#34;通用资源标识符&#34;。 2)Answer同样应该在这里使用视图模型,所以你的属性应该最像是List<AnswerViewModel>,但我并不想让你压得太多。

  2. 永远不会永远将帖子创建的任何内容直接保存到您的数据库中。这实际上是通过使用视图模型来解决的,因为您必须保存与发布内容不同的内容。但是,您应始终将发布数据映射到要保存的实体。这在进行编辑时更为重要,因为您应始终从数据库中提取实体,修改该实例,然后保存该实例。通过这种方式,即使您不使用视图模型,您仍然不需要Bind,因为除非您明确允许,否则用户无法真正获取他们发布的任何内容以保存到数据库中,通过将已发布的属性映射到您要保存的实体属性。

  3. 最后,工作模式的存储库/单元与像Entity Framework这样的ORM完全是多余的。 EF实际上已经实现了这些模式:DbContext是您的工作单元,每个DbSet都是一个存储库。在此之上添加一个层除了混淆正在发生的事情之外什么也没做什么,从而使您的应用程序更难以维护。我听过一百万和一个借口为什么人们认为你仍然应该实施这些模式,但他们都被误导了(&#34;如果没有它,你就无法进行测试!&#34;错误.EF是100%可测试的。)或者通过不同的模式(例如Command Query Responsibility Segregation (CQRS)Service Layer Pattern)更好地解决。