3

(EF4.1 - 4.0 フレームワーク)

Web 上のコード例のほとんどは、Entity Framework のベスト プラクティスを示しています。ステートレスな操作を保証するために、DBContext の使用を using ブロックでラップすると彼らは言います。それでも、共有キャッシュ エラーと思われるものが表示されます。

エラー

同じキーを持つオブジェクトが ObjectStateManager に既に存在します。ObjectStateManager は、同じキーを持つ複数のオブジェクトを追跡できません。

周りを見回すと、この例は、誰かが多くの呼び出しで DBContext のグローバル インスタンスを共有している場合に発生します。

ただし、静的データアクセス層サービスクラスにある次の関数への2回目の呼び出しでこれを受け取ります。

public static void UpdateRollout(Rollout rollout)
        {

               using (ITAMEFContext db = new ITAMEFContext(ConnectionStrings.XYZConnectionString))
                {
                    db.Configuration.ProxyCreationEnabled = false;
                    db.Configuration.LazyLoadingEnabled = false;

                    FixUp(rollout);


                    db.Rollouts.Attach(rollout);
                    db.Entry(rollout).State = System.Data.EntityState.Modified;

                    db.SaveChanges();

                    //db.Entry(rollout).State = System.Data.EntityState.Detached;

                }

}



private static void FixUp(Rollout rollout)
        {
            // ensure manual fixup of foreign keys
            if (rollout.RolloutState != null)
                rollout.FK_RolloutState_ID = rollout.RolloutState.ID;
            if (rollout.Lead != null)
                rollout.RolloutLead_FK_User_ID = rollout.Lead.ID;
        }

EFContext は、edmx モデルを参照する EF 4.x DBContext Fluent Generator によって生成されました。

edmx モデル画像

このように見えます。

public partial class ITAMEFContext : DbContext
{
    static ITAMEFContext()
    {
        Database.SetInitializer<ITAMEFContext>(null);
    }

    public ITAMEFContext() : base("name=ITAMEFContext")
    {
        this.Configuration.LazyLoadingEnabled = false;

    }

    public ITAMEFContext(string nameOrConnectionString) : base(nameOrConnectionString)
    {

    }

    public ITAMEFContext(string nameOrConnectionString, DbCompiledModel model) : base(nameOrConnectionString, model)
    {

    }

    public ITAMEFContext(DbConnection existingConnection, bool contextOwnsConnection) : base(existingConnection, contextOwnsConnection)
    {

    }

    public ITAMEFContext(DbConnection existingConnection, DbCompiledModel model, bool contextOwnsConnection) : base(existingConnection, model, contextOwnsConnection)
    {

    }
    protected override void OnModelCreating(DbModelBuilder modelBuilder)
    {
        modelBuilder.Conventions.Remove<IncludeMetadataConvention>();
        modelBuilder.Configurations.Add(new Asset_Mapping());
        modelBuilder.Configurations.Add(new AssetAllocation_Mapping());
        modelBuilder.Configurations.Add(new AssetAssignee_Mapping());
        modelBuilder.Configurations.Add(new AssetAssigneeType_Mapping());
        modelBuilder.Configurations.Add(new AssetDeAllocation_Mapping());
        modelBuilder.Configurations.Add(new AssetState_Mapping());
        modelBuilder.Configurations.Add(new AssetType_Mapping());
        modelBuilder.Configurations.Add(new Department_Mapping());
        modelBuilder.Configurations.Add(new Location_Mapping());
        modelBuilder.Configurations.Add(new ManagementGroup_Mapping());
        modelBuilder.Configurations.Add(new Role_Mapping());
        modelBuilder.Configurations.Add(new Rollout_Mapping());
        modelBuilder.Configurations.Add(new RolloutState_Mapping());
        modelBuilder.Configurations.Add(new ServiceArea_Mapping());
        modelBuilder.Configurations.Add(new Software_Mapping());
        modelBuilder.Configurations.Add(new SoftwareType_Mapping());
        modelBuilder.Configurations.Add(new SubTeam_Mapping());
        modelBuilder.Configurations.Add(new sys_UserLock_Mapping());
        modelBuilder.Configurations.Add(new Team_Mapping());
        modelBuilder.Configurations.Add(new User_Mapping());
        modelBuilder.Configurations.Add(new WorkingMethod_Mapping());
    }

    public DbSet<Asset> Assets { get; set; }
    public DbSet<AssetAllocation> AssetAllocations { get; set; }
    public DbSet<AssetAssignee> AssetAssignees { get; set; }
    public DbSet<AssetAssigneeType> AssetAssigneeTypes { get; set; }
    public DbSet<AssetDeAllocation> AssetDeAllocations { get; set; }
    public DbSet<AssetState> AssetStates { get; set; }
    public DbSet<AssetType> AssetTypes { get; set; }
    public DbSet<Location> Locations { get; set; }
    public DbSet<Department> Departments { get; set; }
    public DbSet<ManagementGroup> ManagementGroup { get; set; }
    public DbSet<Role> Roles { get; set; }
    public DbSet<ServiceArea> ServiceAreas { get; set; }
    public DbSet<SubTeam> SubTeams { get; set; }
    public DbSet<Team> Teams { get; set; }
    public DbSet<User> User { get; set; }
    public DbSet<WorkingMethod> WorkingMethods { get; set; }
    public DbSet<Rollout> Rollouts { get; set; }
    public DbSet<RolloutState> RolloutStates { get; set; }
    public DbSet<Software> Softwares { get; set; }
    public DbSet<SoftwareType> SoftwareTypes { get; set; }
    public DbSet<sys_UserLock> sys_UserLock { get; set; }
}

BL レイヤーから必要な回数だけ UpdateRollout を呼び出せるようにしたいと考えています。UI は、以前に取得したリストの一部として返される POCO ロールアウト エンティティ グラフを保持する必要があります。

ロールアウトと他のすべてのエンティティは純粋な POCO であり、コンテキスト トラッキングは必要ありません。

using ブロックが IAMEFContext を破棄すると、コンテキストのキャッシング/追跡がすべて消去されることを読みました。しかし、同じアプリケーション ドメイン内の DBContext のインスタンスの下に、ある種のグローバル キャッシュがあるように見えますか?? 正直に言うと、今のところ EF は、レイヤード アプリに古き良きストアド プロシージャを使用するよりも手間がかかるようです。

ポコ。

public partial class Rollout
{
    public Rollout()
    {
        this.AssetAssignees = new HashSet<AssetAssignee>();
    }

    public int ID { get; set; }
    public string Name { get; set; }
    public int RolloutLead_FK_User_ID { get; set; }
    public string EmailContacts { get; set; }
    public System.DateTime Schedule { get; set; }
    public int FK_RolloutState_ID { get; set; }
    public Nullable<int> NotificationDays { get; set; }
    public string Notes { get; set; }

    public virtual ICollection<AssetAssignee> AssetAssignees { get; set; }
    public virtual User Lead { get; set; }
    public virtual RolloutState RolloutState { get; set; }
}

編集:

マッピング。

 internal partial class Rollout_Mapping : EntityTypeConfiguration<Rollout>
{
    public Rollout_Mapping()
    {                   
        this.HasKey(t => t.ID);     
        this.ToTable("Rollout");
        this.Property(t => t.ID).HasColumnName("ID");
        this.Property(t => t.Name).HasColumnName("Name").IsRequired().HasMaxLength(50);
        this.Property(t => t.RolloutLead_FK_User_ID).HasColumnName("RolloutLead_FK_User_ID");
        this.Property(t => t.EmailContacts).HasColumnName("EmailContacts").HasMaxLength(500);
        this.Property(t => t.Schedule).HasColumnName("Schedule");
        this.Property(t => t.FK_RolloutState_ID).HasColumnName("FK_RolloutState_ID");
        this.Property(t => t.NotificationDays).HasColumnName("NotificationDays");
        this.Property(t => t.Notes).HasColumnName("Notes");
        this.HasRequired(t => t.Lead).WithMany(t => t.Rollouts).HasForeignKey(d => d.RolloutLead_FK_User_ID);
        this.HasRequired(t => t.RolloutState).WithMany(t => t.Rollouts).HasForeignKey(d => d.FK_RolloutState_ID);
    }
}
4

2 に答える 2

0

私は非常によく似た問題に遭遇し、あなたと同じように、ある種のグローバル キャッシングが問題の原因であると考えました。

私のユースケースはこれでした:

  1. 新しい DbContext を使用して、データベースにいくつかのテスト データを設定し、DbContet を破棄します
  2. アプリケーションでシステム テストを実行する
  3. データベースをベースライン状態にリセットします (私はこれを EF の外で行っていました)
  4. 次のシステム テストでは、手順 1 から繰り返します。

最初のテストではすべて問題なく動作しましたが、2 回目のテストでは重複キー エラーが発生しました。

これは、テスト データエンティティの一部を構築するために使用していたファクトリ メソッドがそれらを静的オブジェクトとして作成していることに気付くまで、しばらく困惑していました。2 回目のループでは、これらの静的エンティティをコンテキストに追加するとすぐに、これらのエンティティからの完全なオブジェクト グラフが再度追加されたので、後で他のエンティティを追加しようとすると、それらは既に存在していました。

簡単な例を次に示します...

ループ 1:

  1. オブジェクト A (静的) を作成します。変更を保存 [データベースには現在 A が含まれています]
  2. オブジェクト A と関係を持つオブジェクト B (静的ではない) を作成します。変更を保存します [データベースには現在 A と B が含まれています]
  3. データベースをリセット [データベースには何も含まれていません]

ループ 2:

  1. オブジェクト A を作成します (静的であるため、実際には再作成されません。データベースに存在しない場合でも、B への参照が含まれています)。変更を保存 [データベースには現在 A と B が含まれています]
  2. オブジェクト B を作成します (静的ではありません)。変更内容を保存。[ブーム!B はすでにデータベースにあるため、キーが重複しています]

解決策:ファクトリ メソッドを変更して、エンティティが静的にならないようにしました。問題が解決しました。

于 2013-07-02T18:11:50.373 に答える
-1

編集 - 答えを書き直しました。2点。

1: EF DbContext の寿命に関する記事を見つけました (ObjectContext を参照していますが、同じ規則が適用されます): http://blogs.msdn.com/b/alexj/archive/2009/05/07/tip-18-how -あなたのオブジェクトコンテキストの生涯を決定する.aspx

DbContext はスレッドセーフではないことに注意してください。静的メソッドを使用しているため、スレッド化の問題が発生している可能性があります。静的クラスではなく、必要な場所に DbContext を作成することをお勧めします。

2: 理想的には、DbCntext の同じインスタンスで読み取りと書き込みを行います。「切断」とは、エンティティを操作している間エンティティがメモリ内にあり、DbContext が行った変更を追跡していることを意味します。

このようなアプローチ (疑似コード) を使用します。

 public class RolloutManager {
   ...
   // If you just update state and you have no input from somewhere else, you can just  
   // read and write in the same method
   public void UpdateRolloutState() {
       using( var db = new MyDBContext() {
            var stuffToUpdate = db.Rollouts.Where(....).ToList();
            foreach(var stuff in StuffToUpdate){
                stuff.PropertyToUpdate = ....;
            }
            db.SaveChanges();
        }
   }

   // If you have inputs, pass them in (using a different object normally, such as a wcf 
   //contract or viewmodel), read them up from the db, update the db entities and save
   public void UpdateRolloutState(IEnumerable<InputRollout> stuffToUpdate) {
       using( var db = new MyDBContext() {
            foreach(var stuff in StuffToUpdate){
                var dbRollout = db.Rollouts.Find(stuff.Id);
                // copy properties you want to update
            }
            db.SaveChanges();
        }
   }

これが解決策ではないかもしれませんが、解決策を見つけるためのヒントになるかもしれません。

于 2013-04-30T20:23:38.910 に答える