Ключ базового типа миграции EF Core не определен

Я установил группу сущностей, которые наследуются от «базовой» сущности, которая содержит ключевое поле и 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, но после попытки я все еще получаю ошибку (как упоминалось выше). Я схожу с ума и упускаю что-то совершенно очевидное?

Единственное «обходное решение», которое у меня есть, - это взять конфигурацию «BaseEntityConfiguration» и добавить ее ко всем классам конфигурации производных сущностей ... не идеально и как бы сводит на нет использование абстрактного базового класса сущностей. Все еще открыт для предложений!

Adam 01.12.2018 00:55

Что произойдет, если вы добавите modelBuilder.ApplyConfiguration<BaseEntity>(new BaseEntityConfiguration()); перед modelBuilder.ApplyConfiguration<MilitaryUnit>(new MilitaryUnitConfiguration());? Я никогда раньше не использовал абстрактный класс в качестве базовой сущности, но я думаю, что это имеет смысл. Также может упростить вашу жизнь вызов ключа Id вместо Guid и позволить EF предугадать взаимосвязь (с Id EF сделает некоторые предположения, и вам не придется указывать имя вашего внешнего ключа для свойств навигации других классов. которые включают членов MilitaryUnit).

jonsca 01.12.2018 03:27

Возникает вопрос: действительно ли вы хотите, чтобы типы, производные от BaseEntity, находились в одной иерархии наследования, то есть все в одной таблице с дискриминатором (TPH)? Вот что делает builder.HasBaseType<BaseEntity>();.

Gert Arnold 01.12.2018 10:21

@GertArnold Я думаю, мне может понадобиться немного почитать о методе HaseBaseType, я просто предположил, что он сообщает EF, что у меня есть базовый класс со свойствами, которые он должен включить! Я такой глупый

Adam 01.12.2018 11:21
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
1
4
1 094
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Всем спасибо за ответы! Кажется, решение было не слишком уж ужасным:

Я настраиваю класс 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);
    });

Другие вопросы по теме