Search code examples
inheritanceentity-framework-4.1mappingtable-per-hierarchy

EF4.1 Exception creating Database with table-per-hierarchy inheritance


I have created a very simple project to demonstrate table-per-hierarchy inhertiance. In my unit test which tries to generate the database, i get one of a number of errors depending on the config:

without the Required() method

Map<InActiveUser>(x => x.Requires("IsActive").HasValue(false));
Map<ActiveUser>(x => x.Requires("IsActive").HasValue(true));

delivers:

System.Data.DataException : An exception occurred while initializing the database. See the InnerException for details.
  ----> System.Data.EntityCommandCompilationException : An error occurred while preparing the command definition. See the inner exception for details.
  ----> System.Data.MappingException : 
(6,10) : error 3032: Problem in mapping fragments starting at line 6:Condition member 'User.IsActive' with a condition other than 'IsNull=False' is mapped. Either remove the condition on User.IsActive or remove it from the mapping.

with the Required() method:

Map<InActiveUser>(x => x.Requires("IsActive").HasValue(false).IsRequired());

Map<ActiveUser>(x => x.Requires("IsActive").HasValue(true).IsRequired());

delivers:

System.Data.DataException : An exception occurred while initializing the database. See the InnerException for details.
  ----> System.Data.EntityCommandCompilationException : An error occurred while preparing the command definition. See the inner exception for details.
  ----> System.Data.MappingException : 
(6,10) : error 3023: Problem in mapping fragments starting at lines 6, 13, 19:Column User.IsActive has no default value and is not nullable. A column value is required to store entity data.

From what I understand we should not define the discriminator column/property on the base type, but either way it seems to make no difference with or without the column defined:

  public class User
  {
    [Key]
    [DatabaseGenerated(DatabaseGeneratedOption.Identity)]
    public virtual int Id { get; set; }

    [Required]
    public virtual string Username { get; set; }

    [Required]
    [DefaultValue(true)]
    public bool IsActive { get; set; } //have tried without this property
  }

  public class InActiveUser : User
  {
    public virtual DateTime DeActivatedDate { get; set; }
  }

  public class ActiveUser : User
  {
  }

Solution

  • You cannot map discriminator as property in the entity. Discriminator defines type of the entity. The reason is clear - discriminator defines instanced type. What should happen if you would be able to change discriminator value at runtime? How should .NET change the type of instanced object?

    Define entities as:

    public class User
    {
        [Key]
        [DatabaseGenerated(DatabaseGeneratedOption.Identity)]
        public virtual int Id { get; set; }
    
        [Required]
        public virtual string Username { get; set; }
    }
    
    public class InActiveUser : User
    {
        public virtual DateTime DeActivatedDate { get; set; }
    }
    
    public class ActiveUser : User
    { }
    

    And this should work:

    modelBuilder.Entity<User>()
                .Map<InActiveUser>(x => x.Requires("IsActive").HasValue(false))
                .Map<ActiveUser>(x => x.Requires("IsActive").HasValue(true));