Entity Framework为何在删除”一对多”关系中的”多”一方对象时不能正常处理?

huangapple go评论68阅读模式
英文:

Why does Entity Framework not handle a 1:Many when deleting the object in the many

问题

以下是你要翻译的代码部分:

所以这是我的EF 1:Many设置

public class Event
{
	public int Id { get; private set; }
	public Interest? Interest { get; set; }
	public int? InterestId { get; set; }
}

public class Interest
{
	public int Id { get; private set; }
	public ICollection<Event>? Events { get; set; }
}

所以一个事件可能会有一个兴趣。而一个兴趣有许多事件。现在当我调用以下代码:

using (var context = new TrackingDbContext(builder.Options))
{
	var interest = context.Interests.Find(pkInterest);
	context.Interests.Remove(interest);
	context.SaveChanges();
}

我得到了以下异常:

Microsoft.Data.SqlClient.SqlException
删除语句与引用约束"FK_Events_Interests_InterestId"冲突。冲突发生在数据库"TestHoweInterest_2",表"dbo.Events",列'InterestId'。

我可以通过在我的DbContext.SaveChangesAsync()重写中添加以下代码来避免这个问题:

var deletedInterests = ChangeTracker.Entries<Interest>()
	.Where(ei => ei.State == EntityState.Deleted)
	.Select(ei => ei.Entity).ToList();

foreach (var item in deletedInterests)
{
	var listEvents = await Events
		.Where(e => e.InterestId == item.Id)
		.ToListAsync();
	foreach (var eventOn in listEvents)
		eventOn.Interest = null;
}

我的问题是,为什么我需要这样做?难道没有一种方法告诉EF,如果删除Event.Interest,那么将该FK设置为null吗

如果没有办法由数据库处理这个问题,上面的代码是处理这个问题的最佳方法吗?请注意,我不希望有一个规则是确保首先在应用程序中将这些值设置为null - 这将导致这项工作重复进行,如果有人忘记了 - 会导致错误。

----------

感谢@StevePy提供的解决方案

public class InterestConfiguration : IEntityTypeConfiguration<Interest>
{
	public void Configure(EntityTypeBuilder<Interest> builder)
	{
		builder.HasMany(i => i.Events)
			.WithOne(e => e.Interest)
			.OnDelete(DeleteBehavior.SetNull);
	}
}
英文:

So here's my EF 1:Many setup

public class Event
{
	public int Id { get; private set; }
	public Interest? Interest { get; set; }
	public int? InterestId { get; set; }
}

public class Interest
{
	public int Id { get; private set; }
	public ICollection&lt;Event&gt;? Events { get; set; }
}

So an Event may have an Interest. And an Interest has many events. Now when I call the following:

using (var context = new TrackingDbContext(builder.Options))
{
	var interest = context.Interests.Find(pkInterest);
	context.Interests.Remove(interest);
	context.SaveChanges();
}

I get the following exception:

Microsoft.Data.SqlClient.SqlException
The DELETE statement conflicted with the REFERENCE constraint &quot;FK_Events_Interests_InterestId&quot;. The conflict occurred in database &quot;TestHoweInterest_2&quot;, table &quot;dbo.Events&quot;, column &#39;InterestId&#39;.

I can avoid it by adding the following code inside my DbContext.SaveChangesAsync() override:

var deletedInterests = ChangeTracker.Entries&lt;Interest&gt;()
	.Where(ei =&gt; ei.State == EntityState.Deleted)
	.Select(ei =&gt; ei.Entity).ToList();

foreach (var item in deletedInterests)
{
	var listEvents = await Events
		.Where(e =&gt; e.InterestId == item.Id)
		.ToListAsync();
	foreach (var eventOn in listEvents)
		eventOn.Interest = null;
}

My question is, why do I need to do this? Isn't there some way to tell EF that if an Event.Interest is being deleted, then null that FK?

And if there is no way to have this handled by the DB, is the code above the best way to address this? Note, I do not want to have a rule of make sure you null those values in the application first - that would cause this work to be repeated and if someone forgot - bug.


Solution thanks to @StevePy below.

public class InterestConfiguration : IEntityTypeConfiguration&lt;Interest&gt;
{
	public void Configure(EntityTypeBuilder&lt;Interest&gt; builder)
	{
		builder.HasMany(i =&gt; i.Events)
			.WithOne(e =&gt; e.Interest)
			.OnDelete(DeleteBehavior.SetNull);
	}
}

答案1

得分: 0

EF可以管理级联行为,或者可以设置为让数据库来管理它(如果支持)。关键在于,要让EF来管理它,DbContext需要知道相关实体,也就是要追踪这些相关实体。

级联行为选项可以在这里找到:https://learn.microsoft.com/en-us/ef/core/saving/cascade-delete

滚动到“影响数据库架构”部分,这里包含了选项的详细信息。通常我使用数据库端的选项,这可能是在删除操作上选择“Cascade”或“SetNull”。在无法引入数据库端行为的情况下,您可以使用“ClientCascade”或“ClientSetNull”来告诉EF来管理级联,但是在处理像一对多这样的聚合根类型实体时,您需要加载完整的聚合根。这意味着您需要急切加载您的聚合:

using (var context = new TrackingDbContext(builder.Options))
{
    var interest = context.Interests
        .Include(x => x.Events)
        .Single(x => x.Id == pkInterest);

    context.Interests.Remove(interest);
    context.SaveChanges();
}

所以简而言之,如果您使用数据库端行为配置级联,您的级联删除或设置为空应该可以正常工作,而无需急切加载关系。如果您想利用EF执行级联操作,那么请使用Include来急切加载关系。

这也是不要使用Find方法的一个原因,因为它不提供急切加载聚合根关系的选项。

英文:

EF can manage cascading behaviour, or it can be set to let the database manage it. (if supported) The catch is that for EF to manage it, the DbContext needs to know about, as in, be tracking the related entities.

Cascade behaviour options can be found here: https://learn.microsoft.com/en-us/ef/core/saving/cascade-delete

Scroll down to "Impact on database schema" for the meat of the options. Typically I use the database-side options which would be "Cascade" or "SetNull" on the delete behaviour. "SetNull" looks like what you would want. In situations where you cannot introduce the database-side behaviour, you can use the "ClientCascade" or "ClientSetNull" to tell EF to manage the cascade, however when dealing with an aggregate root type entity like a one to many where you are deleting the "One" and want a flow-on effect to the "Many", you need to load the complete aggregate root. This means, eager load your aggregate:

using (var context = new TrackingDbContext(builder.Options))
{
    var interest = context.Interests
        .Include(x =&gt; x.Events)
        .Single(x =&gt; x.Id == pkInterest);

    context.Interests.Remove(interest);
    context.SaveChanges();
}

So in a nutshell, if you configure the cascade with DB-Side behavior your cascade delete or #null should work without needing to eager load the relationships. If you want to leverage EF to perform the cascade then eager-load the relationships with Include.

This is one reason to just never bother with the Find method as this provides no option to eager load aggregate root relationships.

huangapple
  • 本文由 发表于 2023年6月6日 04:20:29
  • 转载请务必保留本文链接:https://go.coder-hub.com/76409749.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定