2
votes

I'm trying to create a linked table that will allow me to have a many to many relationship between my product and accessory tables.

My classes are like this:

public class Product {
    public int Id { get; set; }
    public string Name { get; set; }

    public virtual ICollection<Accessory> Accessories { get; set; }
}

public class Accessory {
    public int Id { get; set; }
    public string Name { get; set; }

    public virtual ICollection<Machine> Machine { get; set; }
}

public class Type {
    public int Id { get; set; }
    public string Name { get; set; }
}

The same accessory can be on a product more than once if it is a different type, which will be determined in the link table. Something like this:

public class ProductAccessoryLink {
    public int productId {get; set;}
    public int accessoryId {get; set;}
    public int typeId {get; set}
    public int sort {get; set;}
    public string notes {get; set}
}

Is this the right approach.

EDIT

This is the error I'm getting when I run update-database:

Introducing FOREIGN KEY constraint 'FK_dbo.ProductAccessoryLinks_dbo.Types_TypeId' on table 'ProductAccessoryLinks' 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.

This is the sql causing the error: ALTER TABLE [dbo].[ProductAccessoryLinks] ADD CONSTRAINT [FK_dbo.ProductAccessoryLinks_dbo.Types_TypeId] FOREIGN KEY ([TypeId]) REFERENCES [dbo].[Types] ([Id]) ON DELETE CASCADE

1

1 Answers

3
votes

In your case you need to map explicitly the junction table. Your model would be like this:

public class Product
{
    public int Id { get; set; }
    public string Name { get; set; }

    public virtual ICollection<ProductAccessoryLink> ProductAccessoryLinks { get; set; }
}

public class Accessory
{
    public int Id { get; set; }
    public string Name { get; set; }

    public virtual ICollection<ProductAccessoryLink> ProductAccessoryLinks { get; set; }
}

public class Type
{
    public int Id { get; set; }
    public string Name { get; set; }
}

public class ProductAccessoryLink 
{
    public int ProductId { get; set; }
    public int AccessoryId { get; set; }
    public int TypeId { get; set; }
    public int sort { get; set; }
    public string notes { get; set; }

    public virtual Type Type { get; set; }
    public virtual Product Product { get; set; }
    public virtual Accessory Accessory { get; set; }
}

And you could configure the relationships overriding the OnModelCreating method on your context this way:

protected override void OnModelCreating(System.Data.Entity.DbModelBuilder modelBuilder)
{
    modelBuilder.Entity<ProductAccessoryLink>().HasKey(i => new { i.ProductId, i.AccesoryId, i.TypeId});

    modelBuilder.Entity<ProductAccessoryLink>()
       .HasRequired(i => i.Product)
       .WithMany(k => k.ProductAccessoryLinks)
       .HasForeignKey(i=>i.ProductId);

    modelBuilder.Entity<ProductAccessoryLink>()
       .HasRequired(i => i.Accesory)
       .WithMany(k => k.ProductAccessoryLinks)
       .HasForeignKey(i=>i.AccesoryId);

    modelBuilder.Entity<ProductAccessoryLink>()
       .HasRequired(i => i.Type)
       .WithMany()
       .HasForeignKey(i=>i.TypeId);
}

EF lets you configure directly many-to-many relationships of the way you were attempting to. Thereby EF is responsible for build a join table in the database with the appropriate keys of the tables it’s joining. (The keys are both primary keys of the join table and foreign keys pointing to the joined tables). That lets you to get your data across the join table without you having to be aware of its presence. But when you want to personalize that table (adding, for example, some additional properties), you need to map it explicitly as I show above.

Update

That exception is caused when you have multiple paths of cascade deletes that could end trying to delete the same row in the Types table. To resolve that problem I recommend you check my answer in this post