1

Looks like a "yet another EF multiple FK" question but please continue reading. I have the following requirements:

  • A "Monitor" can have zero or more "Notifier"
  • A "Monitor" can have zero or more "Setting"
  • A "Notifier" can have zero or more "Setting"

So, deleting a "Monitor" will delete it's "Setting"s and "Notifiers". Also, since a "Notifier" may have zero or more Settings, related "Setting"s should be removed as well.

I have created the following model, but hit the famous "... may cause cycles or multiple cascade paths" error.

The closest answer was Entity Framework Cascading Delete but it uses DB first approach.

Can anyone suggest a workaround to enable cascaded deletes to above model?

And here is my model:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Data.Entity;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;

namespace Testbed
{
    public class Monitor
    {
        public int Id { get; set; }
        [Column(TypeName = "varchar")]
        [MaxLength(255)]
        public string Type { get; set; }
        [Column(TypeName = "varchar")]
        [MaxLength(20)]
        public string RunFrequency { get; set; }
        public List<Setting> Settings { get; set; }
        public List<Notifier> Notifiers { get; set; }
    }

    public class Setting
    {
        public int Id { get; set; }
        [MaxLength(255)]
        [Column(TypeName = "varchar")]
        public string Name { get; set; }
        [MaxLength(512)]
        public string Value { get; set; }
        public bool IsPassword { get; set; }
    }

    public class Notifier
    {
        public int Id { get; set; }
        [MaxLength(255)]
        [Column(TypeName = "varchar")]
        public string Name { get; set; }
        public List<Setting> Settings { get; set; }

    }
}
Community
  • 1
  • 1
meraydin
  • 89
  • 1
  • 1
  • 11

1 Answers1

0

In your Setting and Notifier classes you will have to add some properties to allow the relationship.

public class Setting
{
    public int Id { get; set; }
    [MaxLength(255)]
    [Column(TypeName = "varchar")]
    public string Name { get; set; }
    [MaxLength(512)]
    public string Value { get; set; }
    public bool IsPassword { get; set; }

    //Add these properties
    public int ForeignId{ get; set; }
    public virtual Monitor Monitor { get; set; }
    public virtual Notifier Notifier { get; set; }
}

public class Notifier
{
    public int Id { get; set; }
    [MaxLength(255)]
    [Column(TypeName = "varchar")]
    public string Name { get; set; }
    public List<Setting> Settings { get; set; }

    //Add these properties
    public int MonitorId { get; set; }
    public virtual Monitor Monitor { get; set; }

}

Now that you have the relationships enabled you will need to add the mappings. This can be done either in your EntityModel or in your mapping class if you have them separated.

modelBuilder.Entity<Setting>()
    .HasOptional(e => e.Monitor) //Could be .HasRequired()
    .WithMany(e => e.Settings)
    .HasForeignKey(e => e.ForeignId)
    .WillCascadeOnDelete(true);

modelBuilder.Entity<Setting>()
    .HasOptional(e => e.Notifier) //Could be .HasRequired()
    .WithMany(e => e.Settings)
    .HasForeignKey(e => e.ForeignId)
    .WillCascadeOnDelete(true);

modelBuilder.Entity<Notifier>()
    .HasRequired(e => e.Monitor) //Could be .HasOptional()
    .WithMany(e => e.Notifiers)
    .HasForeignKey(e => e.MonitorId)
    .WillCascadeOnDelete(true);

The way this is setup is always going to cause problems. Your Settings table is being used by multiple tables. In my example I have made a single foreign key in Settings that is used by the other two tables. This can also be split if you wanted into MonitorId and NotifierId. It all depends on your data model.

EDIT: In the case that you need MonitorId and NotifierId.

modelBuilder.Entity<Setting>()
    .HasOptional(e => e.Monitor) //Could be .HasRequired()
    .WithMany(e => e.Settings)
    .HasForeignKey(e => e.MonitorId)
    .WillCascadeOnDelete(true);

modelBuilder.Entity<Setting>()
    .HasOptional(e => e.Notifier) //Could be .HasRequired()
    .WithMany(e => e.Settings)
    .HasForeignKey(e => e.NotifierId)
    .WillCascadeOnDelete(true);

You will also have to add those properties to the Setting class.

public class Setting
{
    public int MonitorId { get; set; }
    public int NotifierId { get; set; }
}
Matt Rowland
  • 4,575
  • 4
  • 25
  • 34
  • Above modification throws "Multiplicity" exception on model creation Also, since the Settings table is being used by multiple tables, this won't solve my problem. Looks like NotifierId and MonitorId properties required. In that case what will be the mapping? – meraydin Feb 06 '16 at 16:18
  • That gives "Introducing FOREIGN KEY constraint 'FK_dbo.Settings_dbo.Notifiers_NotifierId' on table 'Settings' may cause cycles or multiple cascade paths. Specify ON DELETE NO ACTION or ON UPDATE NO ACTION, or modify other FOREIGN KEY constraints" exception. I've tried HasOptional/HasRequired paths too. Also, made NotifierId and MonitorId nullable... No luck. – meraydin Feb 06 '16 at 16:30
  • You will have to configure your SQL with the correct ON DELETE parameters. This is just the C# side. – Matt Rowland Feb 06 '16 at 16:32
  • I've tried every possible ON DELETE parameter, same problem. And, I'm trying to do this with code-first, it should be something in model creation. – meraydin Feb 06 '16 at 16:56
  • 1
    I just replicated this whole issue with a local database as well. If you change the `HasOptional()`s to `HasRequired()` it works perfectly. This works with the `ForeignId` or splitting it into two. I also didn't have to configure any constraints in the database. – Matt Rowland Feb 06 '16 at 17:05
  • Are you sure it works with split mode because I was unable to make it work? By saying split mode, I mean using "MonitorId and NotifierId" instead of ForeignId – meraydin Feb 06 '16 at 17:14
  • Yes it works. What is the error that you are getting? – Matt Rowland Feb 06 '16 at 17:19
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/102794/discussion-between-matt-rowland-and-meraydin). – Matt Rowland Feb 06 '16 at 17:27