9

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

TanvirArjel
  • 30,049
  • 14
  • 78
  • 114
kudlatiger
  • 3,028
  • 8
  • 48
  • 98
  • You want a *single table which is having all the fields of base class as columns*. Yet *My expectation is to create a single Student table with all the BaseEntity fields as columns*. To me that's contradictory. As for the latter, you may like [this](https://stackoverflow.com/a/52021425/861716) approach. – Gert Arnold Jan 05 '19 at 08:54
  • look at [link](https://stackoverflow.com/questions/49175564/ef-core-fluent-api-configuration-prevents-tpc-inheritance) – AmirNorouzpour Jan 05 '19 at 10:10
  • 1
    See the answer to the question marked as duplicate. The culprit is that you shouldn't refer directly to `BaseEntity` in the model navigation properties or fluent API. The problem with the concrete code in the post is `modelBuilder.Entity()` which marks `BaseEntity` as TPH base entity. – Ivan Stoev Jan 07 '19 at 12:46

3 Answers3

11

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);
            });
TanvirArjel
  • 30,049
  • 14
  • 78
  • 114
  • sounds good, meanwhile I have also found different way to resolve it. please see my answer and review it. – kudlatiger Jan 06 '19 at 03:59
  • Fluent API (`modelBuilder.Entity()` in this case) has higher priority than the data annotations (attributes). This definitely is **not** a solution because the attribute conflicts with the fluent configuration. – Ivan Stoev Jan 08 '19 at 12:17
  • @IvanStoev Did not understand! Explain me further. Are you expecting to call `modelBuilder.Ignore();`? – TanvirArjel Jan 08 '19 at 12:19
  • @IvanStoev More importantly I have tested this code on my side and its not conflicting. It generates everything graciously as expected! – TanvirArjel Jan 08 '19 at 12:22
  • The point was that the problem is caused by using `BaseEntity` as generic type argument in `modelBuilder.Entity()` call, and `[NotMapped]` or `Ignore` won't help. I've just tested your code (just in case there is some EF Core "smartness" I'm unaware of), and I'm getting error "The entity type 'BaseEntity' requires a primary key to be defined." – Ivan Stoev Jan 08 '19 at 12:53
  • @IvanStoev I have understood your point and I was aware of that during writing the answer here. I have first tested with answer's code and its worked then I have posted it here. Look at I have also shared the migration code. Is some thing magical has been happened in my case? Let me check again. – TanvirArjel Jan 08 '19 at 12:56
  • @IvanStoev I have checked! I have called `modelBuilder.Ignore();` after the base entity configuration settings and that's why it was working. Look at I have updated my answer! Thanks a lot for noticing this. – TanvirArjel Jan 08 '19 at 13:19
  • 2
    This eliminated the error, but now the property setup (`DefaultValueSql` etc.) is lost (not included in the generated migration code). So unfortunately the hack doesn't work. The correct way to handle such scenario is shown in the duplicate link and some other similar posts iterating `modelBuilder.Model.GetEntityTypes()` and calling some common configuration code for all derived entity types. The base class in such scenario should never be specified as entity, nor its properties can be configured once for all derived entities. – Ivan Stoev Jan 08 '19 at 14:12
  • @IvanStoev You are absolutely correct! Thank you so so much for spending your time! I am updating my answer again! – TanvirArjel Jan 08 '19 at 14:20
  • @IvanStoev Up voted your answer! Because you thoroughly deserve it! :) – TanvirArjel Jan 08 '19 at 14:46
  • 1
    I like your creativity and enthusiasm, really :) – Ivan Stoev Jan 08 '19 at 18:28
0

The TPC pattern is currently on the backlog, which means that it is being considered for inclusion as a feature, but no date has been set as yet.

read more.

AmirNorouzpour
  • 1,119
  • 1
  • 11
  • 26
-1

I have resolved the issue by simple change! We have to use Student entity in modelbuilder. I was mistakenly used BaseEntity

  protected override void OnModelCreating(ModelBuilder modelBuilder)
  {
     modelBuilder.Entity<Student>().Property(x => x.Created).HasDefaultValueSql("GETDATE()");
     modelBuilder.Entity<Student>().Property(x => x.Updated).HasDefaultValueSql("GETDATE()");
  }

Open Issue: The order of generated table columns can not be controlled by order number. This is not currently supported by .net core.

kudlatiger
  • 3,028
  • 8
  • 48
  • 98
  • 4
    This is not the best solution. If you have another model class called `Teacher` which is inheriting from `BaseEntity` then it would not work. You have to write the configuration for `Teacher` model class again. Now think What if you have 100 entities? you have to write 100 times. So follow my solution. – TanvirArjel Jan 06 '19 at 04:04
  • True, I agree with you. Let me try your solution and keep you posted – kudlatiger Jan 06 '19 at 04:11
  • Okay! Check it and be confirmed! – TanvirArjel Jan 06 '19 at 04:12
  • I have small problem. I have composite key requirement which is combination of key from base class and derived class. modelBuilder.Entity().HasKey(c => new { c.Key, c.Id }); after change suggested by you I am getting "A key cannot be configured on 'Student' because it is a derived type. The key must be configured on the root type 'BaseEntity'. Am I doing wrong table design? – kudlatiger Jan 06 '19 at 04:20
  • First think why do you want to make composite key with BaseEntity key and You model class key? `BaseEntity` does not have table on the database. So id on the Student always make sure the uniqueness. – TanvirArjel Jan 06 '19 at 04:23
  • right, actually my base class key is "guid" and there is something unique i want in student class like "ID" which can be readable. like "STUD01", it just example, real scenario is for some other table which really requires readable unique data. i thought of using "unique" constraint also. let me know your view. – kudlatiger Jan 06 '19 at 04:30
  • 1
    Okay! You can do so, I have done this on my side. Composite with BaseEntity key and student key. Please green check my answer and then ask an another question with composite key problem. I am giving answer to that question. – TanvirArjel Jan 06 '19 at 04:32
  • Let us [continue this discussion in chat](https://chat.stackoverflow.com/rooms/186249/discussion-between-tanvirarjel-and-kudlatiger). – TanvirArjel Jan 06 '19 at 04:33