5

I'm attempting to set this up using code first in entity framework and am running into difficulty. To describe what i'm trying to accomplish:

Have an entity of Product. This product optionally may have one or more related "child" products. A product can be the child to one or more parent products.

when I go to generate a controller tied to the model class "Product", i'm getting an error: (updated, more specific, matches code below)

 There was an error running the selected code generator:
'Unable to retrieve metadata for 'ProductCatalog.Models.Product'.
 Multiple object sets per type are not supported. The object sets 
'Product' and 'Products' can both contain instances of type
'ProductCatalog.Models.Product'.

here's the not working model class:

using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
using System.Data.Entity;

namespace ProductCatalog.Models
{
    // Product
    public class Product
    {
        [Key]
        public int ProductId { get; set; } // ProductID (Primary key)
        public string ProductName { get; set; } // ProductName
        public string ProductSku { get; set; } // ProductSKU
        public int BaseQuantity { get; set; } // BaseQuantity
        public decimal BaseCost { get; set; } // BaseCost

        // Reverse navigation
        public virtual ICollection<RelatedProduct> ParentProducts { get; set; } // RelatedProduct.FK_RelatedProductChildID
        public virtual ICollection<RelatedProduct> ChildProducts { get; set; } // RelatedProduct.FK_RelatedProductParentID

        public virtual ICollection<RelatedProduct> RelatedProducts { get; set; }
    }


    // RelatedProduct
    public class RelatedProduct
    {
        [Key, Column(Order = 0)]
        public int ParentId { get; set; } // ParentID
        [Key, Column(Order = 1)]
        public int ChildId { get; set; } // ChildID
        public int Quantity { get; set; } // Quantity
        public bool Required { get; set; } // Required
        public bool Locked { get; set; } // Locked

        // Foreign keys
        public virtual Product ParentProduct { get; set; } //  FK_RelatedProductParentID
        public virtual Product ChildProduct { get; set; } //  FK_RelatedProductChildID
    }

    public class ProductDBContext : DbContext
    {
        public IDbSet<Product> Product { get; set; } // Product
        public IDbSet<RelatedProduct> RelatedProduct { get; set; } // RelatedProduct

        protected override void OnModelCreating(DbModelBuilder modelBuilder)
        {
            base.OnModelCreating(modelBuilder);

            modelBuilder.Entity<RelatedProduct>()
                .HasRequired(a => a.ParentProduct)
                .WithMany(b => b.ChildProducts)
                .HasForeignKey(c => c.ParentId) // FK_RelatedProductParentID
                .WillCascadeOnDelete(false);

            modelBuilder.Entity<RelatedProduct>()
                .HasRequired(a => a.ChildProduct)
                .WithMany(b => b.ParentProducts)
                .HasForeignKey(c => c.ChildId); // FK_RelatedProductChildID

        }
    }
}
Bumble
  • 97
  • 1
  • 7
  • some updates on what i've tried: a) built as database first. this works, but i'm trying to figure this out as code first. b) reverse engineer using db construct. This creates some very nice fluent api code that basically does what I did above. still same error. Will continue to investigate. – Bumble Jan 04 '14 at 14:45
  • Your `DbSet`s are private. Try to make them `public`. Or is it only a copy-and-paste error? – Slauma Jan 04 '14 at 15:20
  • Just forgot. Thanks though! i have updated the model to cleanup some of that. this code can be copied as is into a project and tried. @Slauma, I did find another of your responses that seemed like a close match. I was just about to try working that into my model. http://goo.gl/2rV4pe – Bumble Jan 04 '14 at 17:23
  • I don't think my other answer will help as it is for a m-to-m relationship *without* payload. Your approach (with payload) is correct and the mapping is perfect. The problem must be somewhere else (really weird exception). One thing that's disturbing me is the usage of `ToTable` with a schema-specified table name. Normally you'd use the second param of `ToTable` to define the schema, like `ToTable("Product", "dbo")`. Or just remove the `"dbo."` altogether (use only `ToTable("Product")`) because "dbo" is the default schema anyway. However, I can't image that this explains the strange exception. – Slauma Jan 04 '14 at 17:35
  • @Slauma, yeah the toTable isn't really necessary. EF handled the table naming just fine. the reverse engineering thing added that in there. BUT, in other news. I found the fix. I'm updating the code above shortly, but the fix was the pluralize the DbSet "Product" to "Products".. thats all it took (and adding a willcascadeondelete(false) to the parent relationship). – Bumble Jan 04 '14 at 17:40
  • I just saw the `Product` DbSet too and was going to write it :) Congrats! (EF exceptions often don't point very well to the root of the problem...) – Slauma Jan 04 '14 at 17:42
  • You should not edit the question with the solution, but leave the bug there and write the solution in your answer below. (You can accept your own answer then.) BTW: Doesn't the `RelatedProduct` set need to be pluralized as well? – Slauma Jan 04 '14 at 17:52
  • ok cool. i'll put the bad code back in – Bumble Jan 04 '14 at 18:11

1 Answers1

3

fixed by pluralizing the DbSets

using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
using System.Data.Entity;

namespace ProductCatalog.Models
{
    // Product
    public class Product
    {
        [Key]
        public int ProductId { get; set; } // ProductID (Primary key)
        public string ProductName { get; set; } // ProductName
        public string ProductSku { get; set; } // ProductSKU
        public int BaseQuantity { get; set; } // BaseQuantity
        public decimal BaseCost { get; set; } // BaseCost

        // Reverse navigation
        public virtual ICollection<RelatedProduct> ParentProducts { get; set; } // RelatedProduct.FK_RelatedProductChildID
        public virtual ICollection<RelatedProduct> ChildProducts { get; set; } // RelatedProduct.FK_RelatedProductParentID

        public virtual ICollection<RelatedProduct> RelatedProducts { get; set; }
    }


    // RelatedProduct
    public class RelatedProduct
    {
        [Key, Column(Order = 0)]
        public int ParentId { get; set; } // ParentID
        [Key, Column(Order = 1)]
        public int ChildId { get; set; } // ChildID
        public int Quantity { get; set; } // Quantity
        public bool Required { get; set; } // Required
        public bool Locked { get; set; } // Locked

        // Foreign keys
        public virtual Product ParentProduct { get; set; } //  FK_RelatedProductParentID
        public virtual Product ChildProduct { get; set; } //  FK_RelatedProductChildID
    }

    public class ProductDBContext : DbContext
    {
        public IDbSet<Product> Products { get; set; } // Product
        public IDbSet<RelatedProduct> RelatedProducts { get; set; } // RelatedProduct

        protected override void OnModelCreating(DbModelBuilder modelBuilder)
        {
            base.OnModelCreating(modelBuilder);

            modelBuilder.Entity<RelatedProduct>()
                .HasRequired(a => a.ParentProduct)
                .WithMany(b => b.ChildProducts)
                .HasForeignKey(c => c.ParentId) // FK_RelatedProductParentID
                .WillCascadeOnDelete(false);

            modelBuilder.Entity<RelatedProduct>()
                .HasRequired(a => a.ChildProduct)
                .WithMany(b => b.ParentProducts)
                .HasForeignKey(c => c.ChildId); // FK_RelatedProductChildID

        }
    }
}
Bumble
  • 97
  • 1
  • 7