ASP.NET MVC - 附加类型为“MODELNAME”的实体失败,因为相同类型的另一个实体已具有相同的主键值
本文关键字:实体 另一个 同类型 NET 因为 键值 失败 类型 MODELNAME ASP MVC | 更新日期: 2023-09-27 17:59:17
简而言之,在 POSTing 包装器模型并将一个条目的状态更改为"已修改"期间会引发异常。在更改状态之前,状态设置为"已分离",但调用 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
方法可能会帮助某人,但在这种情况下无济于事,因为在编辑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()
一起使用,但这并不重要,因为我可以通过更改查询来实现相同的效果。
希望这对任何有类似问题的人都有帮助!
有趣的是:
_dbContext.Set<T>().AddOrUpdate(entityToBeUpdatedWithId);
或者,如果您仍然不是通用的:
_dbContext.Set<UserEntity>().AddOrUpdate(entityToBeUpdatedWithId);
似乎顺利解决了我的问题。
您尝试修改的实体似乎未被正确跟踪,因此未被识别为已编辑,而是添加。
尝试执行以下操作,而不是直接设置状态:
//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/
试试这个:
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;
,本地副本是问题的根源。这解决了它
var local = context.Set<Contact>().Local.FirstOrDefault(c => c.ContactId == contact.ContactId);
if (local != null)
{
context.Entry(local).State = EntityState.Detached;
}
我的情况是我无法从我的 MVC 应用程序直接访问 EF 上下文。
因此,如果您使用某种存储库进行实体持久性,则只需分离显式加载的实体,然后将绑定的实体状态设置为"已修改"可能是合适的。
示例(抽象(代码:
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;
}
在获取查询的位置使用AsNoTracking()
。
var result = dbcontext.YourModel.AsNoTracking().Where(x => x.aID == aID && x.UserID==userID).Count();
我添加这个答案只是因为这个问题是基于更复杂的数据模式来解释的,我发现这里很难理解。
我创建了一个相当简单的应用程序。此错误发生在编辑开机自检操作中。该操作接受视图模型作为输入参数。使用 ViewModel 的原因是在保存记录之前进行一些计算。
一旦操作通过验证(例如if(ModelState.IsValid)
(,我的错误行为是将 ViewModel 中的值投影到实体的全新实例中。我以为我必须创建一个新实例来存储更新的数据,然后保存该实例。
后来意识到,我必须从数据库中读取记录:
Student student = db.Students.Find(s => s.StudentID == ViewModel.StudentID);
并更新了此对象。现在一切正常。
我想我会分享我的经验,尽管我觉得没有早点意识到有点愚蠢。
我正在使用存储库模式,并将存储库实例注入到我的控制器中。具体的存储库实例化了我的模型上下文 (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 来使用以前的实例。
我在使用本地 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 的两个对象之间的冲突
我设法通过更新状态来解决问题。 当您在同一记录上触发查找或任何其他查询操作时,sate 已更新为修改,因此我们需要将状态设置为已分离,然后您可以触发更新更改
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;
}
在探测 2-3 天后,我遇到了类似的问题。应删除 AsNoTracking",因为 EF 不会跟踪更改,并假定除非附加对象,否则没有更改。另外,如果我们不使用.与否跟踪一样,EF 会自动知道要保存/更新哪个对象,因此无需使用附加/添加。
我遇到了这个错误
- 单个控制器中的两种方法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();
}
与 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();
}
这是我在类似情况下所做的。
该位置意味着上下文中已经存在相同的实体。所以以下会有所帮助
首先从更改跟踪器检查实体是否在上下文中
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
}
我用"using"块解决了这个问题
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(寻找第二个答案(
您可以使用添加的方法,例如;
_dbContext.Entry(modelclassname).State = EntityState.Added;
但在许多情况下,如果您想在当时使用多个模型,这将不起作用,因为实体已经附加到另一个实体。因此,此时您可以使用ADDOrUpdate实体迁移方法,该方法只需将对象从一个迁移到另一个对象,因此您不会收到任何错误。
_dbContext.Set<modelclassname>().AddOrUpdate(yourmodel);
清除所有状态
dbContextGlobalERP.ChangeTracker.Entries((.其中(e => e.Entity != null(。ToList((。ForEach(e => e.State = EntityState.Detached(;
我遇到此错误的原因:
- 查询现有实体时未使用
.AsNoTracking()
。尤其是在调用帮助程序函数来检查权限时。 - 对查询调用
.Include()
,然后尝试编辑父级。示例:var ent = repo.Query<Ent>().Include(e=>e.Ent2).First(); ...repo.Edit(e.Ent2); repo.Edit(e);
如果要编辑嵌套对象,我现在尝试将它们分成单独的查询调用。如果无法做到这一点,请将子对象设置为 null 并循环访问列表,像这样分离对象 - 在
Put
Web 调用中编辑旧实体。新项已添加到存储库中,因此请修改该项并将其保存在super.Put()
中。将引发错误的示例:public void Put(key, newItem){ var old = repo.Query<Entity>().Where(e=>Id==key).First(); ... repo.Edit(old); super.Put(key,newItem); ... }
- 多个帮助程序函数编辑同一实体。不要将 ID 作为参数传递到每个函数中,而是传递对实体的引用。错误已解决!
就我而言,我已经写了两次相同类型的实体。 所以我删除了它,所有事情都可以正常工作
这个问题也可能在ViewModel
EntityModel
映射(通过使用AutoMapper
等(期间看到,并且尝试包含context.Entry().State
并context.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
}
}
}