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

简而言之,异常是在 POSTing 包装器模型期间抛出的,并将一个条目的状态更改为“ Amendment”。在更改状态之前,状态被设置为“ 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”的实体失败,因为 相同的类型已经有相同的主键值 使用“附加”方法或将实体的状态设置为 “未更改”或“修改”,如果图中的任何实体有 这可能是因为一些实体是新的和 还没有收到数据库生成的键值 “添加”方法或“添加”实体状态来跟踪图形和 然后将非新实体的状态设置为“未更改”或“修改”为 合适。

有没有人看到我的代码中有什么错误,或者知道在什么情况下编辑模型时会抛出这样的错误?

183856 次浏览

您试图修改的实体似乎没有被正确跟踪,因此不能识别为已编辑,而是添加了。

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

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

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

更新:

为了克服前面提到的安全漏洞,您不应该将域模型作为视图模型公开,而应该使用单独的视图模型。然后,您的操作将接收视图模型,您可以使用类似 AutoMapper 的映射工具将其映射回域模型。这样可以防止用户修改敏感数据。

下面是详细的解释:

Http://www.stevefenton.co.uk/content/blog/date/201303/blog/why-you-never-expose-your-domain-model-as-your-mvc-model/

问题解决了!

Attach方法可能对某些人有帮助,但是在这种情况下没有帮助,因为在 Edit GET 控制器函数中加载文档时已经被跟踪了。附加将抛出完全相同的错误。

我在这里遇到的问题是由函数 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(),但它真的没有关系,因为我可以通过更改查询来实现同样的目标。

希望这将帮助任何人与类似的问题!

我想我应该分享一下我的经验,尽管我觉得没有早点意识到这一点有点傻。

我将使用注入到控制器中的回购实例的存储库模式。具体的存储库实例化我的 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 使用以前的实例。

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

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

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

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

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

更新了这个对象,现在一切正常。

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

因此,如果您正在使用某种类型的存储库来实体持久化,那么可以简单地分离显式加载的实体,然后将绑定的 EntityState 设置为 Amendment。

示例(摘要)代码:

车祸

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

有趣的是:

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

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

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

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

对我来说,本地拷贝是问题的根源。 这就解决了

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

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

这个问题也可能出现在 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
}
}
}

希望这个能帮上忙。

这里我做了类似的情况。

这种情况意味着同一个实体已经存在于上下文中

首先从 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
}

我和本地 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 的两个对象之间的冲突

我设法通过更新状态来修复问题。当您触发查找或同一记录状态上的任何其他查询操作已被更新和修改,所以我们需要设置状态为分离,然后您可以激发您的更新更改

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

试试这个:

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;

我也有类似的问题,经过2-3天的调查发现。”。因为 EF 不跟踪变化,并且假设没有变化,除非附加了一个对象,所以应该删除“ AsNoTrack”。还有,如果我们不用。在“无跟踪”中,EF 自动知道要保存/更新哪个对象,因此不需要使用“附加/添加”。

我用一个“使用”块来解决这个问题

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是在西班牙语(寻找第二个答案)

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

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

我在哪里遇到了这个错误

  • 单个控制器中的两个方法 A 和 B 都使用了 ApplicationDbContext 的相同实例 还有
  • 方法 A 称为方法 B
    private ApplicationDbContext db;
// api methods
public JsonResult methodA(string id){
Resource resource = db.Resources.Find(id);
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();
}

我更改了方法 B,使其具有 using 语句,并且仅依赖于本地 Db2。 之后:

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


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

你可以使用添加的方法如;

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

但是在许多情况下,如果您想在同一时间使用多个模型,这将无法工作,因为实体已经附加到另一个实体。因此,在那个时候,您可以使用 ADDOrUpdate 实体迁移方法,它只是将对象从一个迁移到另一个,因此您不会得到任何错误。

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

清空所有州

ChangeTracker。条目()。其中(e = > e. Entity! = null)。 ToList ()。 ForEach (e = > e. State = EntityState. Detached) ;

我遇到这个错误的原因:

  1. 在查询现有实体时没有使用 .AsNoTracking(),尤其是在调用辅助函数检查权限时。
  2. 对查询调用 .Include(),然后尝试编辑父级。示例: var ent = repo.Query<Ent>().Include(e=>e.Ent2).First(); ...repo.Edit(e.Ent2); repo.Edit(e);如果我要编辑一个嵌套对象,我现在尝试将它们分成单独的查询调用。如果做不到这一点,将子对象设置为 null 并在列表中迭代,从而分离像 这个这样的对象
  3. 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); ... }
  4. 多个辅助函数编辑相同的实体。不要将 ID 作为参数传递给每个函数,而是传递对实体的引用。错误解决!

在我的例子中,我实际上写了两次相同类型的实体。所以我删了它,一切都正常了