Unanswered Strange Code First Mapping Problem

  • Thursday, April 12, 2012 10:39 PM
     
     
    I have the following classes:

        public class AccountCode : AccountCodeBase
        {

        }

        public class CodeSegment : CodeSegmentBase
        {

        }


        public abstract class AccountCodeBase : ModelBase
        {

            private string _Code = String.Empty;
            private string _Description = String.Empty;
            private string _Notes = String.Empty;
            private string _Type = String.Empty;

            private bool _IsActive = true;

            public string Code { get { return _Code; } set { _Code = value; } }
            public string Description { get { return _Description; } set { _Description = value; } }
            public string Notes { get { return _Notes; } set { _Notes = value; } }
            public string Type { get { return _Type; } set { _Type = value; } }
            public bool IsActive { get { return _IsActive; } set { _IsActive = value; } }

            public virtual ICollection<CodeSegmentBase> CodeSegments { get; set; }
         }


        public abstract class CodeSegmentBase : ModelBase
        {
            private Guid? _AccountCode_Id;
            public Guid? AccountCode_Id {get { return _AccountCode_Id; } set { _AccountCode_Id = value; }}

            private Int32? _SegmentOrder;
            public Int32? SegmentOrder {get { return _SegmentOrder; } set { _SegmentOrder = value; }}

            private Int32 _CodeLength;
            public Int32 CodeLength {get { return _CodeLength; } set { _CodeLength = value; }}

            private AccountCodeBase _AccountCode;
            public virtual AccountCodeBase AccountCode {get { return _AccountCode; } set { _AccountCode = value; }}

            private string _Code = String.Empty;
            private string _Description = String.Empty;
            private string _Notes = String.Empty;
            private string _Name = String.Empty;

            private bool _IsActive = false;
            private bool _IsPurge = false;
            private bool _IsRequired = false;

            public virtual bool IsRequired {get { return _IsRequired; } set { _IsRequired = value; }}

            public string Code { get { return _Code; } set { _Code = value; } }
            public string Description { get { return _Description; } set { _Description = value; } }
            public string Notes { get { return _Notes; } set { _Notes = value; } }
            public string Name { get { return _Name; } set { _Name = value; } }
            public bool IsActive { get { return _IsActive; } set { _IsActive = value; } }
            public bool IsPurge { get { return _IsPurge; } set { _IsPurge = value; } }

        }

        public class AccountCodeMapping: ModelMapping<AccountCodeBase>
        {
            public AccountCodeMapping()
            {
                ToTable("AccountCode");
                Property(o => o.Description).HasColumnName("Description");
                Property(o => o.Code).HasColumnName("Code");
                Property(o => o.Type).HasColumnName("Type");
                Property(o=>o.IsActive).HasColumnName("IsActive");
                Property(o=>o.Notes).HasColumnName("Notes");
                Property(o => o.CodeSeparator).HasColumnName("CodeSeparator");

                HasMany<CodeSegmentBase>(o => o.CodeSegments);
            }
        }

        public class CodeSegmentMapping : ModelMapping<CodeSegmentBase>
        {
            public CodeSegmentMapping()
            {
                ToTable("CodeSegment");
                Property(o => o.AccountCode_Id).HasColumnName("AccountCode_Id");
                Property(o => o.Code).HasColumnName("Code");
                Property(o => o.Name).HasColumnName("Name");
                Property(o => o.Description).HasColumnName("Description");
                Property(o => o.Notes).HasColumnName("Notes");
                Property(o => o.IsActive).HasColumnName("IsActive");
                Property(o => o.IsPurge).HasColumnName("IsPurge");
                Property(o => o.IsRequired).HasColumnName("IsRequired");
                Property(o => o.SegmentOrder).HasColumnName("SegmentOrder");
                Property(o => o.CodeLength).HasColumnName("CodeLength");

                HasOptional(o => o.AccountCode).WithMany(a => a.CodeSegments).HasForeignKey(o => o.AccountCode_Id); 
            }
        }


        public abstract class ModelMapping<T> : EntityTypeConfiguration<T> where T : Domain.Models.ModelBase
        {
            protected ModelMapping()
            {
                HasKey(o => o.Id).Property(o => o.Id).IsRequired().HasColumnName("id").HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity);
                Property(o => o.RecordStats.WhoAdded).IsRequired().HasColumnName("Who_Added");
                Property(o => o.RecordStats.WhoUpdated).IsRequired().HasColumnName("Who_Updated");
                Property(o => o.RecordStats.WhenAdded).IsRequired().HasColumnName("When_Added");
                Property(o => o.RecordStats.WhenUpdated).IsRequired().HasColumnName("When_Updated");
            }
        }

        public abstract class ModelBase : IModel
        {
            private Guid? _Id;
            public Guid? Id {get { return _Id; } set { _Id = value; }}

            private RecordStatistics _RecordStats;
            public RecordStatistics RecordStats {get { return _RecordStats; } set { _RecordStats = value; }}

        }


        public interface IModel
        {
            Guid? Id { get; set; }
            RecordStatistics RecordStats { get; set; }
        }

    Then I have a class EntityDataProvider that has the following Method:

           protected override void OnModelCreating(DbModelBuilder modelBuilder)
            {
                modelBuilder.Configurations.Add(new AccountCodeMapping());
                modelBuilder.Configurations.Add(new CodeSegmentMapping());
                base.OnModelCreating(modelBuilder);
            }

    I then run the following test with success:

            [Test]
            public void Account_Code_Should_Have_A_Valid_Code_Segment_Collection()
            {
                Repository<AccountCode> repo = new Repository<AccountCode>(new EntityDataProvider("Maximus.Domain", true));
                AccountCode accountCode = repo.GetTable().FirstOrDefault();
                Assert.IsNotNull(accountCode.CodeSegments);
            }

    I then add the following class to my project:

        public class CodeSegmentFund : CodeSegmentBase
        {
            private bool _ReportToState = false;

            public override bool IsRequired
            {
                get
                {
                    return true;
                }
            }

            public bool ReportToState {get { return _ReportToState; } set { _ReportToState = value; }}
        }

    I then run the same test and it fails with the message:

      InnerException: System.Data.SqlClient.SqlException
           Message=Invalid column name 'Discriminator'.
    Invalid column name 'Discriminator'.
    Invalid column name 'Discriminator'.
    Invalid column name 'ReportToState'.
           Source=.Net SqlClient Data Provider
           ErrorCode=-2146232060
    ...

    It seems that since the CodeSegmentFund class inherits from ModelBase, EF tries to map this to a database table at runtime.  How do I prevent this?
    • Edited by WRBehning Thursday, April 12, 2012 11:23 PM
    •  

All Replies

  • Friday, April 13, 2012 12:50 PM
     
     
    Hi WRBehning;

    You can tell EF not to map the class by executing the following stsrem, this should do what you need.

    modelBuilder.Ignore<CodeSegmentFund>();

      

    Fernando (MCSD)

    If a post answers your question, please click "Mark As Answer" on that post and "Mark as Helpful".

  • Friday, April 13, 2012 3:49 PM
     
     

    Thanks.  I assmue then that when I do want to use this class, I will need to create a new EntityTypeConfiguration class that defines the mapping for CodeSegmentFund, and add this to the modelBuilder.


    Bill Behning

  • Friday, April 13, 2012 4:06 PM
     
     

      

    When you remove it from being mapped to the database you later can not re-map it seeming that there is on table in the db called CodeSegmentFund. Or am I missing something here?

      


    Fernando (MCSD)

    If a post answers your question, please click "Mark As Answer" on that post and "Mark as Helpful".

  • Friday, April 13, 2012 4:51 PM
     
     

    I should have stated that when I add this class to the project, I would remove the statement modelBuilder.Ignore<CodeSegmentFund>();, then add the new EntityTypeConfiguration class that defines the mapping for CodeSegmentFund.

    You will have to bear with me a bit here.  It is still early in the design stage, and I have not fully defined the database schema.

    What we have is an AccountCode class.  The AccountCode class can contain any number of CodeSegments.  There are some special types of CodeSegments, one of which is CodeSegmentFund.  These special CodeSegments inherit all the characteristics of a normal CodeSegment, but have extra attributes.

    My schema so far has the table AccountCode and SegmentCode.  The SegmentCode table has a foreign key to AccountCode. 

    I was not wanting to add a table for SegmentCodeFund since it contains just one extra attribute.  My thought process was to add a table CodeSegmentAttribute that would have a foreign key to CodeSegment.  I would then store the extra attributes for the special CodeSegments in this table.

    So I would want to map the CodeSegmentFund ineherited fields to the CodeSegment table, then the exctra attribute to the CodeSegmentAttribute table.

    Make sense?


    Bill Behning

  • Saturday, April 14, 2012 1:19 PM
     
     

    Hi Bill;

    To your statement, "I should have stated that when I add this class to the project, I would remove the statement modelBuilder.Ignore<CodeSegmentFund>();, then add the new EntityTypeConfiguration class that defines the mapping for CodeSegmentFund.", Yes this can be done but you need to be aware that when you do this and run the application and access the database it will be deleted and recreated loseing any data what was in the database unless you are using Entity Framework Migration which is currently available in version 4.3.

    To the rest of your question you need to understand the information in the link Entity Framework Mapping Scenarios. This will help in setting up the conceptual model to create the database schema you need.

      


    Fernando (MCSD)

    If a post answers your question, please click "Mark As Answer" on that post and "Mark as Helpful".

  • Monday, April 16, 2012 6:53 PM
     
     

    I have landed on using TPH for this particular issue.  I added a discriminator colum to the CodeSegment Table, and all is good.  I just have one question now.  How do I map a custom column name in one of the inherited entities?  As it stands this is my CodeSegmentMapping :

                Property(o => o.Code).HasColumnName("Code");
                Property(o => o.Name).HasColumnName("Name");
                Property(o => o.Description).HasColumnName("Description");
                Property(o => o.Notes).HasColumnName("Notes");
                Property(o => o.IsActive).HasColumnName("IsActive");
                Property(o => o.IsPurge).HasColumnName("IsPurge");
                Property(o => o.IsRequired).HasColumnName("IsRequired");
                Property(o => o.SegmentOrder1).HasColumnName("SegmentOrder");
                Property(o => o.CodeLength).HasColumnName("CodeLength");

                Map<CodeSegmentFund>(o => o.Requires("SegmentType").HasValue("Fund")).ToTable("CodeSegment");
                Map<CodeSegmentSubFund>(o=>o.Requires("SegmentType").HasValue("SubFund")).ToTable("CodeSegment");
                Map<CodeSegmentSubObject>(o => o.Requires("SegmentType").HasValue("SubObject")).ToTable("CodeSegment");

    My CodeSegmentFund class has a boolean property named ReportToState.  The table CodeSegment has a column named ReportToState as well, and this works as expected.  However, say perhaps I named the property ReportThisToState.  Where would I define this mapping? 


    Bill Behning