0

下面是使用 EF5 代码优先方法的最简化形式的情况:

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 ...
}

Address和之间的一对多关系Country是在没有级联删除的情况下设置的,如下所示:

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

最后,我有一个通用的基础存储库类,其中包含调用SaveChanges底层 DbContext 以原子方式提交数据更改的 CRUD 方法。例如:

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。但是,之后地址以 null in 结束CountryID,这是无效的,因为Address.CountryID它是必需的,因此后续SaveChanges调用会引发验证异常,除非地址被分离。

我希望当一个对象被删除时,EF5 将足够聪明地首先检查任何像上面那样的级联删除约束,如果找不到任何约束,然后继续删除数据。但情况似乎恰恰相反。

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

第2部分:

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

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

egCountry没有Addresses属性,所以我需要以某种方式在 DbContext 中找到 and 之间的一对多关系的定义,CountryAddress使用它来恢复Addresses与其原始值相关的所有内容。

这可能吗?

4

1 回答 1

0

在第 2 部分回答我自己的问题:

这是我在删除多对一关系的主体端的实体时检查相关依赖项的方法,并且依赖项不作为主体中的导航集合公开(例如,类Address具有Country属性,但类Country没有有一个Addresses集合)。

数据库上下文

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

/// <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>方法获取所有相关依赖项,并且:

  • 如果它们在代码中可级联删除,则显式删除它们
  • 如果它们在数据库本身中是可级联删除的,则将它们从上下文中分离出来
  • 如果它们不可级联删除,则抛出异常。

后一种情况的示例:

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 ...
}

级联删除将由数据库本身完成的示例示例,因此我们需要做的就是将依赖项从上下文中分离出来:

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 ...
}

希望有人会发现此解决方案有帮助。

于 2013-06-04T21:31:58.263 回答