대량 구성 모델링

여러 엔터티 형식에서 동일한 방식으로 측면을 구성해야 하는 경우 다음 기술을 통해 코드 중복을 줄이고 논리를 통합할 수 있습니다.

아래에 표시된 코드 조각이 포함된 전체 샘플 프로젝트를 참조하세요.

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의 단점

  • Fluent API와 달리 모델에 대한 모든 수정을 명시적으로 수행해야 합니다. 예를 들어 Currency 속성 중 일부가 규칙에 따라 탐색으로 구성된 경우 먼저 CLR 속성을 참조하는 탐색을 제거한 후 엔터티 형식 속성을 추가해야 합니다. #9117에서 이 문제를 개선합니다.
  • 규칙은 각 변경 후 실행됩니다. 규칙에 의해 검색된 탐색을 제거하면 규칙이 다시 실행되고 다시 추가할 수 있습니다. 이 문제가 발생하지 않도록 하려면 DelayConventions()를 호출하고 나중에 반환된 개체를 삭제하여 속성이 추가될 때까지 규칙을 지연하거나 AddIgnored를 사용하여 CLR 속성을 무시된 것으로 표시해야 합니다.
  • 이 반복이 수행된 후에 엔터티 형식이 추가될 수 있으며 구성이 적용되지 않습니다. 일반적으로 이 코드를 OnModelCreating의 끝에 배치하여 방지할 수 있지만 상호 종속된 구성 집합이 두 개 있는 경우 일관되게 적용할 수 있는 순서가 없을 수 있습니다.

사전 규칙 구성

EF Core를 사용하면 지정된 CLR 형식에 대해 매핑 구성을 한 번 지정할 수 있습니다. 그런 다음 해당 구성이 검색될 때 모델에서 해당 형식의 모든 속성에 적용됩니다. 모델 빌드 규칙을 실행하도록 허용하기 전에 모델의 측면을 구성하기 때문에 이를 "사전 규칙 모델 구성"이라고 합니다. 이러한 구성은 DbContext에서 파생된 형식에서 ConfigureConventions를 재정의하여 적용됩니다.

이 예제에서는 값 변환기를 가지도록 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. 정확한 형식

Important

사전 규칙 구성은 일치하는 개체가 모델에 추가되는 즉시 적용되는 명시적 구성과 동일합니다. 모든 규칙 및 데이터 주석을 재정의합니다. 예를 들어 위의 구성에서 모든 문자열 외래 키 속성은 주 키와 일치하지 않는 경우에도 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을 구현하는 규칙이 트리거됩니다. 마찬가지로 키 또는 외래 키가 모델에 추가될 때마다 IForeignKeyAddedConventionIKeyAddedConvention을 모두 구현하는 규칙이 트리거됩니다.

모델 빌드 규칙은 모델 구성을 제어하는 강력한 방법이지만 복잡하고 제대로 하기 어려울 수 있습니다. 대부분의 경우 사전 규칙 모델 구성을 대신 사용하여 속성 및 형식에 대한 공통 구성을 쉽게 지정할 수 있습니다.

새 규칙 추가

예: 판별자 속성의 길이 제한

계층당 하나의 테이블 상속 매핑 전략에는 지정된 행에 표시되는 형식을 지정하기 위해 판별자 열이 필요합니다. 기본적으로 EF는 판별자용으로 바인딩되지 않은 문자열 열을 사용하므로 모든 판별자 길이에 대해 작동합니다. 그러나 판별자 문자열의 최대 길이를 제한하면 스토리지 및 쿼리의 효율성을 높일 수 있습니다. 그렇게 할 새 규칙을 만들어 보겠습니다.

EF Core 모델 빌드 규칙은 빌드할 때 모델의 변경 내용에 따라 트리거됩니다. 이렇게 하면 명시적 구성이 이루어지고, 매핑 특성이 적용되고, 다른 규칙이 실행되므로 모델이 최신 상태로 유지됩니다. 이에 참여하기 위해 모든 규칙은 규칙이 트리거되는 시기를 결정하는 하나 이상의 인터페이스를 구현합니다. 예를 들어 새 엔터티 형식이 모델에 추가될 때마다 IEntityTypeAddedConvention을 구현하는 규칙이 트리거됩니다. 마찬가지로 키 또는 외래 키가 모델에 추가될 때마다 IForeignKeyAddedConventionIKeyAddedConvention을 모두 구현하는 규칙이 트리거됩니다.

한 지점에서 모델에 대한 구성이 나중에 변경되거나 제거될 수 있으므로 구현할 인터페이스를 아는 것은 까다로울 수 있습니다. 예를 들어 규칙에 따라 키를 만들 수 있지만 나중에 다른 키를 명시적으로 구성할 때 바뀝니다.

판별자 길이 규칙을 처음 구현하여 좀 더 구체적으로 만들어 보겠습니다.

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을 구현합니다. 즉, 엔터티 형식에 대한 매핑된 상속 계층 구조가 변경될 때마다 트리거됩니다. 그런 다음 규칙은 계층 구조에 대한 문자열 판별자 속성을 찾아 구성합니다.

이 규칙은 ConfigureConventions에서 Add를 호출하여 사용됩니다.

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이며, 이는 사용 중인 가장 긴 판별자 값인 "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)

참고 항목

사전 규칙 구성에서 동일한 작업을 수행할 수 있지만 규칙을 사용하면 적용 가능한 속성을 추가로 필터링하고 데이터 주석이 구성을 재정의할 수 있습니다.

마지막으로 이 예제를 나가기 전에 MaxStringLengthConventionDiscriminatorLengthConvention3을 동시에 사용하면 어떻게 되나요? 그 대답은 모델 마무리 규칙이 추가된 순서대로 실행되기 때문에 추가되는 순서에 따라 달라집니다. 따라서 MaxStringLengthConvention이 마지막으로 추가되면 마지막으로 실행되고 판별자 속성의 최대 길이가 512로 설정됩니다. 따라서 이 경우 다른 모든 문자열 속성을 512로 유지하면서 판별자 속성에 대한 기본 최대 길이를 재정의할 수 있도록 DiscriminatorLengthConvention3을 마지막으로 추가하는 것이 좋습니다.

기존 규칙 바꾸기

때로는 기존 규칙을 완전히 제거하는 대신 기본적으로 동일한 작업을 수행하지만 동작이 변경된 규칙으로 대체하려고 합니다. 이는 기존 규칙이 적절하게 트리거해야 하는 인터페이스를 이미 구현하기 때문에 유용합니다.

예: 옵트인 속성 매핑

EF Core는 모든 공용 읽기-쓰기 속성을 규칙에 따라 매핑합니다. 엔터티 형식이 정의된 방식에는 적합하지 않을 수 있습니다. 이를 변경하려면 OnModelCreating에 명시적으로 매핑되거나 Persist라는 새 특성으로 표시되지 않는 한 속성을 매핑하지 않는 고유한 구현으로 PropertyDiscoveryConvention을 바꿀 수 있습니다.

[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;
            }
        }
    }
}

기본 제공 규칙을 바꿀 때 새 규칙 구현은 기존 규칙 클래스에서 상속되어야 합니다. 일부 규칙에는 관계형 또는 공급자별 구현이 있으며, 이 경우 새 규칙 구현은 사용 중인 데이터베이스 공급자에 대한 가장 구체적인 기존 규칙 클래스에서 상속되어야 합니다.

그런 다음 ConfigureConventionsReplace 메서드를 사용하여 규칙이 등록됩니다.

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(으)로 표시된 구성을 재정의하면 안 됩니다. 이는 Builder 속성에서 가져온 규칙 작성기(예: IConventionPropertyBuilder)를 사용하여 수행됩니다. 예시:

property.Builder.HasMaxLength(512);

규칙 작성기에서 HasMaxLength를 호출하면 매핑 특성 또는 OnModelCreating에서 아직 구성되지 않은 경우에만 최대 길이가 설정됩니다.

이와 같은 작성기 메서드에는 두 번째 매개 변수 fromDataAnnotation도 있습니다. 규칙이 매핑 특성을 대신하여 구성을 만드는 경우 true로 설정합니다. 예시:

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

이렇게 하면 ConfigurationSourceDataAnnotation으로 설정됩니다. 즉, 이제 OnModelCreating에 대한 명시적 매핑을 통해 값을 재정의할 수 있지만 매핑이 아닌 특성 규칙에 의해 재정의될 수 있습니다.

현재 구성을 재정의할 수 없는 경우 메서드는 null을 반환합니다. 추가 구성을 수행해야 하는 경우 이를 고려해야 합니다.

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

유니코드 구성을 재정의할 수 없는 경우 최대 길이는 계속 설정됩니다. 두 호출이 모두 성공한 경우에만 패싯을 구성해야 하는 경우 CanSetMaxLengthCanSetIsUnicode를 호출하고 이를 선제적으로 확인할 수 있습니다.

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을 반환하지 않도록 할 수 있습니다. propertyBuilder와 다를 수 있으므로 HasMaxLength에서 반환된 작성기 인스턴스를 사용하는 것이 좋습니다.

참고 항목

다른 규칙은 규칙이 변경된 직후에 트리거되지 않으며 모든 규칙이 현재 변경 내용 처리를 완료할 때까지 지연됩니다.

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과 유사합니다.

주의

작성기는 지정된 구성이 Fluent API 또는 데이터 주석을 사용하여 이미 지정된 항목을 재정의하는지 여부를 확인하므로 항상 Builder 속성으로 노출된 작성기에서 메서드를 호출하여 구성을 수행하는 것이 좋습니다.

대량 구성에 각 방법을 사용하는 경우

다음과 같은 경우 메타데이터 API를 사용합니다.

  • 구성은 특정 시간에 적용해야 하며 모델의 이후 변경 내용에 반응하지 않습니다.
  • 모델 빌드 속도는 매우 중요합니다. 메타데이터 API는 안전 검사가 적기 때문에 다른 방법보다 약간 더 빠를 수 있지만 컴파일된 모델을 사용하면 시작 시간이 훨씬 더 빨라집니다.

다음과 같은 경우 사전 규칙 모델 구성을 사용합니다.

  • 적용 가능성 조건은 형식에 따라서만 달라지기 때문에 간단합니다.
  • 지정된 형식의 속성이 모델에 추가되고 데이터 주석 및 규칙을 재정의할 때 언제든지 구성을 적용해야 합니다.

다음과 같은 경우 종료 규칙을 사용합니다.

  • 적용 가능성 조건은 복잡합니다.
  • 구성은 데이터 주석으로 지정된 항목을 재정의해서는 안 됩니다.

다음과 같은 경우 대화형 규칙을 사용합니다.

  • 여러 규칙은 서로에 따라 달라집니다. 종료 규칙은 추가된 순서대로 실행되므로 나중에 규칙을 마무리하여 변경한 내용에 대응할 수 없습니다.
  • 논리는 여러 컨텍스트 간에 공유됩니다. 대화형 규칙은 다른 방법보다 안전합니다.