How to properly control cascading deletion in EF Core using Fluent API?

c# ef-core-2.0 entity-framework entity-framework-core

Question

I have two entities, Tag and Member. A member can be marked with multiple tags. A tag can be used to mark multiple members. It's a clear case of many-to-many relation and since I'm using EF Core, I have to declare an explicit connector, which I call Tag_Member. I configure it in the following way.

private void OnModelCreating(EntityTypeBuilder<Tag_Member> entity)
{
  entity.HasKey(e => new { e.TagId, e.MemberId });
  entity.HasOne(e => e.Tag);
  entity.HasOne(e => e.Member)
    .WithMany(e => e.Tag_Member)
    .HasForeignKey(e => e.MemberId);
}

The behavior I wish to enforce when deleting is as follows.

  • When removing an instance of Tag_Member, nothing is changed.
  • When removing an instance of Tag, any connected instances of Tag_Member are deleted.
  • When removing an instance of Member, any connected instances of Tag_Member are deleted.

I'm confused on two points. When I add the condition for deletion as shown below, I have a lot of options to pick from and, despite reading the intellisense, I don't feel certain which to use to enforce the above behavior.

entity.HasOne(e => e.Member)
  .WithMany(e => e.Tag_Member)
  .HasForeignKey(e => e.MemberId)
  .OnDelete(DeleteBehavior.NoAction);

Should I use NoAction, ClientNoAction, Restrict or someting else? I'm not even clear on which of hte entities that the deletetion behavior affects. Which is it?

The second point of confusion is that I don't get OnDelete() to appear for the tag configuration. I haven't used WithMany() because that entity lacks references back to the interlinking entity. Can I still manage its deletion behavior? Do I need to explicitly declare it to achieve the requested behavior?

entity.HasOne(e => e.Tag)
  .OnDelete(DeleteBehavior.NoAction);

The classes look roughly like this.

public class Tag { public Guid Id { get; set; } }
public class Member { public Guid Id { get; set; } }

public class Tag_Member
{
  public Guid TagId { get; set; }
  public Guid MemberId { get; set; }
  public Tag Tag { get; set; }
  public Member Member { get; set; }
}

My references are mainly this and this.

edit: Based on the suggestions in the answer, this is the final version of the relation between members and tag.

private static void OnModelCreating(EntityTypeBuilder<Member> entity)
{
  entity.HasKey(e => e.Id); ...
}

private static void OnModelCreating(EntityTypeBuilder<Tag> entity)
{
  entity.HasKey(e => e.Id); ...
}

private static void OnModelCreating(EntityTypeBuilder<Member_Tag> entity)
{
  entity.HasKey(e => new { e.MemberId, e.TagId });
  entity.HasOne(e => e.Member).WithMany().OnDelete(DeleteBehavior.NoAction);
  entity.HasOne(e => e.Tag).WithMany().OnDelete(DeleteBehavior.NoAction);
}
1
3
7/27/2019 11:50:27 AM

Accepted Answer

I'm not even clear on which of hte entities that the deletetion behavior affects. Which is it?

That's easy. The cascade delete always affects the dependent entity (i.e. the entity containing the FK).

I don't feel certain which to use to enforce the above behavior. Should I use NoAction, ClientNoAction, Restrict or someting else?

You seem to be using EF Core 3.0 preview which adds more options not documented yet. But the option for classic cascade delete implemented at the database level has always been Cascade.

I haven't used WithMany() because that entity lacks references back to the interlinking entity.

In order to be able to fluently configure the relationship aspects, you have to fully specify the relationship parties by using the Has + With pair. Since navigation properties are not mandatory for either side of the relationship, all you need it to pass the correct argument to Has / With method - if you do have navigation property, pass the name or lambda expression accessor, otherwise don't pass anything (but still include the call). e.g.

entity.HasOne(e => e.Tag)
    .WithMany() // <--
    .OnDelete(DeleteBehavior.Cascade); // now you can do this

But note that DeleteBehavior.Cascade is the default for required relationships (in other words, when the FK is non nullable type), so you normally don't need fluent configuration for that. And if the property names follow the EF Core naming conventions, you don't need fluent configuration at all.

Simple example can be seen here.

4
7/28/2019 8:18:39 PM


Related Questions





Related

Licensed under: CC-BY-SA with attribution
Not affiliated with Stack Overflow
Licensed under: CC-BY-SA with attribution
Not affiliated with Stack Overflow