Reputation: 201
I have setup a group of entities that inherit from a 'base' entity which contains the key field and 2 date time audit fields:
/// <summary>
/// The <see cref="BaseEntity"/> class is a standard entity from which all other entities inherit.
/// </summary>
public abstract class BaseEntity
{
/// <summary>
/// Universal unique identifier for the entity.
/// </summary>
public Guid Guid { get; set; }
/// <summary>
/// Timestamp for when the entity was created.
/// </summary>
public DateTime CreatedAtTime { get; set; }
/// <summary>
/// Timestamp for when the entity was last updated.
/// </summary>
public DateTime UpdatedAtTime { get; set; }
}
I have setup an entity that inherits this abstract class:
/// <summary>
/// A <see cref="MilitaryUnit"/> is a group of <see cref="MilitaryMember"/>'s that work together
/// and have a 'chain of command'
/// </summary>
public class MilitaryUnit : BaseEntity
{
public string Name { get; set; }
public string Description { get; set; }
public virtual ICollection<MilitaryMember> Members { get; set; }
public virtual ICollection<MilitaryUnitSection> Sections { get; set; }
public MilitaryUnit()
{
this.Members = new HashSet<MilitaryMember>();
this.Sections = new HashSet<MilitaryUnitSection>();
}
}
In my DbContext I have created a DbSet that refernces the 'MilitaryUnit' entity and I have applied the configuration:
DbContext
public DatabaseContext(DbContextOptions<DatabaseContext> options) : base(options) { }
public DbSet<MilitaryUnit> MilitaryUnits { get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.ApplyConfiguration<MilitaryUnit>(new MilitaryUnitConfiguration());
}
MilitaryUnitConfiguration
public class MilitaryUnitConfiguration : IEntityTypeConfiguration<MilitaryUnit>
{
public void Configure(EntityTypeBuilder<MilitaryUnit> builder)
{
// All entities inherit from the BaseEntity type
builder.HasBaseType<BaseEntity>();
// The unit name can only be 50 characters long and is unique
builder.Property(entity => entity.Name)
.HasColumnType("varchar(50)")
.HasMaxLength(50)
.IsRequired();
builder.HasAlternateKey(entity => entity.Name);
// The unit has a description that can be up to 100 character long
builder.Property(entity => entity.Description)
.HasColumnType("varchar(100)")
.HasMaxLength(100);
// The unit has multiple members
builder.HasMany<MilitaryMember>(entity => entity.Members);
// The unit has multiple sections
builder.HasMany<MilitaryUnitSection>(entity => entity.Sections);
}
}
When I attempt to apply a migration I am getting the following error:
A key cannot be configured on 'MilitaryUnit' because it is a derived type. The key must be configured on the root type 'BaseEntity'. If you did not intend for 'BaseEntity' 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.
Now I have model configuration for the 'BaseEntity':
public class BaseEntityConfiguration : IEntityTypeConfiguration<BaseEntity>
{
public void Configure(EntityTypeBuilder<BaseEntity> builder)
{
builder.HasKey(entity => entity.Guid);
builder.Property(entity => entity.Guid)
.HasColumnType("guid");
builder.Property(entity => entity.CreatedAtTime)
.HasColumnType("datetime")
.HasValueGenerator(typeof(CurrentDateTimeGenerator))
.ValueGeneratedOnAdd();
// The updated timestamp has a default value of the minimum date time value and will only
// generate a new date time when the entity has been updated
builder.Property(entity => entity.UpdatedAtTime)
.HasColumnType("datetime")
.HasDefaultValue(DateTime.MinValue)
.HasValueGenerator(typeof(CurrentDateTimeGenerator))
.ValueGeneratedOnUpdate();
}
}
...but I'm not sure where to apply this! I assumed it was somewhere in the DbContext but after trying this I still get the error (as mentioned above). I am going completely insane and missing something completely obvious?
Upvotes: 1
Views: 1516
Reputation: 201
Thanks all for the replies! So it seems as though the solution wasn't too horrendous:
I setup the BaseEntityConfiguration class to be an abstract class that takes the entity type I want to configure and implement the IEntityTypeConfiguration interface and make the Configure method able to be 'overidable'.
BaseConfiguration
public abstract class BaseEntityConfiguration<TEntityType> : IEntityTypeConfiguration<TEntityType>
where TEntityType : BaseEntity
{
public virtual void Configure(EntityTypeBuilder<TEntityType> builder)
{
builder.HasKey(entity => entity.Guid);
// The created timestamp has a default value of the current system time for when the entity
// was created in the database. This value cannot be changed after it is set
builder.Property(entity => entity.CreatedAtTime)
.HasColumnType("datetime")
.HasValueGenerator(typeof(CurrentDateTimeGenerator))
.ValueGeneratedOnAdd();
// The updated timestamp has a default value of the minimum date time value and will only
// generate a new date time when the entity has been updated
builder.Property(entity => entity.UpdatedAtTime)
.HasColumnType("datetime")
.HasDefaultValue(DateTime.MinValue)
.HasValueGenerator(typeof(CurrentDateTimeGenerator))
.ValueGeneratedOnUpdate();
}
}
Then on the entity configuration classes I extend this BaseEntityConfiguration class and override the Configure method, whilst executing the base Configure method from the abstract class:
public class MilitaryUnitConfiguration : BaseEntityConfiguration<MilitaryUnit>
{
public override void Configure(EntityTypeBuilder<MilitaryUnit> builder)
{
base.Configure(builder);
// The unit name can only be 50 characters long and is unique
builder.Property(entity => entity.Name)
.HasColumnType("varchar(50)")
.HasMaxLength(50)
.IsRequired();
builder.HasAlternateKey(entity => entity.Name);
// The unit has a description that can be up to 100 character long
builder.Property(entity => entity.Description)
.HasColumnType("varchar(100)")
.HasMaxLength(100);
// The unit has multiple members
builder.HasMany<MilitaryMember>(entity => entity.Members);
// The unit has multiple sections
builder.HasMany<MilitaryUnitSection>(entity => entity.Sections);
}
}
Whilst I haven't tested this thoroughly it seems as though the migration has been setup successfully:
migrationBuilder.CreateTable(
name: "MilitaryUnits",
columns: table => new
{
Guid = table.Column<Guid>(nullable: false),
CreatedAtTime = table.Column<DateTime>(type: "datetime", nullable: false),
UpdatedAtTime = table.Column<DateTime>(type: "datetime", nullable: false, defaultValue: new DateTime(1, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified)),
Name = table.Column<string>(type: "varchar(50)", maxLength: 50, nullable: false),
Description = table.Column<string>(type: "varchar(100)", maxLength: 100, nullable: true)
},
constraints: table =>
{
table.PrimaryKey("PK_MilitaryUnits", x => x.Guid);
table.UniqueConstraint("AK_MilitaryUnits_Name", x => x.Name);
});
Upvotes: 1