Search code examples
c#entity-frameworkmigrationentity-framework-coredatabase-migration

Configuring abstract base class without creating table in EF Core


I have added DateCreated, UserCreated, DateModified and UserModified fields to each entity by creating a BaseEntity.cs class. My requirement is to generate single table which is having all the fields of base class as columns.

public class BaseEntity
{
     public DateTime? DateCreated { get; set; }
     public string UserCreated { get; set; }
     public DateTime? DateModified { get; set; }
     public string UserModified { get; set; }
}

Here is my Student class

public class Student : BaseEntity
{
    public int Id { get; set; }
    public string Name { get; set; }
}

Here is my context class

public class SchoolContext: DbContext
{       
    public LibraContext(DbContextOptions<SchoolContext> options)
        : base(options)
    { }

    public DbSet<Student> Students { get; set; }
}

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
        //CreatedDate 
        modelBuilder.Entity<BaseEntity>().Property(x => x.DateCreated).HasDefaultValueSql("GETDATE()");
        //Updated Date
        modelBuilder.Entity<BaseEntity>().Property(x => x.DateModified).HasDefaultValueSql("GETDATE()");
 }

I have run the below migration commands to create script and update database

Add-Migration -Name "InitialMigration" -Context "SchoolContext"
update-database

It has created separate tables BaseEntity and Student in the SQL Server database. My expectation is to create a single Student table with all the BaseEntity fields as columns.

How to achieve it?

I am using ASP.NET CORE2.1


Solution

  • According to Microsoft Documentation you can use [NotMapped] data annotation or modelBuilder.Ignore<TEntity>(); to ignore the table creation for BaseEntity as follows:

    But in your case [NotMapped] would not help you because Fluent API always has higher priority than the data annotations (attributes). So you can call modelBuilder.Ignore<BaseEntity>(); after the BaseEntity configuration in the OnModelCreating but calling modelBuilder.Ignore<BaseEntity>(); will lose the BaseEntity configurations.

    So as per as I am concerned the best solution would be as follows:

    Write the configuration for BaseEntity as follows:

    public class BaseEntityConfigurations<TEntity> : IEntityTypeConfiguration<TEntity> where TEntity : BaseEntity
    {
        public virtual void Configure(EntityTypeBuilder<TEntity> builder)
        {
            //CreatedDate 
            builder.Property(x => x.DateCreated).HasDefaultValueSql("GETDATE()");
            //Updated Date
            builder.Property(x => x.DateModified).HasDefaultValueSql("GETDATE()");
        }
    }
    

    Then write the configuration for Student as follows:

    public class StudentConfigurations : BaseEntityConfigurations<Student>
    {
        public override void Configure(EntityTypeBuilder<Student> builder)
        {
            base.Configure(builder); // Must call this
    
           // other configurations here
        }
    }
    

    Then in the OnModelCreating as follows:

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
         base.OnModelCreating(modelBuilder);
    
         modelBuilder.ApplyConfiguration(new StudentConfigurations());
    }
    

    Migration will generate the only Student table with BaseEntity configuration as follows:

    migrationBuilder.CreateTable(
                name: "Students",
                columns: table => new
                {
                    Id = table.Column<int>(nullable: false)
                        .Annotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn),
                    DateCreated = table.Column<DateTime>(nullable: true, defaultValueSql: "GETDATE()"),
                    UserCreated = table.Column<string>(nullable: true),
                    DateModified = table.Column<DateTime>(nullable: true, defaultValueSql: "GETDATE()"),
                    UserModified = table.Column<string>(nullable: true),
                    Name = table.Column<string>(nullable: true)
                },
                constraints: table =>
                {
                    table.PrimaryKey("PK_Students", x => x.Id);
                });