23 December 2013 EF, Entity Framework, Fix, HowTo Robert Muehsig

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:

image

Dem gesamten Code gibt es natürlich auch auf GitHub.


Written by Robert Muehsig

Software Developer - from Saxony, Germany - working on primedocs.io. Microsoft MVP & Web Geek.
Other Projects: KnowYourStack.com | ExpensiveMeeting | EinKofferVollerReisen.de