以下是使用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 ...
}
设置Address
和Country
之间的一对多关系,没有像这样的级联删除:
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
来为每个实体类型(State
,Order
,Delete
等)明确地这样做,但它闻起来很棒。我更愿意编写一些通用代码,以便在SaveChanges
调用失败后优雅地恢复相关实体。
它需要询问DbContext以获得实体(例如Country
)是主体的所有关系,而不管其类是否定义了依赖实体的导航属性。
E.g。 Country
没有Addresses
属性,因此我需要以某种方式在DbContext中找到Country
和Address
之间一对多关系的定义,并使用它来恢复所有将Addresses
与其原始值相关联。
这可能吗?
答案 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>
方法获取所有相关的依赖项,并且:或者:
后一种情况的例子:
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 ...
}
希望有人会觉得这个解决方案很有帮助。