2

I have requirement to create composite key in Entity Framework.

My base class key is "Guid" and there is something unique I want in student class like "ID" which can be readable. like "STUD01", which really requires readable unique data.

[NotMapped]
public class BaseEntity
{
     public Guid Key { get; set; }
     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 string 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)
 {
     modelBuilder.Entity<BaseEntity>().Property(x => x.DateCreated).HasDefaultValueSql("GETDATE()");
     modelBuilder.Entity<BaseEntity>().Property(x => x.DateModified).HasDefaultValueSql("GETDATE()");

     //here is my composite key
     modelBuilder.Entity<Student>().HasKey(c => new { c.Key, c.Id });
 }

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

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

and I get this error:

A key cannot be configured on 'Student' because it is a derived type. The key must be configured on the root type 'BaseClass'. If you did not intend for 'BaseClass' to be included in the model, ensure that it is not included in a DbSet property on your context, referenced in a configuration call to ModelBuilder, or referenced from a navigation property on a type that is included in the model.

How to achieve it?

I am using ASP.NET Core 2.1

TanvirArjel
  • 30,049
  • 14
  • 78
  • 114
kudlatiger
  • 3,028
  • 8
  • 48
  • 98
  • the modelBuilder.Entity call means there will be a table called BaseTable, on which all key properties have to be defined. What you probably meant were calls to the modelBuilder.Types API. – DevilSuichiro Jan 06 '19 at 07:06

1 Answers1

2

Problem is that [Not Mapped] data annotation is not ignoring the table creation because Fluent API always has the 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 best solution would be:

Write the configuration for BaseEntity as follows:

public class BaseEntityConfigurations<TEntity> : IEntityTypeConfiguration<TEntity> where TEntity : BaseEntity
{
    public virtual void Configure(EntityTypeBuilder<TEntity> builder)
    {
        builder.Property(x => x.Key).HasDefaultValueSql("NEWID()");
        //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

       // composite key
        builder.HasKey(c => new { c.Key, c.Id });
    }
}

Then in the OnModelCreating as follows:

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
     base.OnModelCreating(modelBuilder);

     modelBuilder.ApplyConfiguration(new StudentConfigurations());
}

Now everything should working fine!

Note: If you have already a database then it would not work on migration. You have to generate brand new table with initial migration because Entity Framework core cannot change primary key with migration.

TanvirArjel
  • 30,049
  • 14
  • 78
  • 114
  • @kudlatiger I have updated my answer! Please check it! Now it should solve problem. – TanvirArjel Jan 08 '19 at 13:28
  • @kudlatiger But there is a small issue. I am updating this answer again. and follow the updated version. This will give you full proof solution. – TanvirArjel Jan 09 '19 at 03:04
  • yes please, then I think it's question worth +1 :). let me make change and test it. – kudlatiger Jan 09 '19 at 03:39
  • why there is no "builder.Property" in BaseEntityConfigurations? – kudlatiger Jan 09 '19 at 03:43
  • Have used this `where TEntity : BaseEntity` in `BaseEntityConfigurations`. Copy my code thoroughly. – TanvirArjel Jan 09 '19 at 03:44
  • I mean, where is modelBuilder.Entity().Property(x => x.Key).HasDefaultValueSql("NEWID()"); Key should create guid automatically. – kudlatiger Jan 09 '19 at 03:46
  • Let us [continue this discussion in chat](https://chat.stackoverflow.com/rooms/186390/discussion-between-kudlatiger-and-tanvirarjel). – kudlatiger Jan 09 '19 at 03:46
  • I have updated the answer! Check the `BaseEntityConfigurations` configuration. I have added `builder.Property(x => x.Key).HasDefaultValueSql("NEWID()");` – TanvirArjel Jan 09 '19 at 03:48
  • Great! Check my https://stackoverflow.com/questions/54049471/how-to-track-enable-interface-concepts-with-entity-framework-code-first/54051105#54051105 this answer too. It has solved your problem too. You can marked this as answer too. – TanvirArjel Jan 09 '19 at 03:55
  • do we need to create configuration classes for all the other classes? because now it's not creating key for other classes – kudlatiger Jan 09 '19 at 05:06
  • @kudlatiger Yeh! You have to create configuration for all other classes like `StudentConfigurations` – TanvirArjel Jan 09 '19 at 05:07
  • but those other classes do not have any specific job like student class. it's like duplicate code base.Configure(builder); – kudlatiger Jan 09 '19 at 05:09
  • Yes! Just call the `base.Configure(builder);` in those class configuration. – TanvirArjel Jan 09 '19 at 05:10
  • yes working but it gives warning "remove this method "configure" to simply inherit it's behavior. – kudlatiger Jan 09 '19 at 05:12