3

(EF4.1 - 4.0 框架)

Web 上的大多数代码示例都规定了 Entity Framework 的最佳实践;他们说将您对 DBContext 的使用包装在 using 块中,以确保无状态操作。即便如此,我还是得到了似乎是共享缓存错误。

错误

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 模型图片

看起来像这样。

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 Rollout 实体图。

Rollout 和所有其他实体都是纯 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);
    }
}
4

2 回答 2

0

我遇到了一个非常相似的问题,和你一样,我认为是某种全局缓存导致了这个问题。

我的用例是这样的:

  1. 使用新的 DbContext,在我的数据库上设置一些测试数据,然后处理 DbContet
  2. 在我的应用程序上运行系统测试
  3. 将数据库重置为基线状态(我在 EF 之外执行此操作)
  4. 从步骤 1 开始重复下一个系统测试

第一次测试一切正常,但在第二次测试中,我得到了重复键错误。

这让我困惑了一段时间,直到我意识到我用来构建一些测试数据实体的工厂方法正在将它们创建为静态对象;第二次通过循环,只要我将这些静态实体添加到上下文中,这些实体的完整对象图就会重新添加,所以当我后来添加其他实体时,它们已经在那里了。

这是一个简化的例子......

循环 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

编辑 - 我已经重写了我的答案。两点。

1:我发现这篇关于 EF DbContext 生命周期的文章(它指的是 ObjectContext,但同样的规则适用):http: //blogs.msdn.com/b/alexj/archive/2009/05/07/tip-18-how -to-decide-on-a-life-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();
        }
   }

我希望这会有所帮助——它可能不是解决方案,但它可能会指引你找到一个。

于 2013-04-30T20:23:38.910 回答