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

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

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

Массовая конфигурация в 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 7.0 появились соглашения о создании пользовательских моделей.

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

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

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

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

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

Соглашения о сборке моделей EF Core активируются на основе изменений, внесенных в модель по мере его создания. Благодаря этому модель обновляется так, как выполняется явная конфигурация, применяются атрибуты сопоставления и выполняются другие соглашения. Для участия в этом каждом соглашении реализуется один или несколько интерфейсов, определяющих, когда будет активировано соглашение. Например, соглашение, реализующее 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. Соглашения о завершении модели выполняются после завершения всей другой сборки модели и поэтому имеют доступ к ближайшему состоянию модели. Это отличается от интерактивных соглашений, которые реагируют на изменение каждой 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, что является длиной "Функция", самой длинной дискриминационным значением в использовании.

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: элемент модели был настроен с помощью атрибута сопоставления (заметка к данным aka) в типе CLR
  • Convention: элемент модели был настроен соглашением о сборке модели

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

property.Builder.HasMaxLength(512);

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

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

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

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

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

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

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

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

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

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