Поделиться через


Массовая конфигурация модели

Если аспект необходимо настроить одинаково в нескольких типах сущностей, следующие методы позволяют уменьшить дублирование кода и консолидировать логику.

См. полный пример проекта , содержащий фрагменты кода, представленные ниже.

Групповая конфигурация в OnModelCreating

Каждый объект построителя, возвращаемый из ModelBuilder, представляет собой свойство Model или Metadata, которые предоставляют низкоуровневый доступ к объектам, составляющим модель. В частности, существуют методы, позволяющие выполнять итерацию определенных объектов в модели и применять к ним общую конфигурацию.

В следующем примере модель содержит настраиваемый тип Currencyзначения:

public readonly struct Currency
{
    public Currency(decimal amount)
        => Amount = amount;

    public decimal Amount { get; }

    public override string ToString()
        => $"${Amount}";
}

Свойства этого типа по умолчанию не обнаруживаются, так как текущий поставщик EF не знает, как сопоставить его с типом базы данных. Этот фрагмент OnModelCreating кода добавляет все свойства типа Currency и настраивает преобразователь значений для поддерживаемого типа : decimal

foreach (var entityType in modelBuilder.Model.GetEntityTypes())
{
    foreach (var propertyInfo in entityType.ClrType.GetProperties())
    {
        if (propertyInfo.PropertyType == typeof(Currency))
        {
            entityType.AddProperty(propertyInfo)
                .SetValueConverter(typeof(CurrencyConverter));
        }
    }
}
public class CurrencyConverter : ValueConverter<Currency, decimal>
{
    public CurrencyConverter()
        : base(
            v => v.Amount,
            v => new Currency(v))
    {
    }
}

Недостатки API метаданных

  • В отличие от API Fluent, каждое изменение модели необходимо выполнить явно. Например, если некоторые свойства Currency были настроены как навигационные по соглашению, сначала необходимо удалить навигацию, ссылающуюся на свойство CLR, прежде чем добавлять свойство типа сущности для него. #9117 улучшит это.
  • Конвенции запускаются после каждого изменения. Если вы удалите навигацию, обнаруженную соглашением, то соглашение будет выполняться снова и может добавить его обратно. Чтобы предотвратить это, необходимо либо отложить выполнение соглашений до тех пор, пока свойство не будет добавлено через вызов DelayConventions(), а затем удалить возвращенный объект, либо отметить свойство CLR как игнорируемое с помощью AddIgnored.
  • Типы сущностей могут быть добавлены после этой итерации, и конфигурация не будет применена к ним. Обычно это можно предотвратить, разместив этот код в конце OnModelCreating, но если у вас есть два взаимозависимых набора конфигураций, может не быть порядка, который позволит им применяться согласованно.

Настройка предварительного соглашения

EF Core позволяет задать конфигурацию сопоставления один раз для заданного типа CLR, и эта конфигурация затем применяется ко всем свойствам этого типа в модели по мере их обнаружения. Это называется "предварительной настройкой модели до стандартов", так как она настраивает аспекты модели перед применением стандартов сборки модели. Такая конфигурация применяется путем переопределения ConfigureConventions типа, производного от DbContext.

В этом примере показано, как настроить все свойства типа Currency для преобразователя значений:

protected override void ConfigureConventions(ModelConfigurationBuilder configurationBuilder)
{
    configurationBuilder
        .Properties<Currency>()
        .HaveConversion<CurrencyConverter>();
}

В этом примере показано, как настроить некоторые аспекты для всех свойств типа string:

configurationBuilder
    .Properties<string>()
    .AreUnicode(false)
    .HaveMaxLength(1024);

Замечание

Тип, указанный в вызове ConfigureConventions , может быть базовым типом, интерфейсом или определением универсального типа. Все соответствующие конфигурации будут применены в порядке от наименьшего значения:

  1. Интерфейс
  2. Базовый тип
  3. Определение универсального типа
  4. Тип значения, не допускающий значений NULL
  5. Точный тип

Это важно

Конфигурация предварительного соглашения эквивалентна явной конфигурации, которая применяется сразу после добавления соответствующего объекта в модель. Она переопределит все соглашения и заметки данных. Например, при приведенной выше конфигурации все строковые свойства внешнего ключа будут созданы как не юникод с MaxLength 1024, даже если это не соответствует основному ключу.

Игнорирование типов

Настройка перед началом работы с соглашениями также позволяет игнорировать тип и предотвратить его обнаружение посредством соглашений как типа сущности или как свойства типа сущности.

configurationBuilder
    .IgnoreAny(typeof(IList<>));

Сопоставление типов по умолчанию

Как правило, EF может переводить запросы с константами типа, который не поддерживается поставщиком, если вы указали преобразователь значений для свойства этого типа. Однако в запросах, не связанных с свойствами этого типа, невозможно найти правильный преобразователь значений EF. В этом случае можно вызвать DefaultTypeMapping для добавления или переопределения сопоставления типов поставщика.

configurationBuilder
    .DefaultTypeMapping<Currency>()
    .HasConversion<CurrencyConverter>();

Ограничения конфигурации предварительного соглашения

  • Многие аспекты нельзя настроить с помощью этого подхода. #6787 развернет это до дополнительных типов.
  • В настоящее время конфигурация определяется только типом CLR. #20418 разрешает пользовательские предикаты.
  • Эта конфигурация выполняется перед созданием модели. Если при применении возникают конфликты, трассировка стека исключений не будет содержать ConfigureConventions метод, поэтому может быть труднее найти причину.

Соглашения

Соглашения о сборке моделей EF Core — это классы, содержащие логику, которая активируется на основе изменений, внесенных в модель по мере ее создания. Это поддерживает модель up-to-date в актуальном состоянии, так как выполняется явная конфигурация, применяются атрибуты сопоставления и запускаются другие соглашения. Для участия в этом каждом соглашении реализуется один или несколько интерфейсов, определяющих, когда будет активирован соответствующий метод. Например, соглашение, реализующее IEntityTypeAddedConvention , будет активировано при добавлении нового типа сущности в модель. Аналогичным образом, соглашение, реализующее оба IForeignKeyAddedConvention и IKeyAddedConvention будет активировано всякий раз, когда ключ или внешний ключ добавляются в модель.

Соглашения о построении моделей — это эффективный способ управления конфигурацией модели, но они могут быть сложными и трудными для правильного использования. Во многих случаях конфигурацию модели предварительного соглашения можно использовать вместо того, чтобы легко указать общую конфигурацию свойств и типов.

Добавление нового соглашения

Пример: ограничение длины дискриминационных свойств

Стратегия сопоставления наследования таблиц для каждой иерархии требует от дискриминационных столбцов, чтобы указать, какой тип представлен в любой строке. По умолчанию EF использует неограниченный строковый столбец для дискриминатора, что гарантирует работу с любой длиной дискриминатора. Однако ограничение максимальной длины дискриминационных строк может обеспечить более эффективное хранение и запросы. Давайте создадим новое соглашение, которое сделает это.

Соглашения о сборке моделей EF Core активируются на основе изменений, вносимых в модель по мере её создания. Это поддерживает модель up-to-date в актуальном состоянии, так как выполняется явная конфигурация, применяются атрибуты сопоставления и запускаются другие соглашения. Для участия в этом каждом соглашении реализуется один или несколько интерфейсов, определяющих, когда будет активировано соглашение. Например, соглашение, реализующее IEntityTypeAddedConvention , будет активировано при добавлении нового типа сущности в модель. Аналогичным образом, соглашение, реализующее оба IForeignKeyAddedConvention и IKeyAddedConvention будет активировано всякий раз, когда ключ или внешний ключ добавляются в модель.

Знание того, какие интерфейсы реализовывать, может быть затруднительно, поскольку изначально сделанная конфигурация в модели может позднее измениться или быть удалена. Например, ключ может быть создан по соглашению, но затем позже заменен при явной настройке другого ключа.

Давайте сделаем это немного более конкретным, сделав первую попытку реализовать соглашение о дискриминационных длинах:

public class DiscriminatorLengthConvention1 : IEntityTypeBaseTypeChangedConvention
{
    public void ProcessEntityTypeBaseTypeChanged(
        IConventionEntityTypeBuilder entityTypeBuilder,
        IConventionEntityType? newBaseType,
        IConventionEntityType? oldBaseType,
        IConventionContext<IConventionEntityType> context)
    {
        var discriminatorProperty = entityTypeBuilder.Metadata.FindDiscriminatorProperty();
        if (discriminatorProperty != null
            && discriminatorProperty.ClrType == typeof(string))
        {
            discriminatorProperty.Builder.HasMaxLength(24);
        }
    }
}

Это соглашение реализует IEntityTypeBaseTypeChangedConvention, что означает, что соглашение будет срабатывать при изменении сопоставленной иерархии наследования для типа сущности. Затем соглашение находит и настраивает строковое дискриминационное свойство для иерархии.

Затем это соглашение используется путем вызова Add в ConfigureConventions:

protected override void ConfigureConventions(ModelConfigurationBuilder configurationBuilder)
{
    configurationBuilder.Conventions.Add(_ =>  new DiscriminatorLengthConvention1());
}

Замечание

Вместо того, чтобы добавлять экземпляр соглашения напрямую, метод Add принимает фабрику для создания экземпляров этого соглашения. Это позволяет соглашению использовать зависимости от внутреннего поставщика услуг EF Core. Так как это соглашение не имеет зависимостей, параметр поставщика услуг называется _, указывая, что он никогда не используется.

Создание модели и просмотр Post типа сущности показывает, что это работало - дискриминационные свойства теперь настроены на максимальную длину 24:

 Discriminator (no field, string) Shadow Required AfterSave:Throw MaxLength(24)

Но что произойдет, если мы сейчас явно настраиваем другое дискриминационное свойство? Рассмотрим пример.

modelBuilder.Entity<Post>()
    .HasDiscriminator<string>("PostTypeDiscriminator")
    .HasValue<Post>("Post")
    .HasValue<FeaturedPost>("Featured");

Глядя на представление отладки модели, мы обнаружили, что длина дискриминатора больше не сконфигурирована.

 PostTypeDiscriminator (no field, string) Shadow Required AfterSave:Throw

Это связано с тем, что свойство дискриминатора, которое мы настроили в нашем соглашении, позже было удалено при добавлении пользовательского дискриминатора. Мы могли бы попытаться исправить это, реализуя другой интерфейс в нашем соглашении, чтобы реагировать на дискриминационные изменения, но выяснить, какой интерфейс реализовать не просто.

К счастью, есть более простой подход. Часто не имеет значения, как выглядит модель во время её сборки, если конечная модель верна. Кроме того, конфигурация, которую мы хотим применить, часто не требует активации других соглашений для реагирования. Поэтому наша конференция может реализовать IModelFinalizingConvention. Соглашения о завершении модели выполняются после завершения всех остальных этапов сборки модели, и поэтому имеют доступ к почти окончательному состоянию модели. Это отличается от интерактивных соглашений , которые реагируют на каждое изменение модели и убедитесь, что модель up-to-date в любой точке OnModelCreating выполнения метода. Соглашение о финализации модели обычно выполняет итерацию по всей модели, настраивая ее элементы по ходу выполнения. Таким образом, в этом случае мы найдем каждую дискриминацию в модели и настроим ее:

public class DiscriminatorLengthConvention2 : IModelFinalizingConvention
{
    public void ProcessModelFinalizing(IConventionModelBuilder modelBuilder, IConventionContext<IConventionModelBuilder> context)
    {
        foreach (var entityType in modelBuilder.Metadata.GetEntityTypes()
                     .Where(entityType => entityType.BaseType == null))
        {
            var discriminatorProperty = entityType.FindDiscriminatorProperty();
            if (discriminatorProperty != null
                && discriminatorProperty.ClrType == typeof(string))
            {
                discriminatorProperty.Builder.HasMaxLength(24);
            }
        }
    }
}

После создания модели с использованием этого нового соглашения мы обнаружили, что длина дискриминатора теперь настроена правильно, несмотря на то, что она была изменена.

PostTypeDiscriminator (no field, string) Shadow Required AfterSave:Throw MaxLength(24)

Мы можем сделать на шаг дальше и настроить максимальную длину, чтобы она была длиной самого длинного значения дискриминатора.

public class DiscriminatorLengthConvention3 : IModelFinalizingConvention
{
    public void ProcessModelFinalizing(IConventionModelBuilder modelBuilder, IConventionContext<IConventionModelBuilder> context)
    {
        foreach (var entityType in modelBuilder.Metadata.GetEntityTypes()
                     .Where(entityType => entityType.BaseType == null))
        {
            var discriminatorProperty = entityType.FindDiscriminatorProperty();
            if (discriminatorProperty != null
                && discriminatorProperty.ClrType == typeof(string))
            {
                var maxDiscriminatorValueLength =
                    entityType.GetDerivedTypesInclusive().Select(e => ((string)e.GetDiscriminatorValue()!).Length).Max();

                discriminatorProperty.Builder.HasMaxLength(maxDiscriminatorValueLength);
            }
        }
    }
}

Теперь максимальная длина столбца дискриминатора составляет 8, что является длиной "Featured", самого длинного значения дискриминатора, используемого в системе.

PostTypeDiscriminator (no field, string) Shadow Required AfterSave:Throw MaxLength(8)

Пример: длина по умолчанию для всех строковых свойств

Давайте рассмотрим другой пример, в котором можно использовать соглашение о завершении — установку максимальной длины по умолчанию для любого строкового свойства. Конвенция выглядит довольно похоже на предыдущий пример.

public class MaxStringLengthConvention : IModelFinalizingConvention
{
    public void ProcessModelFinalizing(IConventionModelBuilder modelBuilder, IConventionContext<IConventionModelBuilder> context)
    {
        foreach (var property in modelBuilder.Metadata.GetEntityTypes()
                     .SelectMany(
                         entityType => entityType.GetDeclaredProperties()
                             .Where(
                                 property => property.ClrType == typeof(string))))
        {
            property.Builder.HasMaxLength(512);
        }
    }
}

Это соглашение довольно простое. Он находит каждое строковое свойство в модели и задает максимальную длину 512. В представлении отладки в свойствах Postмы видим, что все свойства строки теперь имеют максимальную длину 512.

EntityType: Post
  Properties:
    Id (int) Required PK AfterSave:Throw ValueGenerated.OnAdd
    AuthorId (no field, int?) Shadow FK Index
    BlogId (no field, int) Shadow Required FK Index
    Content (string) Required MaxLength(512)
    Discriminator (no field, string) Shadow Required AfterSave:Throw MaxLength(512)
    PublishedOn (DateTime) Required
    Title (string) Required MaxLength(512)

Замечание

Того же можно добиться при предварительной настройке, однако использование соглашения позволяет выполнять дополнительную фильтрацию применимых свойств и позволяет Атрибутам данных переопределять конфигурацию.

Наконец, прежде чем оставить этот пример, что произойдет, если мы используем оба MaxStringLengthConvention и DiscriminatorLengthConvention3 одновременно? Ответ заключается в том, что это зависит от того, в каком порядке они добавляются, поскольку соглашения о завершении модели выполняются в порядке их добавления. Таким образом, если MaxStringLengthConvention добавляется последним, оно будет выполняться последним и установит максимальную длину свойства дискриминатора до 512. Таким образом, в этом случае лучше добавить DiscriminatorLengthConvention3 последнее, чтобы можно было переопределить максимальную длину по умолчанию только для дискриминационных свойств, оставляя все остальные свойства строки как 512.

Замена существующего соглашения

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

Пример: сопоставление свойств opt-in

EF Core сопоставляет все общедоступные свойства для чтения и записи в соответствии с соглашением. Это может быть не подходит для способа определения типов сущностей. Чтобы изменить это, мы можем заменить PropertyDiscoveryConvention на нашу собственную реализацию, которая не сопоставляет какое-либо свойство, если оно явно не сопоставлено в OnModelCreating или не отмечено новым атрибутом Persist.

[AttributeUsage(AttributeTargets.Property | AttributeTargets.Field)]
public sealed class PersistAttribute : Attribute
{
}

Вот новое соглашение:

public class AttributeBasedPropertyDiscoveryConvention : PropertyDiscoveryConvention
{
    public AttributeBasedPropertyDiscoveryConvention(ProviderConventionSetBuilderDependencies dependencies)
        : base(dependencies)
    {
    }

    public override void ProcessEntityTypeAdded(
        IConventionEntityTypeBuilder entityTypeBuilder,
        IConventionContext<IConventionEntityTypeBuilder> context)
        => Process(entityTypeBuilder);

    public override void ProcessEntityTypeBaseTypeChanged(
        IConventionEntityTypeBuilder entityTypeBuilder,
        IConventionEntityType? newBaseType,
        IConventionEntityType? oldBaseType,
        IConventionContext<IConventionEntityType> context)
    {
        if ((newBaseType == null
             || oldBaseType != null)
            && entityTypeBuilder.Metadata.BaseType == newBaseType)
        {
            Process(entityTypeBuilder);
        }
    }

    private void Process(IConventionEntityTypeBuilder entityTypeBuilder)
    {
        foreach (var memberInfo in GetRuntimeMembers())
        {
            if (Attribute.IsDefined(memberInfo, typeof(PersistAttribute), inherit: true))
            {
                entityTypeBuilder.Property(memberInfo);
            }
            else if (memberInfo is PropertyInfo propertyInfo
                     && Dependencies.TypeMappingSource.FindMapping(propertyInfo) != null)
            {
                entityTypeBuilder.Ignore(propertyInfo.Name);
            }
        }

        IEnumerable<MemberInfo> GetRuntimeMembers()
        {
            var clrType = entityTypeBuilder.Metadata.ClrType;

            foreach (var property in clrType.GetRuntimeProperties()
                         .Where(p => p.GetMethod != null && !p.GetMethod.IsStatic))
            {
                yield return property;
            }

            foreach (var property in clrType.GetRuntimeFields())
            {
                yield return property;
            }
        }
    }
}

Подсказка

При замене встроенного соглашения новая реализация соглашения должна наследоваться от существующего класса соглашения. Обратите внимание, что некоторые соглашения имеют реализации, которые могут быть либо реляционными, либо специфичными для поставщика. В таком случае новая реализация соглашения должна наследовать от наиболее конкретного существующего класса соглашения для используемого поставщика базы данных.

Затем соглашение регистрируется с помощью Replace метода в ConfigureConventions:

protected override void ConfigureConventions(ModelConfigurationBuilder configurationBuilder)
{
    configurationBuilder.Conventions.Replace<PropertyDiscoveryConvention>(
        serviceProvider => new AttributeBasedPropertyDiscoveryConvention(
            serviceProvider.GetRequiredService<ProviderConventionSetBuilderDependencies>()));
}

Подсказка

Это случай, когда существующее соглашение имеет зависимости, представленные ProviderConventionSetBuilderDependencies объектом зависимостей. Они получены от внутреннего поставщика услуг с помощью GetRequiredService и передаются конструктору соглашения.

Обратите внимание, что это соглашение позволяет сопоставлять поля (в дополнение к свойствам) до тех пор, пока они помечены с помощью [Persist]. Это означает, что в модели можно использовать закрытые поля в качестве скрытых ключей.

Например, рассмотрим следующие типы сущностей:

public class LaundryBasket
{
    [Persist]
    [Key]
    private readonly int _id;

    [Persist]
    public int TenantId { get; init; }

    public bool IsClean { get; set; }

    public List<Garment> Garments { get; } = new();
}

public class Garment
{
    public Garment(string name, string color)
    {
        Name = name;
        Color = color;
    }

    [Persist]
    [Key]
    private readonly int _id;

    [Persist]
    public int TenantId { get; init; }

    [Persist]
    public string Name { get; }

    [Persist]
    public string Color { get; }

    public bool IsClean { get; set; }

    public LaundryBasket? Basket { get; set; }
}

Модель, созданная из этих типов сущностей:

Model:
  EntityType: Garment
    Properties:
      _id (_id, int) Required PK AfterSave:Throw ValueGenerated.OnAdd
      Basket_id (no field, int?) Shadow FK Index
      Color (string) Required
      Name (string) Required
      TenantId (int) Required
    Navigations:
      Basket (LaundryBasket) ToPrincipal LaundryBasket Inverse: Garments
    Keys:
      _id PK
    Foreign keys:
      Garment {'Basket_id'} -> LaundryBasket {'_id'} ToDependent: Garments ToPrincipal: Basket ClientSetNull
    Indexes:
      Basket_id
  EntityType: LaundryBasket
    Properties:
      _id (_id, int) Required PK AfterSave:Throw ValueGenerated.OnAdd
      TenantId (int) Required
    Navigations:
      Garments (List<Garment>) Collection ToDependent Garment Inverse: Basket
    Keys:
      _id PK

Как правило, IsClean было бы сопоставлено, но, поскольку оно не отмечено с [Persist], теперь оно рассматривается как несопоставленное свойство.

Подсказка

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

Рекомендации по реализации соглашений

EF Core отслеживает способ создания каждой части конфигурации. Это представлено перечислением ConfigurationSource . Различные типы конфигурации:

  • Explicit: элемент модели был явно настроен в OnModelCreating
  • DataAnnotation: элемент модели был настроен с помощью атрибута сопоставления (также известного как аннотация данных) в типе CLR
  • Convention: элемент модели был настроен согласно соглашению о построении модели

Соглашения никогда не должны переопределять конфигурацию, помеченную как DataAnnotation или Explicit. Это достигается с помощью построителя соглашений, например IConventionPropertyBuilder, который получается из свойства Builder. Рассмотрим пример.

property.Builder.HasMaxLength(512);

Вызов HasMaxLength построителя соглашений будет задавать только максимальную длину, если она еще не настроена атрибутом сопоставления или в OnModelCreating.

Методы создания, подобные этому, также имеют второй параметр: fromDataAnnotation. Установите это значение true, если соглашение делает конфигурацию от имени атрибута сопоставления. Рассмотрим пример.

property.Builder.HasMaxLength(512, fromDataAnnotation: true);

Это устанавливает ConfigurationSource в DataAnnotation, что означает, что значение теперь может быть переопределено явным сопоставлением на OnModelCreating, но не с помощью атрибутных соглашений, не связанных с сопоставлением.

Если текущая конфигурация не может быть переопределена, метод возвращается null, это необходимо учитывать, если вам нужно выполнить дополнительную настройку:

property.Builder.HasMaxLength(512)?.IsUnicode(false);

Обратите внимание, что если конфигурация юникода не может быть переопределена, максимальная длина по-прежнему будет задана. В случае, если необходимо настроить аспекты только при успешном выполнении обоих вызовов, можно предварительно проверить это путем вызова CanSetMaxLength и CanSetIsUnicode:

public class MaxStringLengthNonUnicodeConvention : IModelFinalizingConvention
{
    public void ProcessModelFinalizing(IConventionModelBuilder modelBuilder, IConventionContext<IConventionModelBuilder> context)
    {
        foreach (var property in modelBuilder.Metadata.GetEntityTypes()
                     .SelectMany(
                         entityType => entityType.GetDeclaredProperties()
                             .Where(
                                 property => property.ClrType == typeof(string))))
        {
            var propertyBuilder = property.Builder;
            if (propertyBuilder.CanSetMaxLength(512)
                && propertyBuilder.CanSetIsUnicode(false))
            {
                propertyBuilder.HasMaxLength(512)!.IsUnicode(false);
            }
        }
    }
}

Здесь мы можем быть уверены, что вызов HasMaxLength не вернет null. По-прежнему рекомендуется использовать экземпляр построителя, возвращаемый из HasMaxLength, так как он может отличаться от propertyBuilder.

Замечание

Другие соглашения не активируются сразу после того, как соглашение вносит изменения, они задерживаются до тех пор, пока все соглашения не завершат обработку текущего изменения.

IConventionContext

Все методы соглашения также имеют IConventionContext<TMetadata> параметр. Он предоставляет методы, которые могут быть полезны в некоторых конкретных случаях.

Пример: соглашение NotMappedAttribute

Это соглашение ищет NotMappedAttribute на типе, который добавляется в модель, и пытается удалить этот тип сущности из модели. Но если тип сущности удаляется из модели, то любые другие соглашения, реализующие ProcessEntityTypeAdded больше не нужно выполнять. Это можно сделать путем вызова StopProcessing():

public virtual void ProcessEntityTypeAdded(
    IConventionEntityTypeBuilder entityTypeBuilder,
    IConventionContext<IConventionEntityTypeBuilder> context)
{
    var type = entityTypeBuilder.Metadata.ClrType;
    if (!Attribute.IsDefined(type, typeof(NotMappedAttribute), inherit: true))
    {
        return;
    }

    if (entityTypeBuilder.ModelBuilder.Ignore(entityTypeBuilder.Metadata.Name, fromDataAnnotation: true) != null)
    {
        context.StopProcessing();
    }
}

IConventionModel

Каждый объект строителя, переданный в рамках соглашения, содержит Metadata свойство, которое предоставляет низкоуровневый доступ к объектам, составляющим модель. В частности, существуют методы, позволяющие выполнять итерацию определенных объектов в модели и применять к ним общую конфигурацию, как показано в примере: длина по умолчанию для всех строковых свойств. Этот API похож на IMutableModel показанную в массовой конфигурации.

Осторожность

Рекомендуется всегда выполнять настройку, вызывая методы построителя, предоставляемые в качестве Builder свойства, так как построитель проверяет, переопределяет ли данная конфигурация то, что уже было указано с помощью API Fluent или заметок данных.

Когда следует использовать каждый подход для массовой настройки

Используйте API метаданных , когда:

  • Конфигурация должна применяться в определенное время и не реагировать на последующие изменения в модели.
  • Скорость построения модели очень важна. API метаданных имеет меньше проверок безопасности, поэтому может быть немного быстрее, чем другие подходы, однако использование скомпилированной модели даст еще лучшее время запуска.

Используйте конфигурацию модели предварительного соглашения , если:

  • Условие применимости просто, так как оно зависит только от типа.
  • Конфигурация должна применяться в любой момент, когда свойство заданного типа добавляется в модель и переопределяет заметки и соглашения о данных.

Используйте соглашения о завершении , когда:

  • Условие применимости сложно.
  • Конфигурация не должна переопределять то, что указано аннотациями данных.

Используйте интерактивные соглашения , когда:

  • Несколько конвенций зависят друг от друга. Завершение работы заключительных соглашений выполняется в том порядке, в который они были добавлены, и поэтому не способны реагировать на изменения, внесенные в последующие заключительные соглашения.
  • Логика разделяется между несколькими контекстами. Интерактивные соглашения более безопасны, чем другие подходы.