Я установил группу сущностей, которые наследуются от «базовой» сущности, которая содержит ключевое поле и 2 поля аудита даты и времени:
/// <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; }
}
Я установил объект, наследующий этот абстрактный класс:
/// <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>();
}
}
В моем DbContext я создал DbSet, который ссылается на объект MilitaryUnit, и применил конфигурацию:
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());
}
Военная часть
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);
}
}
Когда я пытаюсь применить миграцию, я получаю следующую ошибку:
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.
Теперь у меня есть конфигурация модели для 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();
}
}
... но я не знаю, где это применить! Я предположил, что это где-то в DbContext, но после попытки я все еще получаю ошибку (как упоминалось выше). Я схожу с ума и упускаю что-то совершенно очевидное?
Что произойдет, если вы добавите modelBuilder.ApplyConfiguration<BaseEntity>(new BaseEntityConfiguration()); перед modelBuilder.ApplyConfiguration<MilitaryUnit>(new MilitaryUnitConfiguration());? Я никогда раньше не использовал абстрактный класс в качестве базовой сущности, но я думаю, что это имеет смысл. Также может упростить вашу жизнь вызов ключа Id вместо Guid и позволить EF предугадать взаимосвязь (с Id EF сделает некоторые предположения, и вам не придется указывать имя вашего внешнего ключа для свойств навигации других классов. которые включают членов MilitaryUnit).
Возникает вопрос: действительно ли вы хотите, чтобы типы, производные от BaseEntity, находились в одной иерархии наследования, то есть все в одной таблице с дискриминатором (TPH)? Вот что делает builder.HasBaseType<BaseEntity>();.
@GertArnold Я думаю, мне может понадобиться немного почитать о методе HaseBaseType, я просто предположил, что он сообщает EF, что у меня есть базовый класс со свойствами, которые он должен включить! Я такой глупый





Всем спасибо за ответы! Кажется, решение было не слишком уж ужасным:
Я настраиваю класс BaseEntityConfiguration как абстрактный класс, который принимает тип сущности, который я хочу настроить, и реализовать интерфейс IEntityTypeConfiguration, а также сделать метод Configure «переопределяемым».
Базовая конфигурация
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();
}
}
Затем в классах конфигурации сущностей я расширяю этот класс BaseEntityConfiguration и переопределяю метод Configure, одновременно выполняя базовый метод Configure из абстрактного класса:
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);
}
}
Хотя я не проверял это полностью, похоже, что миграция была успешно настроена:
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);
});
Единственное «обходное решение», которое у меня есть, - это взять конфигурацию «BaseEntityConfiguration» и добавить ее ко всем классам конфигурации производных сущностей ... не идеально и как бы сводит на нет использование абстрактного базового класса сущностей. Все еще открыт для предложений!