EF Core — создание составного уникального индекса с объектом-значением и родительским типом

У меня есть объект с объектом значения ExternalSystemName и родительским типом Deployment, который является другим объектом. Важная часть модели выглядит так:

public sealed class ExternalSystem : Entity
{
    public ExternalSystemName Name { get; private set; }

    public Deployment Deployment { get; private set; }
}

Уникальность этой сущности определяется комбинацией идентификатора развертывания (хранящегося в классе сущности развертывания) и имени (которое является значением объекта значения ExternalSystemName). Другими словами, в развертывании не может быть 2 внешних систем с одинаковыми именами.

Я столкнулся с проблемой при попытке настроить этот комбинированный уникальный индекс с реализацией IEntityTypeConfiguration:

internal sealed class ExternalSystemsConfiguration : 
IEntityTypeConfiguration<ExternalSystem>
{
    public void Configure(EntityTypeBuilder<ExternalSystem> builder)
    {
        builder.ToTable("TblExternalSystems");

        builder.OwnsOne(e => e.Name, navigationBuilder =>
        {
            navigationBuilder.Property(e => e.Value)
            .HasColumnName("Name");
        });

        builder.HasIndex(e => new { e.Name, e.Deployment }).IsUnique();
    }
}

Я получаю это исключение при запуске моего API:

System.InvalidOperationException: ''Name' cannot be used as a property on entity type 'ExternalSystem' because it is configured as a navigation.'

Вместо этого я попытался указать индекс на e.Name.Value, и я получаю эту ошибку:

System.ArgumentException: 'The expression 'e => new <>f__AnonymousType0`2(Value = e.Name.Value, Deployment = e.Deployment)' is not a valid member access expression. The expression should represent a simple property or field access: 't => t.MyProperty'. When specifying multiple properties or fields, use an anonymous type: 't => new { t.MyProperty, t.MyField }'. (Parameter 'memberAccessExpression')'

Я также попробовал уникальный индекс только для одного из этих свойств, и все равно получаю ошибку навигации. Боюсь, я уже знаю ответ, но означает ли это, что EF Core поддерживает индексы только для столбцов, которые не относятся к типу non-entity, non-valueObject? Означает ли это, что моя модель должна иметь свойство Guid, представляющее идентификатор развертывания, а не само развертывание?

ОБНОВИТЬ

Я узнал, что EF Core прекрасно справляется с парами эталон/примитив. Имея это в виду, моя сущность ExternalSystem теперь может иметь ОБА этих свойства:

public Deployment Deployment { get; private set; }

public Guid DeploymentId { get; private set; }

Это свойство Guid не является частью конструктора, и поскольку они в конечном итоге получают одно и то же имя столбца, все работает нормально. Теперь я могу просто добавить это в свою конфигурацию для этого объекта, и индекс будет создан правильно:

builder.HasIndex(e => new { e.DeploymentId}).IsUnique();

Моя проблема теперь связана с объектом значения. Полагаю, используя тот же подход, я мог бы сделать что-то подобное?

public ExternalSystemName NameV { get; private set; }

public string Name { get; private set; }

Я должен переименовать свойство объекта значения, поскольку они, очевидно, не могут иметь одно и то же имя. Это не то, что мне нужно было делать с типом объекта, поскольку EF Core изначально знал, что нужно добавить «Id» к имени столбца. При такой настройке EF Core дублирует столбцы. У одного есть имя «Имя», а у другого — «ExternalSystem_Name». Очевидно, что все остальное не работает, поскольку этот столбец не принимает нулевые значения. Почему это происходит?

Добавьте свойства NameId и DeploymentId в ExternalSystem и добавьте индекс для этих свойств.

Gert Arnold 29.10.2022 18:52

чтобы эти свойства Id могли жить параллельно со связанным типом объекта и хорошо работать с базовой БД?

Martin 30.10.2022 17:25

Да, базовая база данных уже должна иметь эти столбцы, поскольку FK и EF знают, как работать с парами ссылочных/примитивных свойств (также известными как ассоциации внешних ключей).

Gert Arnold 31.10.2022 19:05

Это работает для типа объекта, но я не уверен, как затем справиться с объектом значения в том же подходе. Я обновлю свой оригинальный пост.

Martin 31.10.2022 20:47
Стоит ли изучать 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 называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
4
4
291
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Вам нужно вручную определить ключ как внешний ключ, используя Fluent API.

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<ExternalSystem>()
       .HasOne(g => g.NameV)
       .WithOne()
       .HasForeignKey<ExternalSystem>(j => j.Name);
 }

Поместите этот метод в свой класс контекста БД

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

Я исправил это, определив преобразователь для свойства ExternalSystemName:

builder.Property(e => e.Name)
        .HasConversion(
            e => e.Value,
            v => ExternalSystemName.Create(v).Value);

Теперь я могу объявить свой уникальный индекс как таковой:

    builder.HasIndex(e => new { e.DeploymentId, e.Name}).IsUnique();

Я также обновился до .NET 7 и EF Core 7 после того, как задал этот вопрос, так что это тоже могло быть фактором.

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