一言で言えば、ラッパーモデルをPOSTし、1つのエントリの状態を 'Modified'に変更するときに例外がスローされます。状態を変更する前に、状態は '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'メソッドを使用するか、エンティティの状態を 'Unchanged'または 'Modified'に設定すると、これが発生する可能性があります。これは、一部のエンティティが新しく、まだデータベースで生成されたキー値を受け取っていないことが原因である可能性があります。この場合は、「追加」メソッドまたは「追加」エンティティ状態を使用してグラフを追跡してから、必要に応じて非新規エンティティの状態を「未変更」または「変更」に設定します。
誰かが私のコードで何か間違ったものを見たり、モデル編集中にどのような状況でそのようなエラーが発生するのかを理解していますか?
問題が解決しました!
Attach
メソッドは誰かに役立つ可能性がありますが、Edit GETコントローラ関数でロードされている間にドキュメントはすでに追跡されているので、この状況では役に立ちません。 Attachは全く同じエラーを投げます。
ここで私が遭遇した問題は、オブジェクトaの状態を更新する前にAエンティティをロードする関数canUserAccessA()
によって引き起こされました。これは追跡対象のエンティティを台無しにし、オブジェクトの状態を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のような何らかのマッピングツールを使ってドメインモデルにマップし直すことができるviewmodelを受け取るでしょう。これにより、ユーザーが機密データを変更するのを防ぐことができます。
これが詳しい説明です。
私の場合は、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;
}
これを試して:
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;
}
私はもっと早く気付かないことに少しばかげた感じがするけれども、私はこれについて私の経験を分かち合うと思った。
私は私のコントローラに注入されたリポジトリインスタンスでリポジトリパターンを使用しています。具象リポジトリは、リポジトリの存続期間を持続するmyContextContext(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を指定して必要に応じて以前のインスタンスを使用することもできます。
クエリを取得している場所でAsNoTracking()
を使用します。
var result = dbcontext.YourModel.AsNoTracking().Where(x => x.aID == aID && x.UserID==userID).Count();
問題はより複雑なデータパターンに基づいて説明されているため、この回答を追加しましたが、ここでは理解するのが難しいことがわかりました。
私はかなり単純なアプリケーションを作成しました。このエラーはEdit POSTアクション内で発生しました。アクションはViewModelを入力パラメーターとして受け入れました。 ViewModelを使用する理由は、レコードが保存される前に計算を行うためです。
アクションがif(ModelState.IsValid)
のような検証を通過すると、私の間違ったことはViewModelからEntityの全く新しいインスタンスに値を投影することでした。更新されたデータを格納するために新しいインスタンスを作成し、そのようなインスタンスを保存する必要があると思いました。
後で気付いたのは、データベースからレコードを読み取らなければならなかったということです。
Student student = db.Students.Find(s => s.StudentID == ViewModel.StudentID);
そしてこのオブジェクトを更新しました。すべてうまくいった。
私はローカルの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);
同じキーを持つロードされたオブジェクトの問題の原因なので、最初にそのオブジェクトをデタッチし、同じキーを持つ2つのオブジェクト間の競合を避けるために更新を行います。
私は "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 はスペイン語です(2番目の答えを探してください)
ここで私が同じような場合にしたこと。
その状況は、同じエンティティがすでにコンテキスト内に存在していることを意味します。
エンティティがコンテキスト内にあるかどうかを最初に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
}
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();
}
私は状態を更新することによって問題を解決するために管理します。同じレコードに対してfindや他のクエリ操作がmodifiedで更新されたため、statusをDetachedに設定する必要がある場合は、更新の変更を起動できます。
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;
}
この問題は、ViewModel
からEntityModel
へのマッピング中(AutoMapper
などを使用)にも発生し、以下のようにusingブロックのようにcontext.Entry().State
とcontext.SaveChanges()
を含めようとすると問題が解決します。 context.SaveChanges()
メソッドはif-block
の直後に使用するのではなく2回使用されることに注意してください。これはブロックの使用にもあるからです。
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
}
}
}
お役に立てれば...
EFは変更を追跡せず、オブジェクトがアタッチされていない限り変更がないと想定するため、2〜3日の調査後に ".AsNoTracking"を削除する必要があります。 .AsNoTrackingを使用しない場合も、EFは自動的にどのオブジェクトを保存または更新するのかを認識しているので、Attach/Addedを使用する必要はありません。
私はどこでこのエラーが発生しました
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();
}
メソッド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){
using (var db2 = new ApplicationDbContext())
{
Resource resource = db2.Resources.Find(id);
db2.Entry(resource).State = EntityState.Modified;
db2.SaveChanges();
}
return new JsonResult();
}