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

Chr*_*zak 118 c# asp.net-mvc entity-framework

简而言之,在POSTing包装器模型期间抛出异常并将一个条目的状态更改为"已修改".在更改状态之前,状态设置为'Detached'但调用Attach()会产生相同的错误.我正在使用EF6.

请在下面找到我的代码(模型名称已更改,以便于阅读)

模型

// Wrapper classes
        public class AViewModel
        {
            public A a { get; set; }
            public List<B> b { get; set; }
            public C c { get; set; }
        }   
Run Code Online (Sandbox Code Playgroud)

调节器

        public ActionResult Edit(int? id)
        {
            if (id == null)
            {
                return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
            }

            if (!canUserAccessA(id.Value))
                return new HttpStatusCodeResult(HttpStatusCode.Forbidden);

            var aViewModel = new AViewModel();
            aViewModel.A = db.As.Find(id);

            if (aViewModel.Receipt == null)
            {
                return HttpNotFound();
            }

            aViewModel.b = db.Bs.Where(x => x.aID == id.Value).ToList();
            aViewModel.Vendor = db.Cs.Where(x => x.cID == aViewModel.a.cID).FirstOrDefault();

            return View(aViewModel);
        }

[HttpPost]
        [ValidateAntiForgeryToken]
        public ActionResult Edit(AViewModel aViewModel)
        {
            if (!canUserAccessA(aViewModel.a.aID) || aViewModel.a.UserID != WebSecurity.GetUserId(User.Identity.Name))
                return new HttpStatusCodeResult(HttpStatusCode.Forbidden);

            if (ModelState.IsValid)
            {
                db.Entry(aViewModel.a).State = EntityState.Modified; //THIS IS WHERE THE ERROR IS BEING THROWN
                db.SaveChanges();
                return RedirectToAction("Index");
            }
            return View(aViewModel);
        }
Run Code Online (Sandbox Code Playgroud)

如上图所示

db.Entry(aViewModel.a).State = EntityState.Modified;
Run Code Online (Sandbox Code Playgroud)

抛出异常:

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

有没有人在我的代码中看到任何错误或了解在编辑模型期间会在什么情况下抛出这样的错误?

Chr*_*zak 145

问题解决了!

Attach方法可能有助于某人,但在这种情况下无法帮助,因为在编辑GET控制器功能中加载文档时已经跟踪了该文档.Attach会抛出完全相同的错误.

我在这里遇到的问题是由canUserAccessA()在更新对象a的状态之前加载A实体的函数引起的.这搞砸了被跟踪的实体,它正在改变一个对象的状态Detached.

解决方案是修改,canUserAccessA()以便不加载我加载的对象.AsNoTracking()查询上下文时应调用函数.

// User -> Receipt validation
private bool canUserAccessA(int aID)
{
    int userID = WebSecurity.GetUserId(User.Identity.Name);
    int aFound = db.Model.AsNoTracking().Where(x => x.aID == aID && x.UserID==userID).Count();

    return (aFound > 0); //if aFound > 0, then return true, else return false.
}
Run Code Online (Sandbox Code Playgroud)

出于某种原因,我无法使用.Find(aID),AsNoTracking()但它并不重要,因为我可以通过更改查询来实现相同的目的.

希望这能帮助任何有类似问题的人!

  • 注意:你需要`使用System.Data.Entity;`来使用`AsNoTracking()`. (11认同)
  • 稍微整洁,性能更高:if(db.As.AsNoTracking().Any(x => x.aID == aID && x.UserID == userID)) (10认同)
  • 大量帮助.我在我的FirstOrDefault()之前添加了.AsNoTracking()并且它有效. (3认同)

gun*_*sus 102

有趣的是:

_dbContext.Set<T>().AddOrUpdate(entityToBeUpdatedWithId);
Run Code Online (Sandbox Code Playgroud)

或者如果你仍然不是通用的:

_dbContext.Set<UserEntity>().AddOrUpdate(entityToBeUpdatedWithId);
Run Code Online (Sandbox Code Playgroud)

似乎顺利解决了我的问题.

  • 对于任何人来说,`AddOrUpdate`是`System.Data.Entity.Migrations`命名空间中的扩展方法. (49认同)
  • 这有效.关于附加和使用notracking的所有其他建议都失败了,因为我已经做了noTracking.谢谢你的解决方案. (4认同)
  • 这对我有用,同时更新**同一工作单元内的**父母和子女**实体**.非常感谢 (2认同)

Kas*_*ols 15

您尝试修改的实体似乎未被正确跟踪,因此未被识别为已编辑,而是添加.

尝试执行以下操作,而不是直接设置状态:

//db.Entry(aViewModel.a).State = EntityState.Modified;
db.As.Attach(aViewModel.a); 
db.SaveChanges();
Run Code Online (Sandbox Code Playgroud)

另外,我想提醒您,您的代码包含潜在的安全漏洞.如果您在视图模型中直接使用实体,则可能会有人通过在提交的表单中添加正确命名的字段来修改实体的内容.例如,如果用户添加了名为"A.FirstName"的输入框并且实体包含此类字段,则该值将绑定到viewmodel并保存到数据库,即使在正常的应用程序操作中不允许用户更改该值也是如此.

更新:

为了克服前面提到的安全漏洞,您不应该将域模型公开为viewmodel,而是使用单独的viewmodel.然后你的动作会收到viewmodel,你可以使用像AutoMapper这样的映射工具将其映射回域模型.这样可以避免用户修改敏感数据.

这是扩展说明:

http://www.stevefenton.co.uk/Content/Blog/Date/201303/Blog/Why-You-Never-Expose-Your-Domain-Model-As-Your-MVC-Model/

  • 嗨Kaspars,谢谢你的意见.Attach方法抛出与我的问题中提到的相同的错误.问题是canUserAccessA()函数加载实体以及上面提到的CodeCaster.但是说我对你的安全问题非常感兴趣.你能建议我该怎样做才能防止这种行为? (3认同)

小智 11

试试这个:

var local = yourDbContext.Set<YourModel>()
                         .Local
                         .FirstOrDefault(f => f.Id == yourModel.Id);
if (local != null)
{
  yourDbContext.Entry(local).State = EntityState.Detached;
}
yourDbContext.Entry(applicationModel).State = EntityState.Modified;
Run Code Online (Sandbox Code Playgroud)


小智 10

我的情况是我没有从我的MVC应用程序直接访问EF上下文.

因此,如果您使用某种存储库来实现实体持久性,那么可以适当地简单地分离显式加载的实体,然后将绑定的EntityState设置为Modified.

示例(摘要)代码:

MVC

public ActionResult(A a)
{
  A aa = repo.Find(...);
  // some logic
  repo.Detach(aa);
  repo.Update(a);
}
Run Code Online (Sandbox Code Playgroud)

知识库

void Update(A a)
{
   context.Entry(a).EntityState = EntityState.Modified;
   context.SaveChanges();
}

void Detach(A a)
{
   context.Entry(a).EntityState = EntityState.Detached;
}
Run Code Online (Sandbox Code Playgroud)


小智 9

对我来说,本地副本是问题的根源.这解决了它

var local = context.Set<Contact>().Local.FirstOrDefault(c => c.ContactId == contact.ContactId);
                if (local != null)
                {
                    context.Entry(local).State = EntityState.Detached;
                }
Run Code Online (Sandbox Code Playgroud)


Abd*_*zad 7

使用AsNoTracking()您收到查询的位置。

  var result = dbcontext.YourModel.AsNoTracking().Where(x => x.aID == aID && x.UserID==userID).Count();
Run Code Online (Sandbox Code Playgroud)