모델 기반 규칙

참고 항목

EF6 이상만 - 이 페이지에서 다루는 기능, API 등은 Entity Framework 6에 도입되었습니다. 이전 버전을 사용하는 경우 이 정보의 일부 또는 전체가 적용되지 않습니다.

모델 기반 규칙은 규칙 기반 모델 구성의 고급 방법입니다. 대부분의 시나리오에서는 DbModelBuilder의 사용자 지정 Code First 규칙 API를 사용해야 합니다. 모델 기반 규칙을 사용하기 전에 규칙에 대한 DbModelBuilder API를 이해하는 것이 좋습니다.

모델 기반 규칙을 사용하면 표준 규칙을 통해 구성할 수 없는 속성 및 테이블에 영향을 주는 규칙을 만들 수 있습니다. 이러한 예제는 계층당 하나의 모델 테이블의 판별자 열 및 독립 연결 열입니다.

규칙 만들기

모델 기반 규칙을 만드는 첫 번째 단계는 파이프라인에서 규칙을 모델에 적용해야 할 경우를 선택하는 것입니다. 모델 규칙에는 Conceptual(C-Space) 및 Store(S-Space)의 두 가지 유형이 있습니다. C-Space 규칙은 애플리케이션이 빌드하는 모델에 적용되는 반면, S-Space 규칙은 데이터베이스를 나타내고 자동으로 생성된 열의 이름을 지정하는 방법과 같은 항목을 제어하는 모델 버전에 적용됩니다.

모델 규칙은 IConceptualModelConvention 또는 IStoreModelConvention에서 확장하는 클래스입니다. 이러한 인터페이스는 모두 규칙이 적용되는 데이터 형식을 필터링하는 데 사용되는 MetadataItem 형식일 수 있는 제네릭 형식을 허용합니다.

규칙 추가

모델 규칙은 일반 규칙 클래스와 동일한 방식으로 추가됩니다. OnModelCreating 메서드에서 모델에 대한 규칙 목록에 규칙을 추가합니다.

using System.Data.Entity;
using System.Data.Entity.Core.Metadata.Edm;
using System.Data.Entity.Infrastructure;
using System.Data.Entity.ModelConfiguration.Conventions;

public class BlogContext : DbContext  
{  
    public DbSet<Post> Posts { get; set; }  
    public DbSet<Comment> Comments { get; set; }  

    protected override void OnModelCreating(DbModelBuilder modelBuilder)  
    {  
        modelBuilder.Conventions.Add<MyModelBasedConvention>();  
    }  
}

Conventions.AddBefore<> 또는 Conventions.AddAfter<> 메서드를 사용하여 다른 규칙과 관련하여 규칙을 추가할 수도 있습니다. Entity Framework가 적용되는 규칙에 대한 자세한 내용은 참고 섹션을 참조하세요.

protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
    modelBuilder.Conventions.AddAfter<IdKeyDiscoveryConvention>(new MyModelBasedConvention());
}

예: 판별자 모델 규칙

EF에서 생성된 열의 이름 바꾸기는 다른 규칙 API로는 수행할 수 없는 작업의 예입니다. 모델 규칙을 사용하는 것이 유일한 옵션인 상황입니다.

모델 기반 규칙을 사용하여 생성된 열을 구성하는 방법의 예는 판별자 열의 이름을 지정하는 방법을 사용자 지정하는 것입니다. 다음은 "판별자"라는 모델의 모든 열 이름을 "EntityType"으로 바꾸는 간단한 모델 기반 규칙의 예입니다. 여기에는 개발자가 단순히 "판별자"라고 명명한 열이 포함됩니다. "판별자" 열은 생성된 열이므로 S-Space에서 실행해야 합니다.

using System.Data.Entity;
using System.Data.Entity.Core.Metadata.Edm;
using System.Data.Entity.Infrastructure;
using System.Data.Entity.ModelConfiguration.Conventions;

class DiscriminatorRenamingConvention : IStoreModelConvention<EdmProperty>  
{  
    public void Apply(EdmProperty property, DbModel model)  
    {            
        if (property.Name == "Discriminator")  
        {  
            property.Name = "EntityType";  
        }  
    }  
}

예: 일반 IA 이름 바꾸기 규칙

작동 중인 모델 기반 규칙의 또 다른 복잡한 예는 IA(독립 연결)의 이름을 지정하는 방법을 구성하는 것입니다. IA가 EF에 의해 생성되고 DbModelBuilder API가 액세스할 수 있는 모델에 없기 때문에 이는 모델 규칙이 적용 가능한 상황입니다.

EF는 IA를 생성할 때 EntityType_KeyName이라는 열을 만듭니다. 예를 들어 CustomerId라는 키 열이 있는 Customer라는 연결의 경우 Customer_CustomerId라는 열을 생성합니다. 다음 규칙은 IA에 대해 생성된 열 이름에서 '_' 문자를 제거합니다.

using System.Data.Entity;
using System.Data.Entity.Core.Metadata.Edm;
using System.Data.Entity.Infrastructure;
using System.Data.Entity.ModelConfiguration.Conventions;

// Provides a convention for fixing the independent association (IA) foreign key column names.  
public class ForeignKeyNamingConvention : IStoreModelConvention<AssociationType>
{

    public void Apply(AssociationType association, DbModel model)
    {
        // Identify ForeignKey properties (including IAs)  
        if (association.IsForeignKey)
        {
            // rename FK columns  
            var constraint = association.Constraint;
            if (DoPropertiesHaveDefaultNames(constraint.FromProperties, constraint.ToRole.Name, constraint.ToProperties))
            {
                NormalizeForeignKeyProperties(constraint.FromProperties);
            }
            if (DoPropertiesHaveDefaultNames(constraint.ToProperties, constraint.FromRole.Name, constraint.FromProperties))
            {
                NormalizeForeignKeyProperties(constraint.ToProperties);
            }
        }
    }

    private bool DoPropertiesHaveDefaultNames(ReadOnlyMetadataCollection<EdmProperty> properties, string roleName, ReadOnlyMetadataCollection<EdmProperty> otherEndProperties)
    {
        if (properties.Count != otherEndProperties.Count)
        {
            return false;
        }

        for (int i = 0; i < properties.Count; ++i)
        {
            if (!properties[i].Name.EndsWith("_" + otherEndProperties[i].Name))
            {
                return false;
            }
        }
        return true;
    }

    private void NormalizeForeignKeyProperties(ReadOnlyMetadataCollection<EdmProperty> properties)
    {
        for (int i = 0; i < properties.Count; ++i)
        {
            int underscoreIndex = properties[i].Name.IndexOf('_');
            if (underscoreIndex > 0)
            {
                properties[i].Name = properties[i].Name.Remove(underscoreIndex, 1);
            }                 
        }
    }
}

기존 규칙 확장

Entity Framework가 이미 모델에 적용하는 규칙 중 하나와 유사한 규칙을 작성해야 하는 경우 해당 규칙을 항상 확장하여 처음부터 다시 작성할 필요가 없도록 할 수 있습니다. 이 예에서는 기존 ID 일치 규칙을 사용자 지정 규칙으로 바꿉니다. 키 규칙 재정의의 추가 이점은 재정의된 메서드가 이미 검색되거나 명시적으로 구성된 키가 없는 경우에만 호출된다는 것입니다. Entity Framework에서 사용하는 규칙 목록은 http://msdn.microsoft.com/library/system.data.entity.modelconfiguration.conventions.aspx에서 확인할 수 있습니다.

using System.Data.Entity;
using System.Data.Entity.Core.Metadata.Edm;
using System.Data.Entity.Infrastructure;
using System.Data.Entity.ModelConfiguration.Conventions;
using System.Linq;  

// Convention to detect primary key properties.
// Recognized naming patterns in order of precedence are:
// 1. 'Key'
// 2. [type name]Key
// Primary key detection is case insensitive.
public class CustomKeyDiscoveryConvention : KeyDiscoveryConvention
{
    private const string Id = "Key";

    protected override IEnumerable<EdmProperty> MatchKeyProperty(
        EntityType entityType, IEnumerable<EdmProperty> primitiveProperties)
    {
        Debug.Assert(entityType != null);
        Debug.Assert(primitiveProperties != null);

        var matches = primitiveProperties
            .Where(p => Id.Equals(p.Name, StringComparison.OrdinalIgnoreCase));

        if (!matches.Any())
       {
            matches = primitiveProperties
                .Where(p => (entityType.Name + Id).Equals(p.Name, StringComparison.OrdinalIgnoreCase));
        }

        // If the number of matches is more than one, then multiple properties matched differing only by
        // case--for example, "Key" and "key".  
        if (matches.Count() > 1)
        {
            throw new InvalidOperationException("Multiple properties match the key convention");
        }

        return matches;
    }
}

그런 다음 기존 키 규칙 전에 새 규칙을 추가해야 합니다. CustomKeyDiscoveryConvention을 추가한 후 IdKeyDiscoveryConvention을 제거할 수 있습니다. 기존 IdKeyDiscoveryConvention을 제거하지 않은 경우 이 규칙은 먼저 실행되기 때문에 ID 검색 규칙보다 우선하지만 "키" 속성이 없는 경우 "id" 규칙이 실행됩니다. 예를 들어 이전 규칙이 사용자 지정 규칙에 관심 있는 항목과 일치하도록 열 이름을 업데이트한 경우(이름이 관심이 없었던 경우) 해당 열에 적용되도록 각 규칙이 모델을 이전 규칙에 의해 업데이트된 것으로 간주하기 때문에 이 동작이 표시됩니다.

public class BlogContext : DbContext
{
    public DbSet<Post> Posts { get; set; }
    public DbSet<Comment> Comments { get; set; }

    protected override void OnModelCreating(DbModelBuilder modelBuilder)
    {
        modelBuilder.Conventions.AddBefore<IdKeyDiscoveryConvention>(new CustomKeyDiscoveryConvention());
        modelBuilder.Conventions.Remove<IdKeyDiscoveryConvention>();
    }
}

주의

Entity Framework에서 현재 적용되는 규칙 목록은 MSDN 설명서(http://msdn.microsoft.com/library/system.data.entity.modelconfiguration.conventions.aspx)에서 제공됩니다. 이 목록은 소스 코드에서 직접 가져옵니다. Entity Framework 6의 소스 코드는 GitHub에서 사용할 수 있으며 Entity Framework에서 사용하는 많은 규칙은 사용자 지정 모델 기반 규칙에 적합한 시작점입니다.