locked
Interesting problem with many to one association. RRS feed

  • Question

  • Hi,

    The problem manifests itself with a hierarchy based on an abstract class, mapped with TPT. The base abstract class defines a relationship one-to-many to some container. In this case the foreign key in the abstract class' table is not generated. Here is the test code:

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using System.Data.Entity;
    using System.Data.Entity.ModelConfiguration;
    using System.ComponentModel.DataAnnotations.Schema;
    
    namespace OneToManyAbstract
    {
        abstract class Entity
        {
            public virtual long Id { get; set; }
        }
    
        abstract class Item : Entity
        {
            public virtual Container Container { get; set; }
            public virtual long? ContainerId { get; protected internal set; }
        }
    
        class SmallItem : Item
        {
            public virtual short Number { get; set; }
        }
    
        class BigItem : Item
        {
            public virtual long Number { get; set; }
        }
    
        class Container : Entity
        {
            public virtual string Name { get; set; }
            public virtual ICollection<Item> Items { get; set; }
        }
    
        class EntityConfiguration : EntityTypeConfiguration<Entity>
        {
            public EntityConfiguration()
            {
                HasKey(e => e.Id);
                Property(i => i.Id)
                    .IsRequired()
                    .HasDatabaseGeneratedOption(DatabaseGeneratedOption.None);
            }
        }
                    
    
        class ItemConfiguration : EntityTypeConfiguration<Item>
        {
            public ItemConfiguration()
            {
                Map(p =>
                {
                    p.MapInheritedProperties();
                    p.ToTable("Item");
                });
            }
        }
    
        class SmallItemConfiguration : EntityTypeConfiguration<SmallItem>
        {
            public SmallItemConfiguration()
            {
                ToTable("SmallItem");
            }
        }
    
        class BigItemConfiguration : EntityTypeConfiguration<BigItem>
        {
            public BigItemConfiguration()
            {
                ToTable("BigItem");
            }
        }
    
        class ContainerConfiguration : EntityTypeConfiguration<Container>
        {
            public ContainerConfiguration()
            {
                Map(p =>
                {
                    p.MapInheritedProperties();
                    p.ToTable("Container");
                });
            }
        }
                                
    
        class Repository : DbContext
        {
            public IDbSet<Container> Containers { get { return Set<Container>(); } }
            public IDbSet<Item> Items { get { return Set<Item>(); } }
    
            protected override void OnModelCreating(DbModelBuilder modelBuilder)
            {
                base.OnModelCreating(modelBuilder);
    
                modelBuilder
                    .Configurations
                        .Add(new EntityConfiguration())
                        .Add(new ContainerConfiguration())
                        .Add(new ItemConfiguration())
                            .Add(new BigItemConfiguration())
                            .Add(new SmallItemConfiguration());
            }
        }
    
        class Program
        {
            static void Main()
            {
                Database.SetInitializer(new DropCreateDatabaseAlways<Repository>());
                using (var repo = new Repository())
                {
                    repo.Database.Initialize(false);
                    var n = repo.Containers.Count();
                }
            }
        }
    }

    You'd expect the table Item to contain a foreign key column ContainerId, right? Here is what was generated:

    The workaround turned out to be very easy but took me more than a day to figure out: remove the abstract qualifier from the class Item. Do you see any other way to fix this and keep the abstract-ness of Item?

    Thanks Val



    • Edited by Valo Friday, May 18, 2012 11:41 PM
    Friday, May 18, 2012 11:39 PM

Answers

  • Hi Valo,

    I have rewrite the code and it now works fine, please refer to it.

    namespace ConsoleApplication2
    {
        class Program
        {
            static void Main(string[] args)
            {
                TestContext context = new TestContext();
                context.Database.Create();
            }
        }
    
        abstract class Entity
        {
            [DatabaseGenerated(System.ComponentModel.DataAnnotations.DatabaseGeneratedOption.None)]
            public virtual long Id { get; set; }
        }
    
        abstract class Item : Entity
        {
            public virtual string Name { get; set; }
            public virtual Container container { get; set; }
        }
    
        class SmallItem : Item
        {
            public virtual short Number { get; set; }
        }
    
        class BigItem : Item
        {
            public virtual long Number { get; set; }
        }
    
        class Container : Entity
        {
            public virtual string Name { get; set; }
            public virtual ICollection<Item> items { get; set; }
        }
    
        class TestContext : DbContext
        {
            public DbSet<Item> itemSet { get; set; }
            public DbSet<Container> containerSet { get; set; }
    
            protected override void OnModelCreating(DbModelBuilder modelBuilder)
            {
                modelBuilder.Entity<Item>().Map(m =>
                    {
                        m.MapInheritedProperties();
                        m.ToTable("Item");
                    });
                modelBuilder.Entity<Container>().Map(m =>
                    {
                        m.MapInheritedProperties();
                        m.ToTable("Container");
                    });
    
                modelBuilder.Entity<SmallItem>().ToTable("SmallItem");
                modelBuilder.Entity<BigItem>().ToTable("BigItem");
            }
        }
    }

    The FK has already generated in the database:

    Best Regards


    Allen Li [MSFT]
    MSDN Community Support | Feedback to us

    • Marked as answer by Allen_MSDN Sunday, June 3, 2012 6:20 AM
    Tuesday, May 29, 2012 9:13 AM

All replies

  • Hi Valo,

    Welcome to MSDN Forum.

    We will do some more pending research  about your problem and come back as soon as possible, Thanks for understanding.

    Have a nice day.


    Allen Li [MSFT]
    MSDN Community Support | Feedback to us

    Monday, May 21, 2012 6:06 AM
  • Hi Valo,

    I'm not clear about what's the inheritance mapping you want to achieve. If it is TPC, the Item table shouldn't be appear in the database, if it is TPH, the SmallItem and BigItem shouldn't be appear in the database, all of the records of these two types should in Item table. Could you please clarify what's the inheritance mapping you want to achieve? This is, so I can help you more effectively.

    Best Regards


    Allen Li [MSFT]
    MSDN Community Support | Feedback to us

    Monday, May 21, 2012 8:52 AM
  • Hi Allen,

    The base class of all is Entity. Item and Container inherit from it and the inheritance mapping is TPC. BigItem and SmallItem inherit from Item via TPT. So, as expected there is no table for Entity, a table for Item, a table for BigItem and SmallItem with PK,FK to Item and a table for container which is associated with Item-s 1-to-many.

    Val


    Thanks Val

    Tuesday, May 22, 2012 1:41 AM
  • Hi Valo,

    If Base class with Item and Container are TPC inheritance, the Item class shouldn't be abstract. If it is abstract, it will can't be instantiate, then we can't insert records into this table. So, I think we need to remove abstract keyword from it.

    Best Regards


    Allen Li [MSFT]
    MSDN Community Support | Feedback to us

    Tuesday, May 22, 2012 6:53 AM
  • I do not understand the reasons for your statement. Is this an EF limitation that the base class in a TPT hierarchy cannot be abstract or the descendant classes in a TPC hierarchy must be concrete...? Here is the class diagram one more time:


    Thanks Val

    Thursday, May 24, 2012 1:33 AM
  • Hi Valo,

    If the inheritance mapping is TPT, 'Item', 'SmallItem' and 'BigItem' are needed in database and model, you may insert some records into these tables. But now, 'Item' class is abstract, how do you instantiate an instance and add it into database? So, the 'Item' class should be a normal class, not an abstract.

    Best Regards


    Allen Li [MSFT]
    MSDN Community Support | Feedback to us

    Thursday, May 24, 2012 5:05 AM
  • Hi Valo,

    Have you solved the issue? I look forward to hearing from you. If you need further help, please feel free to let me know, I will be more than happy to be of assistance. :)

    Best Regards


    Allen Li [MSFT]
    MSDN Community Support | Feedback to us

    Monday, May 28, 2012 5:22 AM
  • Allen,

    Of course you cannot instantiate Item objects - Item is abstract, however that should not mean that you cannot insert BigItems and SmallItems - they'll populate the Item table. This is a case that NHibernate does not have an issue with. One should be able to define abstract classes in a TPT mode.

    Val


    Thanks Val

    Monday, May 28, 2012 9:20 PM
  • I do not think that I should resolve my issue - I find this to be an issue with EF: having abstract classes is pretty common in OOP and *Object*/Relational Mapping tool should be able to handle it. It seems to me that this is a shortcoming of EF...

    I work very hard to get used to EF but things like this are really shaking my faith...

    Val


    Thanks Val

    Monday, May 28, 2012 9:28 PM
  • Hi Valo,

    Thanks for your feedback, I will do more research on this issue and come back as soon as possible. :)

    Best Regards


    Allen Li [MSFT]
    MSDN Community Support | Feedback to us

    Tuesday, May 29, 2012 2:49 AM
  • Hi Valo,

    I have rewrite the code and it now works fine, please refer to it.

    namespace ConsoleApplication2
    {
        class Program
        {
            static void Main(string[] args)
            {
                TestContext context = new TestContext();
                context.Database.Create();
            }
        }
    
        abstract class Entity
        {
            [DatabaseGenerated(System.ComponentModel.DataAnnotations.DatabaseGeneratedOption.None)]
            public virtual long Id { get; set; }
        }
    
        abstract class Item : Entity
        {
            public virtual string Name { get; set; }
            public virtual Container container { get; set; }
        }
    
        class SmallItem : Item
        {
            public virtual short Number { get; set; }
        }
    
        class BigItem : Item
        {
            public virtual long Number { get; set; }
        }
    
        class Container : Entity
        {
            public virtual string Name { get; set; }
            public virtual ICollection<Item> items { get; set; }
        }
    
        class TestContext : DbContext
        {
            public DbSet<Item> itemSet { get; set; }
            public DbSet<Container> containerSet { get; set; }
    
            protected override void OnModelCreating(DbModelBuilder modelBuilder)
            {
                modelBuilder.Entity<Item>().Map(m =>
                    {
                        m.MapInheritedProperties();
                        m.ToTable("Item");
                    });
                modelBuilder.Entity<Container>().Map(m =>
                    {
                        m.MapInheritedProperties();
                        m.ToTable("Container");
                    });
    
                modelBuilder.Entity<SmallItem>().ToTable("SmallItem");
                modelBuilder.Entity<BigItem>().ToTable("BigItem");
            }
        }
    }

    The FK has already generated in the database:

    Best Regards


    Allen Li [MSFT]
    MSDN Community Support | Feedback to us

    • Marked as answer by Allen_MSDN Sunday, June 3, 2012 6:20 AM
    Tuesday, May 29, 2012 9:13 AM
  • Hi Valo,

    Any update about this issue? If you need further help, please feel free to let me know, I will be more than happy to be of assistance.

    Best Regards


    Allen Li [MSFT]
    MSDN Community Support | Feedback to us

    Thursday, May 31, 2012 3:09 AM