2013-04-30 457 views
2

(EF4.1 - 4.0框架)實體框架DBContext全局緩存?

Web上的大多數代碼示例都指示了實體框架的最佳實踐;他們說你在一個使用塊中包裝你的DBContext的用法,以確保無狀態操作。即使如此,我得到了似乎是共享緩存錯誤。

ERROR

具有相同鍵的對象已經存在於ObjectStateManager。 ObjectStateManager無法使用相同的 鍵追蹤多個對象。

環顧四周,當有人在許多調用中共享一個DBContext的全局實例時,會發生這種情況。

但是,我在第二次調用下面的函數時收到了這個問題,它位於靜態數據訪問層服務類中。

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 model picture

看起來是這樣。

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,並且不需要上下文跟蹤。

我讀過,任何上下文緩存/跟蹤被刪除,一旦使用塊處理ITAMEFContext。然而,它似乎有某種全局緩存在相同的應用程序域中的DBContext的任何實例下?我必須誠實地說,到目前爲止EF似乎比使用舊的存儲過程分層的應用程序更多的工作。

POCO。

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); 
    } 
} 
+0

在附加之前檢查「EntityState」的展開情況。如果已附加,請先將其分離(如果將導航屬性設置爲已跟蹤實體,則會自動添加新實體)。 – VahidN 2013-07-02 18:46:22

回答

-1

編輯 - 我改寫了我的答案。兩點。

1:我發現這篇文章關於EF的DbContext續航時間(它指的是ObjectContext的,但同樣的規則也適用): http://blogs.msdn.com/b/alexj/archive/2009/05/07/tip-18-how-to-decide-on-a-lifetime-for-your-objectcontext.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(); 
     } 
    } 

我希望這可以幫助 - 它可能不是解決辦法,但它可能會指向你找到一個。

+0

但是,你如何解釋第一次調用'UpdateRollout'運行良好?你的評論「這個對象可能已經存在了」讓核心問題無法解釋。此外,Find會通過附加的方式繞過OP嘗試阻止的數據庫。並且'rollout'本身的任何更改都不會保存。 -1,對不起。 – 2013-04-30 23:29:56

+0

如上所述。我完全理解我是否使用了相同的上下文,因爲您必須檢查它是否已被緩存並更新屬性或使用Find,但每次調用此函數都會導致上下文被丟棄。因此,第二次電話應該是一個新的背景。這就是暗示在DBContext的工作背後隱藏着一些隱藏的行爲。無論DBContext被處理多少次,它都必須以某種方式保持狀態,這要求開發人員發現這種情況,並且與一些人建議的方式完全不同。 – Terry 2013-05-01 08:39:32

+0

@Gert Arnold:我舉了一個例子,說明它是如何正常完成的。您不需要自己附加對象並自行設置狀態,您可以讓Entity Framework執行此操作。這可能不是什麼原因導致的問題,但我會先從標準方式開始,然後嘗試弄清楚爲什麼在我以非標準方式使用它時不起作用。就Find()而言,只有在數據庫尚未存在的情況下才會發送到數據庫。 – gabnaim 2013-05-01 14:15:05

0

我遇到了一個非常類似的問題,和你一樣,我認爲這是導致問題的某種全局緩存。

我的使用情況是這樣的:

  1. 使用新的DbContext,建立在我的數據庫中的一些測試數據然後清除DbContet
  2. 在我的應用程序
  3. 運行系統測試重置數據庫基線狀態(I在做該EF的外側)從步驟1
  4. 重複用於下一系統測試

Everyt興第一次測試運行良好,但在第二次測試中,我得到了重複鍵錯誤。

這讓我難住了一段時間,直到我意識到我用來構建我的一些測試數據的工廠方法實體正在將它們創建爲靜態對象;第二次通過循環,只要我將這些靜態實體添加到上下文中,這些實體的完整對象圖就會重新添加,所以當我後來添加其他實體時,它們已經在那裏。

下面是一個簡化的例子...

循環1:

  1. 創建對象A(靜態)。保存更改[數據庫現在包含A]
  2. 創建一個與對象A.保存更改關係對象B(不是靜態的)數據庫現在包含A和B]
  3. 復位數據庫[數據庫現在包含任何]

循環2:

  1. 創建對象A(。靜態的,所以實際上沒有重新創建仍然包含參考到B即使這不是在數據庫中)。保存更改[數據庫現在包含A和B]
  2. 創建對象B(非靜態)。保存更改。 [繁榮!重複鍵,因爲B是已經在數據庫]

解決方案:我改變了我的工廠方法,這樣沒有我的實體是靜態的。問題解決了。