C# 如何删除级联底部的实体->;EF中的非级联删除链?
很抱歉这个毫无用处的标题。。。我无法用更好的话来形容这个问题 我有以下实体,每个实体都由C# 如何删除级联底部的实体->;EF中的非级联删除链?,c#,sql,entity-framework,C#,Sql,Entity Framework,很抱歉这个毫无用处的标题。。。我无法用更好的话来形容这个问题 我有以下实体,每个实体都由Id属性标识: 底盘 槽 纸牌 当我使用POCO时,这些实体并不是什么突破性的东西。例如,机箱类的定义如下: public class Chassis { public int Id { get; set; } // Other properties omitted for brevity. public ICollection<Slot> Slots { get;
Id
属性标识:
- 底盘
- 槽
- 纸牌
机箱
类的定义如下:
public class Chassis
{
public int Id { get; set; }
// Other properties omitted for brevity.
public ICollection<Slot> Slots { get; set; }
public Chassis()
{
Slots = new Collection<Slot>();
}
}
。。。但这引发了一个例外:
EntityFramework.dll中出现“System.Data.Entity.Infrastructure.DbUpdateException”类型的异常,但未在用户代码中处理
其他信息:无法插入或更新实体,因为“机箱\ U插槽”关系的主体端已删除
然后我尝试添加ctx.Detach
在我的内部foreach
循环中,要停止EF尝试通过级联槽实体保存已删除的内容:
foreach (var s in slots)
{
if (s.Card != null)
{
ctx.Cards.Remove(s.Card);
}
// Otherwise EF attempts to save the slot, which results in a exception saying the principle
// end of the relatioship Chassis_Slots has already been deleted.
ctx.Detach(s);
}
。。。但是,EF会发出哭声,但以下情况除外:
其他信息:DELETE语句与引用约束“FK_dbo.Slots_dbo.Cards_cardd”冲突。冲突发生在数据库“…”、表“dbo.Slots”、列“cardd”中
声明已终止
。。。这让我在岩石和坚硬的地方之间,完全失去了想法
有人能推荐一种更成功的方法吗?以下是顺序
- 当
被删除时,由于删除级联机制,所有机箱
也将被删除。简单对吧插槽
- 然后拦截保存更改,您还希望在同一事务单上手动删除
,我不确定EF将如何生成sql查询,即使稍后添加了cards deletation语法,但当我使用探查器检查时,它首先删除了卡(应该是机箱优先)李>Card
- 发生的情况是,
(将由未更改的插槽
自动删除)更改为删除级联
修改的插槽
- 为什么??因为当您同时删除
时,相应的卡
必须设置为空插槽的CardId
- 现在,最终结果是
被修改,但插槽
已被删除机箱
TransactionScope
public override int SaveChanges()
{
var deletedCardIds = new List<int>();
var chassises = ChangeTracker.Entries<Chassis>().Where(e => e.State == EntityState.Deleted);
foreach (var chassis in chassises)
{
var cardIds = Slots.Where(s => s.ChassisId == chassis.Entity.Id)
.Where(s => s.CardId.HasValue)
.Select(s => s.CardId.Value)
.ToArray();
deletedCardIds.AddRange(cardIds);
}
int originalRowsAffected;
int additionalRowsAffected;
using (var transaction = new TransactionScope())
{
originalRowsAffected = base.SaveChanges();
deletedCardIds.Distinct().ToList()
.ForEach(id => Entry(new Card { Id = id }).State = EntityState.Deleted);
additionalRowsAffected = base.SaveChanges();
transaction.Complete();
}
return originalRowsAffected + additionalRowsAffected;
}
public override int SaveChanges()
{
var deletedCardIds=新列表();
var chassises=ChangeTracker.Entries()。其中(e=>e.State==EntityState.Deleted);
foreach(底盘中的var底盘)
{
var cardIds=Slots.Where(s=>s.ChassisId==chassis.Entity.Id)
.Where(s=>s.cardd.HasValue)
.Select(s=>s.cardd.Value)
.ToArray();
删除CardCardDS.AddRange(CardDS);
}
内部原始rowsaffected;
int-additionalRowsAffected;
使用(var transaction=new TransactionScope())
{
originalRowsAffected=base.SaveChanges();
deletedCardIds.Distinct().ToList()文件
.ForEach(id=>Entry(新卡{id=id}).State=EntityState.Deleted);
additionalRowsAffected=base.SaveChanges();
transaction.Complete();
}
返回原始rowsaffected+附加rowsaffected;
}
通过在上述更新的代码中引入事务,
SaveChanges
要么全部发生,要么什么都不发生,现在保证了原子性。为什么要删除机箱,然后在额外事件中只删除卡?我相信这是你所有问题的根源。在我的项目中,每当我想删除数据库中没有明确级联关系的父项和子项时,我总是从子项开始,然后毫无问题地删除父项
在删除机箱的代码中,获取所有插槽并遍历它们,然后移除所有卡,就像您当前在事件中所做的那样。如果在删除机箱或插槽之前将卡标记为已删除,则在删除卡时不会将插槽标记为已修改。这将防止您的错误,并允许您在一个事务中删除所有内容
将所有图元标记为在一个点中删除也更为清晰易读。现在,阅读chassis delete的任何人都很容易意识到,它也会删除与之相关的任何卡。当前解决方案中有一些因素使级联删除变得麻烦
卡
实体是自参考的。也就是说,一张卡可以是叶级卡(没有子卡),也可以是聚合子卡的分支级卡。从域的角度来看,我想象一张卡要么没有子卡,要么只有一组子卡。我将使用这个假定的领域知识作为解决方案的一部分。如果有了自由统治,我会把它改造成两个实体:Card
和CardWithChildren
,并将其明确化插槽
实体似乎纯粹是一个面向对象的设备,允许卡
实体属于一个机箱
或另一个卡
。然而,我会从另一个角度来看待这种关系,看看问题中是如何表述的。与插槽可以属于机箱或卡不同,我会将其建模为卡属于插槽,然后在卡上以多态方式表示关系。这可以进一步简化为仅表示一张卡由机箱或另一张卡拥有public override int SaveChanges()
{
var deletedCardIds = new List<int>();
var chassises = ChangeTracker.Entries<Chassis>().Where(e => e.State == EntityState.Deleted);
foreach (var chassis in chassises)
{
var slots = Slots.Where(s => s.ChassisId == chassis.Entity.Id).ToArray();
foreach (var slot in slots)
{
if (slot.CardId.HasValue && !deletedCardIds.Contains(slot.CardId.Value))
{
deletedCardIds.Add(slot.CardId.Value);
}
}
}
// Commits original transaction.
var originalRowsAffected = base.SaveChanges();
int additionalRowsAffected = 0;
if (deletedCardIds.Count > 0)
{
// Opens new transaction.
using (var newContext = new AppContext())
{
foreach (var cardId in deletedCardIds)
{
var deletedCard = newContext.Cards.Find(cardId);
if (deletedCard != null)
{
newContext.Cards.Remove(deletedCard);
}
}
// Commits new transaction.
additionalRowsAffected = newContext.SaveChanges();
}
}
return originalRowsAffected + additionalRowsAffected;
}
begin tran
delete from dbo.Chassis
delete from dbo.Cards
commit tran
public override int SaveChanges()
{
var deletedCardIds = new List<int>();
var chassises = ChangeTracker.Entries<Chassis>().Where(e => e.State == EntityState.Deleted);
foreach (var chassis in chassises)
{
var cardIds = Slots.Where(s => s.ChassisId == chassis.Entity.Id)
.Where(s => s.CardId.HasValue)
.Select(s => s.CardId.Value)
.ToArray();
deletedCardIds.AddRange(cardIds);
}
int originalRowsAffected;
int additionalRowsAffected;
using (var transaction = new TransactionScope())
{
originalRowsAffected = base.SaveChanges();
deletedCardIds.Distinct().ToList()
.ForEach(id => Entry(new Card { Id = id }).State = EntityState.Deleted);
additionalRowsAffected = base.SaveChanges();
transaction.Complete();
}
return originalRowsAffected + additionalRowsAffected;
}
public abstract class CardContainer
{
public int Id { get; set; }
public ICollection<Card> Cards { get; set; }
protected CardContainer()
{
Cards = new List<Card>();
}
}
public class Chassis : CardContainer
{
}
public class Card : CardContainer
{
public CardContainer Container { get; set; }
public int ContainerId { get; set; }
}
public class CardContainerConfiguration : EntityTypeConfiguration<CardContainer>
{
public CardContainerConfiguration()
{
ToTable("CardContainers");
HasKey(k => k.Id);
Property(p => p.Id).HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity);
HasMany(f => f.Cards)
.WithRequired(p => p.Container)
.HasForeignKey(p => p.ContainerId)
.WillCascadeOnDelete(true);
}
}
public class ChassisConfiguration : EntityTypeConfiguration<Chassis>
{
public ChassisConfiguration()
{
ToTable("Chassis");
}
}
public class CardConfiguration : EntityTypeConfiguration<Card>
{
public CardConfiguration()
{
ToTable("Cards");
HasRequired(x => x.Container).WithMany(x => x.Cards).HasForeignKey(x => x.ContainerId);
}
}
public class EfContext : DbContext
{
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Configurations.Add(new CardContainerConfiguration());
modelBuilder.Configurations.Add(new ChassisConfiguration());
modelBuilder.Configurations.Add(new CardConfiguration());
base.OnModelCreating(modelBuilder);
}
}
public void CanCascadeDeleteCards()
{
using (var context = new EfContext())
{
// We have to tell Entity Framework of the maximum depth of the
// card-->card relationship using the Include method
var chassis = context.CardContainers.OfType<Chassis>()
.Include(x => x.Cards.Select(y => y.Cards))
.First();
context.CardContainers.Remove(chassis);
context.SaveChanges();
}
}