2013-04-30 206 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是已经在数据库]

解决方案:我改变了我的工厂方法,这样没有我的实体是静态的。问题解决了。