Sam
Sam

Reputation: 4339

Many to Many and cascading in EF with no inverse property

I have an object in my domain model, User which contains a List of configuration settings. It looks like this:

public class User
{
    public int ID { get; set; }

    /* various user properties */

    public List<ConfigurationSetting> Settings { get; set; }
}

The Configuration Setting has the following definition:

public class ConfigurationSetting
{
    public int ID { get; set; }
    public string Name { get; set; }
    public string Value { get; set; }
    public MasterAccount Account { get; set; }
}

As you can see, there is no inverse navigation property in the configuration class. This is because settings are attached to a MasterAccount and optionally applied to a User, hence the need for a mapping table.

I am trying to create the relationship in my configuration UserConfiguration as follows:

            HasMany(x => x.Settings)
            .WithMany()
            .Map(m =>
            {
                m.ToTable("MapUserSettings");
                m.MapLeftKey("UserId");
                m.MapRightKey("SettingId");
            });

Note that the ID's are renamed using the following:

        HasKey(x => x.ID);
        Property(x => x.ID)
            .HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity)
            .HasColumnName("UserId");

When EF Code First attempts to create the model I get the following:

'FK_dbo.MapUserSettings_dbo.Settings_SettingId' on table 'MapUserSettings' may cause cycles or multiple cascade paths. Specify ON DELETE NO ACTION or ON UPDATE NO ACTION, or modify other FOREIGN KEY constraints.

How can I correctly configure cascading?

Upvotes: 1

Views: 267

Answers (1)

Myk Willis
Myk Willis

Reputation: 12879

I've tried to reproduce your problem given the information you've provided, and I can't get EF to complain about building the model.

The one thing you didn't provide was a definition for MasterAccount, so I have to believe that you have some relationship coded there that is causing the circular dependency.

If the issue is simply that you don't want cascading delete for one/all of the relationships, you can disable it using the Fluent API by adding

.WillCascadeOnDelete(false)

to the relationship's configuration. For example:

   modelBuilder.Entity<User>()
        .HasMany(x => x.Settings)
        .WithRequired()
        .WillCascadeOnDelete(false)

Below is what I coded up that works fine in my environment:

public class User
{
    public int ID { get; set; }

    /* various user properties */

    public List<ConfigurationSetting> Settings { get; set; }
}

public class ConfigurationSetting
{
    public int ID { get; set; }
    public string Name { get; set; }
    public string Value { get; set; }
    public MasterAccount Account { get; set; }
}

public class MasterAccount
{
    public int ID { get; set; }
    public List<ConfigurationSetting> Settings { get; set; }
    public User User { get; set; }
}

public class MyContext : DbContext
{
    public DbSet<MasterAccount> MasterAccounts { get; set; }
    public DbSet<ConfigurationSetting> Settings { get; set; }
    public DbSet<User> Users { get; set; }

    protected override void OnModelCreating(DbModelBuilder modelBuilder)
    {
        modelBuilder.Entity<User>()
                .HasKey(x => x.ID);

        modelBuilder.Entity<User>()
            .Property(x => x.ID)
            .HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity)
            .HasColumnName("UserId");

        modelBuilder.Entity<User>()
            .HasMany(x => x.Settings)
            .WithMany()
            .Map(m =>
             {
                 m.ToTable("MapUserSettings");
                 m.MapLeftKey("UserId");
                 m.MapRightKey("SettingId");
             });

        base.OnModelCreating(modelBuilder);
    }
}

Upvotes: 2

Related Questions