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

时间:2014-04-21 16:58:46

标签: 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; }
        }   

控制器

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

如上图所示

db.Entry(aViewModel.a).State = EntityState.Modified;

抛出异常:

  

附加类型为'A'的实体失败,因为该实体的另一个实体   相同类型已具有相同的主键值。这可能发生在   使用'Attach'方法或将实体的状态设置为   如果图中的任何实体具有“未更改”或“已修改”   冲突的关键值。这可能是因为一些实体是新的和   尚未收到数据库生成的键值。在这种情况下使用   “添加”方法或“已添加”实体状态可跟踪图表和   然后将非新实体的状态设置为“未更改”或“已修改”为   合适的。

是否有人在我的代码中发现任何错误或了解在编辑模型时会在什么情况下抛出此类错误?

21 个答案:

答案 0 :(得分:140)

问题解决了!

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

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

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

答案 1 :(得分:94)

有趣的是:

_dbContext.Set<T>().AddOrUpdate(entityToBeUpdatedWithId);

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

_dbContext.Set<UserEntity>().AddOrUpdate(entityToBeUpdatedWithId);

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

答案 2 :(得分:15)

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

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

//db.Entry(aViewModel.a).State = EntityState.Modified;
db.As.Attach(aViewModel.a); 
db.SaveChanges();

另外,我想提醒您,您的代码包含潜在的安全漏洞。如果您在视图模型中直接使用实体,则可能会有人通过在提交的表单中添加正确命名的字段来修改实体的内容。例如,如果用户添加了名为“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/

答案 3 :(得分:10)

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

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

示例(摘要)代码:

MVC

public ActionResult(A a)
{
  A aa = repo.Find(...);
  // some logic
  repo.Detach(aa);
  repo.Update(a);
}

存储库

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

void Detach(A a)
{
   context.Entry(a).EntityState = EntityState.Detached;
}

答案 4 :(得分:9)

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

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

答案 5 :(得分:9)

试试这个:

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;

答案 6 :(得分:3)

我以为我会分享我在这个方面的经验,尽管我觉得有点傻到没有早日实现。

我正在使用存储库模式,并将repo实例注入到我的控制器中。具体的存储库实例化我的ModelContext(DbContext),它持续存储库的生命周期,即IDisposable并由控制器处理。

对我来说,问题是我的实体上有一个修改过的邮票和行版本,所以我首先得到它们以便与入站标题进行比较。当然,这会加载并跟踪随后更新的实体。

修复只是将存储库从构造函数中的一次上下文更改为具有以下方法:

    private DbContext GetDbContext()
    {
        return this.GetDbContext(false);
    }


    protected virtual DbContext GetDbContext(bool canUseCachedContext)
    {
        if (_dbContext != null)
        {
            if (canUseCachedContext)
            {
                return _dbContext;
            }
            else
            {
                _dbContext.Dispose();
            }
        }

        _dbContext = new ModelContext();

        return _dbContext;
    }

    #region IDisposable Members

    public void Dispose()
    {
        this.Dispose(true);
    }

    protected virtual void Dispose(bool isDisposing)
    {
        if (!_isDisposed)
        {
            if (isDisposing)
            {
                // Clear down managed resources.

                if (_dbContext != null)
                    _dbContext.Dispose();
            }

            _isDisposed = true;
        }
    }

    #endregion

这允许存储库方法在每次使用时通过调用GetDbContext重新创建其上下文实例,或者如果他们希望通过指定true则使用先前的实例。

答案 7 :(得分:2)

我之所以添加这个答案只是因为问题是基于更复杂的数据模式来解释的,我发现这里很难理解。

我创建了一个相当简单的应用程序。 Edit POST操作中发生此错误。该操作接受ViewModel作为输入参数。使用ViewModel的原因是在保存记录之前进行一些计算。

一旦操作通过if(ModelState.IsValid)等验证,我的错误就是将ViewModel中的值投影到一个全新的Entity实例中。我以为我必须创建一个新实例来存储更新的数据,然后保存这样的实例。

我后来意识到我必须从数据库中读取记录:

Student student = db.Students.Find(s => s.StudentID == ViewModel.StudentID);

并更新了这个对象。现在一切都有效。

答案 8 :(得分:1)

我有本地var的这个问题,我只是将它分开:

if (ModelState.IsValid)
{
    var old = db.Channel.Find(channel.Id);
    if (Request.Files.Count > 0)
    {
        HttpPostedFileBase objFiles = Request.Files[0];
        using (var binaryReader = new BinaryReader(objFiles.InputStream))
        {
            channel.GateImage = binaryReader.ReadBytes(objFiles.ContentLength);
        }

    }
    else
        channel.GateImage = old.GateImage;
    var cat = db.Category.Find(CatID);
    if (cat != null)
        channel.Category = cat;
    db.Entry(old).State = EntityState.Detached; // just added this line
    db.Entry(channel).State = EntityState.Modified;
    await db.SaveChangesAsync();
    return RedirectToAction("Index");
}
return View(channel);

带有相同Key的加载对象的问题原因,首先我们将分离该对象并进行更新以避免两个具有相同Key的对象之间发生冲突

答案 9 :(得分:1)

您可以使用类似的添加方法;

_dbContext.Entry(modelclassname).State = EntityState.Added;

,但是在很多情况下,如果您当时想使用多个模型,则该方法将无效,因为实体已经连接到另一个实体。因此,那时您可以使用ADDOrUpdate实体迁移方法,该方法只是将对象从一个对象迁移到另一个对象,因此不会出现任何错误。

_dbContext.Set<modelclassname>().AddOrUpdate(yourmodel);

答案 10 :(得分:1)

在获取查询的地方使用AsNoTracking()

  var result = dbcontext.YourModel.AsNoTracking().Where(x => x.aID == aID && x.UserID==userID).Count();

答案 11 :(得分:1)

我通过“使用”块解决了这个问题

using (SqlConnection conn = new SqlConnection(connectionString))

    {

       // stuff to do with data base
    }

    // or if you are using entity framework 
    using (DataBaseEntity data = new DataBaseEntity)
{

    }

在这里,我得到https://social.msdn.microsoft.com/Forums/sqlserver/es-ES/b4b350ba-b0d5-464d-8656-8c117d55b2af/problema-al-modificar-en-entity-framework?forum=vcses是西班牙语的想法(寻找第二个答案)

答案 12 :(得分:1)

我有一个类似的问题,经过探索2-3天后发现&#34; .AsNoTracking&#34;应该删除,因为EF不跟踪更改并假设除非附加了对象,否则没有更改。此外,如果我们不使用.AsNoTracking,EF会自动知道要保存/更新的对象,因此无需使用附加/添加。

答案 13 :(得分:1)

我通过更新状态来解决问题。当您在同一记录上触发查找或任何其他查询操作时,状态已更新为已修改,因此我们需要将状态设置为已分离,然后您可以触发更新更改

     ActivityEntity activity = new ActivityEntity();
      activity.name="vv";
    activity.ID = 22 ; //sample id
   var savedActivity = context.Activities.Find(22);

            if (savedActivity!=null)
            {
                context.Entry(savedActivity).State = EntityState.Detached;
                context.SaveChanges();

                activity.age= savedActivity.age;
                activity.marks= savedActivity.marks; 

                context.Entry(activity).State = EntityState.Modified;
                context.SaveChanges();
                return activity.ID;
            }

答案 14 :(得分:1)

这是我在类似案例中所做的。

这种情绪化意味着相同的实体已经存在于上下文中。以下可以提供帮助

如果实体位于上下文中,请首先检查ChangeTracker

var trackedEntries=GetContext().ChangeTracker.Entries<YourEntityType>().ToList();

var isAlreadyTracked =
                    trackedEntries.Any(trackedItem => trackedItem.Entity.Id ==myEntityToSave.Id);

如果存在

  if (isAlreadyTracked)
            {
                myEntityToSave= trackedEntries.First(trackedItem => trackedItem.Entity.Id == myEntityToSave.Id).Entity;
            } 

else
{
//Attach or Modify depending on your needs
}

答案 15 :(得分:1)

ViewModelEntityModel映射期间(使用AutoMapper等)并尝试包含context.Entry().Statecontext.SaveChanges()这样的问题也可能会出现此问题如下所示的使用块可以解决问题。请注意,context.SaveChanges()方法使用了两次,而不是仅在if-block之后使用,因为它也必须使用块。

public void Save(YourEntity entity)
{
    if (entity.Id == 0)
    {
        context.YourEntity.Add(entity);
        context.SaveChanges();
    }
    else
    {
        using (var context = new YourDbContext())
        {
            context.Entry(entity).State = EntityState.Modified;
            context.SaveChanges(); //Must be in using block
        }
    }            
}

希望这会有所帮助......

答案 16 :(得分:1)

类似于Luke Puplett的说法,问题可能是因为没有妥善处理或创建您的上下文。

就我而言,我有一个类接受了一个名为ContextService的上下文:

public class ContextService : IDisposable
{
    private Context _context;

    public void Dispose()
    {
        _context.Dispose();
    }
    public ContextService(Context context)
    {
        _context = context;
    }
//... do stuff with the context

我的上下文服务有一个函数,它使用实例化的实体对象更新实体:

        public void UpdateEntity(MyEntity myEntity, ICollection<int> ids)
        {
            var item = _context.Entry(myEntity);
            item.State = EntityState.Modified;
            item.Collection(x => x.RelatedEntities).Load();
            myEntity.RelatedEntities.Clear();
            foreach (var id in ids)
            {
                myEntity.RelatedEntities.Add(_context.RelatedEntities.Find(id));
            }
            _context.SaveChanges();
        }

所有这一切都很好,我初始化服务的控制器就是问题所在。我的控制器最初看起来像这样:

    private static NotificationService _service = 
        new NotificationService(new NotificationContext());
    public void Dispose()
    {
    }

我将其更改为此错误消失了:

    private static NotificationService _service;
    public TemplateController()
    {
        _service = new NotificationService(new NotificationContext());
    }
    public void Dispose()
    {
        _service.Dispose();
    }

答案 17 :(得分:0)

我在

处遇到此错误
  • 单个控制器中的两个方法A和B都使用同一ApplicationDbContext实例,
  • 方法A称为方法B
Clipboard c = Toolkit.getDefaultToolkit().getSystemClipboard();

Transferable aaa = c.getContents(null);

String secondText = (String)aaa.getTransferData(DataFlavor.stringFlavor);

我将方法B更改为具有using语句,并且仅依赖于本地 db2 。 之后:

    private ApplicationDbContext db;
    // api methods
    public JsonResult methodA(string id){
        Resource resource = db.Resources.Find(g);
        db.Entry(resource).State = EntityState.Modified;
        db.SaveChanges();
        return methodB()
    }

    public JsonResult methodB(string id){
        Resource resource = db.Resources.Find(id);
        db.Entry(resource).State = EntityState.Modified;
        db.SaveChanges();
        return new JsonResult();
    }

答案 18 :(得分:0)

清除所有状态

dbContextGlobalERP.ChangeTracker.Entries()。Where(e => e.Entity!= null).ToList()。ForEach(e => e.State = EntityState.Detached);

答案 19 :(得分:0)

原因,我遇到了此错误:

  1. 查询现有实体时未使用.AsNoTracking()。尤其是在调用辅助函数以检查权限时。
  2. 在查询中调用.Include(),然后尝试编辑父级。示例:var ent = repo.Query<Ent>().Include(e=>e.Ent2).First(); ...repo.Edit(e.Ent2); repo.Edit(e);如果要编辑嵌套对象,请立即尝试将它们分成单独的查询调用。如果您不能这样做,请将子对象设置为null并遍历列表,分离this
  3. 之类的对象
  4. Put网络通话中编辑旧实体。新项目已添加到存储库中,因此请修改该项目并将其保存在super.Put()中。引发错误的示例:public void Put(key, newItem){ var old = repo.Query<Entity>().Where(e=>Id==key).First(); ... repo.Edit(old); super.Put(key,newItem); ... }
  5. 多个帮助器功能可编辑同一实体。不要将ID作为参数传递给每个函数,而应将引用传递给实体。错误已解决!

答案 20 :(得分:0)

就我而言,我确实写过两次相同类型的实体。所以我删除了它,一切正常工作