web-dev-qa-db-ja.com

ASP.NET MVC - 同じタイプの別のエンティティが既に同じ主キー値を持っているため、 'MODELNAME'タイプのエンティティのアタッチに失敗しました

一言で言えば、ラッパーモデルを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'に設定すると、これが発生する可能性があります。これは、一部のエンティティが新しく、まだデータベースで生成されたキー値を受け取っていないことが原因である可能性があります。この場合は、「追加」メソッドまたは「追加」エンティティ状態を使用してグラフを追跡してから、必要に応じて非新規エンティティの状態を「未変更」または「変更」に設定します。

誰かが私のコードで何か間違ったものを見たり、モデル編集中にどのような状況でそのようなエラーが発生するのかを理解していますか?

111
Chris Ciszak

問題が解決しました!

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()と一緒に使用することはできませんでしたが、クエリを変更することで同じことを達成できるので、実際には問題になりません。

これが同様の問題を抱えている人に役立つことを願っています!

141
Chris Ciszak

興味深いことに:

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

それでもあなたが一般的でないならば:

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

問題がスムーズに解決したようです。

95
guneysus

変更しようとしているエンティティは正しく追跡されていないため、編集済みとして認識されず、代わりに追加されたようです。

状態を直接設定する代わりに、次のことを試してください。

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

また、あなたのコードに潜在的なセキュリティ上の脆弱性があることを警告します。ビューモデルでエンティティを直接使用している場合は、送信されたフォームに正しく名前が付けられたフィールドを追加することによって、誰かがエンティティの内容を変更する可能性があります。たとえば、ユーザーが "A.FirstName"という名前の入力ボックスを追加し、エンティティにそのようなフィールドが含まれている場合、ユーザーがアプリケーションの通常の操作で値を変更できなくても、値はviewmodelにバインドされデータベースに保存されます。 。

更新:

前述のセキュリティの脆弱性を乗り越えるために、あなたのドメインモデルをあなたのビューモデルとして公開することは絶対に避けてください。代わりに別のビューモデルを使用してください。それからあなたの行動はあなたがAutoMapperのような何らかのマッピングツールを使ってドメインモデルにマップし直すことができるviewmodelを受け取るでしょう。これにより、ユーザーが機密データを変更するのを防ぐことができます。

これが詳しい説明です。

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

15
Kaspars Ozols

私の場合は、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;
}
10
sephirot

これを試して:

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;
                }
9
add-Naan

私はもっ​​と早く気付かないことに少しばかげた感じがするけれども、私はこれについて私の経験を分かち合うと思った。

私は私のコントローラに注入されたリポジトリインスタンスでリポジトリパターンを使用しています。具象リポジトリは、リポジトリの存続期間を持続する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を指定して必要に応じて以前のインスタンスを使用することもできます。

3
Luke Puplett

クエリを取得している場所でAsNoTracking()を使用します。

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

問題はより複雑なデータパターンに基づいて説明されているため、この回答を追加しましたが、ここでは理解するのが難しいことがわかりました。

私はかなり単純なアプリケーションを作成しました。このエラーはEdit POSTアクション内で発生しました。アクションはViewModelを入力パラメーターとして受け入れました。 ViewModelを使用する理由は、レコードが保存される前に計算を行うためです。

アクションがif(ModelState.IsValid)のような検証を通過すると、私の間違ったことはViewModelからEntityの全く新しいインスタンスに値を投影することでした。更新されたデータを格納するために新しいインスタンスを作成し、そのようなインスタンスを保存する必要があると思いました。

後で気付いたのは、データベースからレコードを読み取らなければならなかったということです。

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

そしてこのオブジェクトを更新しました。すべてうまくいった。

2
Celdor

私はローカルの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つのオブジェクト間の競合を避けるために更新を行います。

1
lvl4fi4

私は "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番目の答えを探してください)

1
Suzume

ここで私が同じような場合にしたこと。

その状況は、同じエンティティがすでにコンテキスト内に存在していることを意味します。

エンティティがコンテキスト内にあるかどうかを最初に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
}
1
erhan355

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();
    }
1
Jared Beach

私は状態を更新することによって問題を解決するために管理します。同じレコードに対して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;
            }
1
Veera Induvasi

この問題は、ViewModelからEntityModelへのマッピング中(AutoMapperなどを使用)にも発生し、以下のようにusingブロックのようにcontext.Entry().Statecontext.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
        }
    }            
}

お役に立てれば...

1
Murat Yıldız

EFは変更を追跡せず、オブジェクトがアタッチされていない限り変更がないと想定するため、2〜3日の調査後に ".AsNoTracking"を削除する必要があります。 .AsNoTrackingを使用しない場合も、EFは自動的にどのオブジェクトを保存または更新するのかを認識しているので、Attach/Addedを使用する必要はありません。

1
Prem Kumar

私はどこでこのエラーが発生しました

  • 1つのコントローラ内の2つのメソッドAとBが、どちらもApplicationDbContextの同じインスタンスを使用していました。and
  • メソッドAがメソッドBと呼ばれる
    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();
    }
0
colbybhearn