Enity Framework M zu N oder “Many-To-Many”:
Eine simple M zu N Beziehung mittels Entity Framework Code First zu definieren ist ziemlich trivial:
1: public class DemoContext : DbContext
2: {
3: public DbSet<User> Users { get; set; }
4: public DbSet<Group> Groups { get; set; }
5: }
6:
7: public class User
8: {
9: public Guid Id { get; set; }
10: public string DisplayName { get; set; }
11: public virtual List<Group> MemberOf { get; set; }
12: }
13:
14: public class Group
15: {
16: public Guid Id { get; set; }
17: public string DisplayName { get; set; }
18: public virtual List<User> Members { get; set; }
19: }
20: }
Alles was man dazu braucht ist jeweils eine Liste des jeweiligen anderen Typs, welche mit “virtual” gekennzeichnet ist.
Problem: “Introducing FOREIGN KEY constraint… may cause cycles or multiple cascade” – “Multitenant-Szenario”
In meinem Beispiel gab es aber noch die Entität des “Tenants” – jeder Tenant hat X-User und X-Groups. Das hat das normale Mapping etwas aus dem Konzept gebracht.
Folgender Code:
1: public class DemoContext : DbContext
2: {
3:
4: public DbSet<User> Users { get; set; }
5: public DbSet<Tenant> Tenants { get; set; }
6: public DbSet<Group> Groups { get; set; }
7:
8: }
9:
10: public class User
11: {
12: public Guid Id { get; set; }
13: public string DisplayName { get; set; }
14: public Tenant Tenant { get; set; }
15: [ForeignKey("Tenant")]
16: public Guid TenantId { get; set; }
17: public virtual List<Group> MemberOf { get; set; }
18:
19: }
20:
21: public class Group
22: {
23: public Guid Id { get; set; }
24: public string DisplayName { get; set; }
25: public Tenant Tenant { get; set; }
26: [ForeignKey("Tenant")]
27: public Guid TenantId { get; set; }
28: public virtual List<User> Members { get; set; }
29: }
30:
31: public class Tenant
32: {
33: public Guid Id { get; set; }
34: public string Name { get; set; }
35: public List<User> Users { get; set; }
36: public List<Group> Groups { get; set; }
37:
38: }
39: }
Dieser Code wird bei der Erzeugung der Db diesen Fehler werfen:
An unhandled exception of type 'System.Data.SqlClient.SqlException' occurred in EntityFramework.dll
Additional information: Introducing FOREIGN KEY constraint 'FK_dbo.UserGroups_dbo.Groups_Group_Id' on table 'UserGroups' may cause cycles or multiple cascade paths. Specify ON DELETE NO ACTION or ON UPDATE NO ACTION, or modify other FOREIGN KEY constraints.
Could not create constraint. See previous errors.
Lösung des Problems: OnModelCreation & die Fluent-API
Durch die Verlinkung des Tenants kann das Entity Framework nicht mehr automatisch die entsprechenden Constraints ermiteln. Es hat bei mir eine Weile gedauert und ich bin erst durch diesen Blogpost auf das Ergebnis gekommen, aber dies ist des Rätsels Lösung:
1: public class DemoContext : DbContext
2: {
3: protected override void OnModelCreating(DbModelBuilder modelBuilder)
4: {
5:
6: modelBuilder.Entity<User>()
7: .HasRequired(u => u.Tenant)
8: .WithMany(t => t.Users)
9: .HasForeignKey(x => x.TenantId)
10: .WillCascadeOnDelete(false);
11:
12: modelBuilder.Entity<Group>()
13: .HasRequired(g => g.Tenant)
14: .WithMany(t => t.Groups)
15: .HasForeignKey(x => x.TenantId)
16: .WillCascadeOnDelete(false);
17:
18: base.OnModelCreating(modelBuilder);
19: }
20: public DbSet<User> Users { get; set; }
21: public DbSet<Tenant> Tenants { get; set; }
22: public DbSet<Group> Groups { get; set; }
23:
24: }
Mit diesem Code sollte die “gewünschte” Datenbank erstellt werden:
Dem gesamten Code gibt es natürlich auch auf GitHub.