Присоединение кэшированных отключенных объектов в Entity Framework 4.1 Code First - PullRequest
3 голосов
/ 05 июля 2011

У нас есть следующее: Порядок с UserId и таблицей User.

Допустим, мы хотим кэшировать все объекты User как отключенные объекты, а затем использовать их в ASP.NET или веб-службах, таких как среда.

Поскольку среда среды использует платформы UnitOfWork и IOC, мы хотели бы избежать каких-либо манипуляций с контекстом.Как и в следующем модульном тесте:

  1. Получить отключенный пользовательский объект
  2. Создать контекст
  3. Создать объект заказа
  4. Присоединить пользователя через пользовательский объект или идентификатор
  5. Сохранить весь объект Порядка.

После двух дней яростного поиска в Google я не смог найти никакого решения.

Проблемы:

1.USER OBJECT

Если мы присоединяем объект пользователя (с userId = 1), при поведении по умолчанию EF думает, что это новый объект пользователя, и пытается сохранить новый объект пользователя в БД,Это дует, потому что db уже имеет userId == 1.

Возможное решение - переопределить DbContext.SaveChanges, попытаться выяснить, «отключен» ли пользователь, и принудительно присоединить его к контексту.Я не мог понять, как это сделать, потому что методы расширения EFExtensionMethods.IsAttached или EFExtensionMethods.AttachItem при вызове из SaveChanges думают, что T является объектом.

2.USER_ID

Это работает, за исключением случаев, когда вы хотите получить доступ к объекту Order.User перед сохранением и перезагрузкой всей сущности.

Поверх этого нам потребуется разделить API, поэтомуэто сохранение только с использованием идентификаторов объектов (т.е. Order.UserId), а не фактический объект (Order.User).После перезагрузки объекта мы можем использовать оба.Но я не вижу способа обеспечить его применение через API.

3.ОБА ОБЪЕКТ ПОЛЬЗОВАТЕЛЯ и USER_ID

В этом сценарии, даже если пользователь помечен как использующий UserId в качестве внешнего ключа, EF все еще пытается сохранить объект User в контексте, решая проблемы, описанные в 1.

Похоже, я что-то упустил (или многое) фундаментальное, и вопросы:

  • Что бы вы порекомендовали сделать?
  • Есть ли хороший и общий способсоздания EFExtensionMethods.IsAttached из DbContext.SaveChanges
  • Есть ли хороший и универсальный способ создания EFExtensionMethods.AttachItem из DbContext.SaveChanges

Любая помощь очень ценится. 1055

[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;}
}

Ответы [ 2 ]

6 голосов
/ 06 июля 2011

Мы добавили маркерный интерфейс к объекту пользователя и пометили все объекты ICacheableEntity как Entity.В 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;
            }
        }
    }
}
1 голос
/ 05 июля 2011

Разве в вашем 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.

...