【问题标题】:Attaching cached disconnected entities in Entity Framework 4.1 Code First在 Entity Framework 4.1 Code First 中附加缓存的断开连接的实体
【发布时间】:2011-09-28 20:55:13
【问题描述】:

我们有以下内容:带有 UserId 和 User 表的订单。

假设我们要将所有用户对象缓存为断开连接的实体,然后在 ASP.NET 或类似环境的 Web 服务中使用它们。

因为 environmnet 使用 UnitOfWork 和 IOC 框架,我们希望避免任何上下文操作。就像下面的单元测试一样:

  1. 获取断开连接的用户对象
  2. 创建上下文
  3. 创建订单对象
  4. 通过用户对象或 ID 附加用户
  5. 保留整个 Order 对象。

经过两天的疯狂谷歌搜索,我找不到任何解决方案。

问题是:

1.用户对象

如果我们附加用户对象(userId = 1),默认行为EF认为它是一个新的用户对象并试图将新的用户对象持久化到数据库中。这很糟糕,因为 db 已经有 userId == 1。

可能的解决方案是覆盖 DbContext.SaveChanges,尝试确定用户是否“分离”并强制将其附加到上下文。我不知道该怎么做,因为从 SaveChanges 调用扩展方法 EFExtensionMethods.IsAttached 或 EFExtensionMethods.AttachItem 认为 T 是一个对象。

2。 USER_ID

这可行,除非您想在持久化和重新加载整个实体之前访问 Order.User 对象。

除此之外,我们还需要拆分 API,以便仅使用对象的 id(即 Order.UserId)而不是实际对象(Order.User)进行保存。重新加载对象后,我们可以同时使用两者。但我看不到通过 API 实际执行它的方法。

3. USER OBJECT 和 USER_ID

在这种情况下,即使用户被标记为使用 UserId 作为外键,EF 仍会尝试将用户对象保存到上下文中,从而遇到 1 中描述的问题。

看起来我缺少一些(或很多)基本知识,问题是:

  • 您会建议做什么?
  • 是否有从 DbContext.SaveChanges 使 EFExtensionMethods.IsAttached 工作的好方法和通用方法
  • 是否有使 EFExtensionMethods.AttachItem 从 DbContext.SaveChanges 工作的好方法和通用方法

非常感谢任何帮助。

[TestMethod]
public void Creating_Order_With_Both_User_And_Id()
{
    int userCount = CountAll<User>();
    User user;
    using (var db = GetContext()) { user = db.Users.AsNoTracking().First(); }
    using (var db = GetContext())
    {
        var o = CreateOrder();
        o.OrderUser = user;         // attach user entity
        o.UserId = user.UserID;     // attach by id
        db.Orders.Add(o);
        db.SaveChanges();
    }
    int newUserCount = CountAll<User>();
    Assert.IsTrue(userCount == newUserCount, string.Format("Expected {0} got {1}", userCount, newUserCount));
}

上下文和类:

public class User
{
    public User() {}
    public int UserID {get;set;}
    public string UserName {get;set;}
}

public class Order
{
    public Order() { }

    public int OrderID { get; set; }
    public DateTime OrderDate { get; set; }
    public string OrderName { get; set; }

    public int UserId { get; set; }
    public virtual User OrderUser { get; set; }
}

public class OrderConfiguration : EntityTypeConfiguration<Order>
{
    public OrderConfiguration()
    {
        this.ToTable("ORDERS");
        this.Property(x => x.OrderName).HasMaxLength(200);
        this.HasRequired(u => u.OrderUser).WithMany().HasForeignKey(u => u.UserId);
    }
}

public static class EFExtensionMethods
{
    // does not work, when called from DbContext.SaveChanges thinks T is an Object.
    public static bool IsAttached<T>(this PPContext db, T entity) where T: class
    {
        return db.Set<T>().Local.Any(e => e == entity);
    }

    // does not work, when called from DbContext.SaveChanges thinks T is an Object.
    public static void AttachItem<T>(this PPContext db, T entity) where T: class
    {
        db.Set<T>().Attach(entity);
    }
}

public class PPContext : DbContext
{
    public PPContext() : base() { }
    public PPContext(DbConnection connection) : base(connection, true) { }

    protected override void OnModelCreating(DbModelBuilder modelBuilder)
    {
        modelBuilder.Configurations.Add(new OrderConfiguration());
    }

    public override int SaveChanges()
    {
        var modified = ChangeTracker.Entries().Where(e => e.State == EntityState.Modified || e.State == EntityState.Added);
        foreach ( var item in modified )
        {
            ????
        }
    }

    public DbSet<User> Users {get;set;}
}

【问题讨论】:

    标签: .net entity-framework-4.1 ef-code-first


    【解决方案1】:

    我们为用户对象添加了标记接口,并将所有 ICacheableEntity 实体标记为实体。在 SaveChanges 中,我们只是将项目标记为未更改。这样就可以了。

    public class User : ICacheableEntity
    {
        public User() { }
        public int UserID {get;set;}
        public string UserName {get;set;}
    }
    
    class PPContext
    {
        public bool IsSeeding {get;set;}
    
        public override int SaveChanges()
        {
            var modified = ChangeTracker.Entries().Where(e => e.State == EntityState.Modified || e.State == EntityState.Added);
            if (!IsSeeding)
            {
                foreach (var item in modified.Where(item => (item.Entity is ICacheableEntity)))
                {
                    item.State = EntityState.Unchanged;
                }
            }
        }
    }
    

    【讨论】:

    • 所有缓存项都是不可变的吗?既然看起来任何被缓存的东西都不能改变?还是您正在使用 IsSeeding 标志做些什么?
    • 本练习的范围不考虑缓存的不变性。我想得越多,它就越不重要。真的:)
    【解决方案2】:

    您的 TestMethod 中是否缺少 Attach

    // ...
    using (var db = GetContext())
    {
        var o = CreateOrder();
    
        db.Users.Attach(user);
    
        o.OrderUser = user;         // attach user entity
        o.UserId = user.UserID;     // attach by id
        db.Orders.Add(o);
        db.SaveChanges();
    }
    // ...
    

    否则,当您将订单添加到上下文时,EF 会将用户置于Added 状态,并在您调用SaveChanges 时创建新用户。

    【讨论】:

    • 谢谢斯劳马。调用 Attach 正是我试图避免的。我试图看看是否还有其他可能性,例如在 SaveChanges 方法中跟踪所有“添加”实体并将它们强制附加到上下文中。
    • @b0rg:嗯,也许你接受我的回答太快了。我不想说除了打电话给Attach之外别无他法。我不知道你想避免这种情况。也许对于您的SaveChanges 方法中的????,类似于item.State = EntityState.Unchanged; 是可能的。 (但您必须将订单与用户区分开来,因为两者都处于Added 状态。)但我不明白您为什么要避免Attach。对我来说这看起来容易多了。
    • 我还是新手。你的回答是正确的,没有简单的方法。我想为您阅读大量代码的努力+1。避免附加的主要原因,因为在我们的应用程序中它不是订单和用户,而是一些具有 20 多个缓存“字典”对象的大对象,并且每次都附加所有对象有点无聊...... :)
    • 在架构上我倾向于同意你的观点。但我也认为该框架应该允许以简单的(或者我应该说是流利的)方式操作这些“字典”实体,无论是类还是其他枚举,这样人们就不必求助于黑客。另一个原因是,我们所有的上下文都隐藏在 UnitOfWork 后面,有时很难从我们实际将 User 分配给 Order 的业务逻辑层获取它们。
    猜你喜欢
    • 1970-01-01
    • 2012-05-15
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-11-11
    • 2023-04-02
    • 2011-08-01
    • 1970-01-01
    相关资源
    最近更新 更多