使用Entity Framework 5删除主体时处理依赖实体

时间:2013-06-04 14:52:37

标签: entity-framework entity-framework-5

以下是使用EF5 Code-First方法的最简化形式的情况:

public abstract class EntityBase<PK>
{
    public PK ID { get; set; }
}
public class Country : EntityBase<string>
{
    public string Name { get; set; }
}
public class Address : EntityBase<int>
{
    [Required]
    public string CountryID { get; set; }
    public Country Country { get; set; }
    // ... other address properties ...
}

设置AddressCountry之间的一对多关系,没有像这样的级联删除:

modelBuilder.Entity<Address>()
    .HasRequired(a => a.Country)
    .WithMany()
    .HasForeignKey(a => a.CountryID)
    .WillCascadeOnDelete(false);

最后,我有一个带有CRUD方法的通用基础存储库类,它在底层DbContext上调用SaveChanges以原子方式提交数据更改。 E.g:

public class EFRepository<T, PK> : IRepository<T, PK> where T : EntityBase<PK>
{
    //
    // ... other methods ... 
    //
    public virtual void Delete(T instance)
    {
        // ... trigger validations, write to log, etc...
        _dbContext.Set<T>().Remove(instance);
        try
        {
            _dbContext.SaveChanges();
        }
        catch(Exception ex)
        {
            // ... handle the error ...
        }
    }
}

第1部分:

情景:

var countryRepo = new EFRepository<Country>();
var country = countryRepo.Save(new Country() { ID="??", Name="Test Country" });

var addressRepo = new EFRepository<Address>();
var address = addressRepo.Save(new Address() { Country=country });

countryRepo.Delete(country);

由于存在依赖Address,这应该会失败。但是,之后地址在CountryID中以空值结尾,这是无效的,因为需要Address.CountryID,因此后续的SaveChanges调用会抛出验证异常,除非地址被分离。

我预计当一个对象被删除时,EF5会足够智能,首先检查上面的任何级联删除约束,并且找不到任何一个,然后继续删除数据。但事实恰恰相反。

这是正常行为还是我做错了什么?

第2部分:

SaveChanges调用失败后,某些Addresses现在在我的DbContext中处于无效状态,需要恢复为原始值。当然,我总是可以通过创建专门的存储库类并覆盖Country来为每个实体类型(StateOrderDelete等)明确地这样做,但它闻起来很棒。我更愿意编写一些通用代码,以便在SaveChanges调用失败后优雅地恢复相关实体。

它需要询问DbContext以获得实体(例如Country)是主体的所有关系,而不管其类是否定义了依赖实体的导航属性。

E.g。 Country没有Addresses属性,因此我需要以某种方式在DbContext中找到CountryAddress之间一对多关系的定义,并使用它来恢复所有将Addresses与其原始值相关联。

这可能吗?

1 个答案:

答案 0 :(得分:0)

第2部分

中回答我自己的问题

这是我在多对一关系的主要末端删除实体时检查相关家属的方法,以及家属未公开作为导航集合的情况(例如,班级Address Country属性,但类Country没有Addresses个集合。

的DbContext

将以下方法添加到上下文类:

/// <summary>
/// Returns an array of entities tracked by the 
/// context that satisfy the filter criteria.
/// </summary>
public DbEntityEntry[] GetTrackedEntities<T>(
    Expression<Func<DbEntityEntry<T>, bool>> filterCriteria) 
    where T : class
{
    var result = new List<DbEntityEntry>();
    var doesItMatch = filterCriteria.Compile();

    foreach (var entry in this.ChangeTracker.Entries<T>())
    {
        if (doesItMatch(entry))
            result.Add(entry);
    }
    return result.ToArray();
}

存储库

为每个具有某些依赖关系的类创建一个存储库,覆盖Delete方法并使用新的GetTrackedEntities<T>方法获取所有相关的依赖项,并且:或者:

  • 如果代码中可以级联删除,则明确删除它们
  • 如果它们在DB本身中可级联删除,则将它们与上下文分离
  • 如果它们不是级联可删除的,则抛出异常。

后一种情况的例子:

public class EFCountryRepository : 
    EFReadWriteRepository<Country, string>, 
    ICountryRepository 
{
    public override void Delete(Country instance)
    {
        // Allow the Country to be deleted only if there are no dependent entities
        // currently in the context that are NOT cascade-deletable.
        if (
            // are there any Regions in the context that belong to this Country?
            _dbContext.GetTrackedEntities<Region>(e => 
                e.Entity.CountryID == instance.ID || 
                e.Entity.Country == instance).Length > 0  
            ||
            // are there any Addresses in the context that belong to this Country?
            _dbContext.GetTrackedEntities<Address>(e =>
                e.Entity.CountryID == instance.ID || 
                e.Entity.Country == instance).Length > 0
        )
            throw new Exception(String.Format(
                "Country '{0}' is in use and cannot be deleted.", instance.ID));

        base.Delete(instance);
    }
    // ... other methods ...
}

DB本身将进行级联删除的情况示例,因此我们需要做的是将依赖项与上下文分离:

public class EFOrderRepository : 
    EFReadWriteRepository<Order, string>, 
    IOrderRepository 
{
    public override void Delete(Order instance)
    {
        foreach (var orderItem in _dbContext.GetTrackedEntities<OrderItem>(e => 
                e.Entity.OrderID == instance.ID || 
                e.Entity.Order == instance))
        {
            _dbContext.Entry(orderItem).State = System.Data.EntityState.Detached;
        }
        base.Delete(instance);
    }
    // ... other methods ...
}

希望有人会觉得这个解决方案很有帮助。