1. ホーム
  2. データベース

EF Exception Inquiry (エンティティオブジェクトは、IEntityChangeTrackerの複数のインスタンスから参照できません。)...

2022-02-09 03:51:17

今日、以前の古いプロジェクトを改修しているときに、以前の非標準的なEFの書き方に起因するバグが発生しました。例外メッセージは以下のようなものでした。

" 1 つのエンティティ オブジェクトを IEntityChangeTracker の複数のインスタンスで参照することはできません。 ( エンティティオブジェクトは、IEntityChangeTracker の複数のインスタンスからは参照できません。 )"。

のプログラムなので、実は問題を見つけるのは簡単なのです。

同じエンティティを追跡するために異なるDbContextが使用されています。 .

次のデモコードでは、この例外を簡単に発生させることができます。

            using (var dbContext = new ADbContext())
            {var aa = dbContext.ClassA.Where(p => p.Id == 1).FirstOrDefault();
                dbContext.Entry<ClassA>(aa).State = System.Data.Entity.EntityState.Modified;
                EntityState.Modified; using (var db2 = new ADbContext())
                {
                    db2.Entry<ClassA>(aa).State = System.Data.Entity.EntityState.Modified;
                    dbContext.SaveChanges();
                }
                dbContext.SaveChanges();
            }

ただし、ClassA には   ナビゲーション属性  を以下のように設定します。

    public class ClassA
        
    {
        public int Id { get; set; }
        public string guid { get; set; }
        public int? child_id { get; set; }

        [ForeignKey("child_id")]
        public virtual ClassB child { get; set; }
}

ほとんどの場合、よく設計されたシステムは Uow (unit of work) を使って、すべてのリクエストが同じ DbContext を使うようにします (MSDTC を必要とするシステムもあるでしょうが、これは対象外です)。任意に新しい DbContext を作成すると例外が発生しやすいだけでなく、適時に解放できないためにメモリの問題が生じることもあります。

上記の例外を修正するのは簡単です。同じDbContextを使用すればいいのです。

しかし、ここで問題が発生します。

このプロジェクトは、以前の実行でなぜエラーが発生しなかったのでしょうか?

エクスペリエンス


上記と同じプロジェクトを使用し、ナビゲーション属性の 仮想 以下のように、削除します。

    public class ClassA
        
    classA {
        public int Id { get; set; }
        public string guid { get; set; }
        public int? child_id { get; set; }

        [ForeignKey("child_id")]
        public ClassB child { get; set; }
    }

プロジェクトを実行すると、例外がなくなっていることが確認できます。

具体的にはどのような原因なのでしょうか?

EFについて少し知っている人は、EFのナビゲーションプロパティはデフォルトでディファードローディングがオンになっていることを知っています。

知らない人はキーワードで検索してEFの遅延ロードの基礎知識を追加し、英語が得意な人は公式ドキュメントを閲覧してください( アソシエーションとナビゲーションのプロパティ その 関連するエンティティの読み込み )

virtualが削除されたことで、そのナビゲーションプロパティの遅延ロードがオフになり、そして例外が消えました。

この例外は、遅延読み込みが原因ですか?

また、遅延ロードと IEntityChangeTracker との間にはどのような関係があるのでしょうか?

その理由


IEntityChangeTracker はその名の通り、実際にはエンティティ情報を追跡するために使用されますが、遅延読み込みをオフにした後、両方の DbContexes でエンティティが追跡されていても、エラーが報告されないのは不可解です。

EFのディファードローディングがどのように実装されているかを考えてみましょう。

EFはCastleと同様のダイナミックプロキシ技術を使用していますが、同じ欠点(バーチャルとして識別されないメンバーを傍受できない)があります。

EF のソースコードを見ていないし、公式ドキュメントにも詳しく書かれていないので、IEntityChangeTracker が実際には以下のようなインターセプターと同様の機能を担っているのではないかと推測しています。

DbContext が呼び出されると、エンティティに対する動的プロキシが EF によって生成され、データベースにアクセスするリクエストを傍受し、アクセスされたときにナビゲーションプロパティを入力します。

また、動的プロキシは生成後、他のDbContextにアタッチすることができません。

この推測に基づいて、DbContext の動的プロキシをオフにする次のコードを使用してテストすることができます。

            using (var dbContext = new ADbContext())
            {
                dbContext.Configuration.ProxyCreationEnabled = false;
                Where(p => p.Id == 1).FirstOrDefault(). var aa = dbContext.ClassA;
                dbContext.Entry<ClassA>(aa).State = System.Data.Entity.EntityState.Modified;
                EntityState.Modified; using (var db2 = new ADbContext())
                {
                    db2.Entry<ClassA>(aa).State = System.Data.Entity.EntityState.Modified;
                    dbContext.SaveChanges();
                }
                dbContext.SaveChanges();
            }

合格した結果です。

この例外が実際には遅延ロードとは無関係であることを証明するために、動的プロキシをオンにしてから、遅延ロードをオフにします。

            using (var dbContext = new ADbContext())
            {
                dbContext.Configuration.ProxyCreationEnabled = true;
                Configuration.LazyLoadingEnabled = false. dbContext;
                Where(p => p.Id == 1).FirstOrDefault(). var aa = dbContext.ClassA;
                dbContext.Entry<ClassA>(aa).State = System.Data.Entity.EntityState.Modified;
                EntityState.Modified; using (var db2 = new ADbContext())
                {
                    db2.Entry<ClassA>(aa).State = System.Data.Entity.EntityState.Modified;
                    dbContext.SaveChanges();
                }
                dbContext.SaveChanges();
            }

それでも例外が発生します。

例外が発生したのは、遅延ロード機能ではなく、EF の動的プロキシされたオブジェクトが単一の DbContext によってのみ追跡可能であることが証明されました。

もう一つ特筆すべき点は

また、EFは、ナビゲーションプロパティがない場合、エンティティの動的プロキシを生成しない。

取得元:https://www.cnblogs.com/RobotZero/p/6496964.html