Share via


モデルベースの規則

Note

EF6 以降のみ - このページで説明する機能、API などは、Entity Framework 6 で導入されました。 以前のバージョンを使用している場合、一部またはすべての情報は適用されません。

モデル ベースの規則は、規則ベースのモデル構成の高度な方法です。 ほとんどのシナリオでは、DbModelBuilder の Code First カスタム規則 API を使用する必要があります。 モデル ベースの規則を使用する前に、規則に対する DbModelBuilder API について理解することをお勧めします。

モデル ベースの規則を使用すると、標準の規則では構成できないプロパティとテーブルに影響を与える規則を作成することができます。 これらの例としては、階層モデルごとのテーブル内の識別子列と、独立した関連付け列があります。

規則の作成

モデル ベースの規則を作成する最初の手順は、パイプラインのどの段階でモデルに規則を適用するかを選択することです。 モデルの規則には、概念 (C 空間) とストア (S 空間) の 2 種類があります。 C 空間規則は、アプリケーションが構築するモデルに適用されます。一方、S 空間規則は、データベースを表すモデルのバージョンに適用され、自動的に生成される列の名前付けといったことを制御します。

モデルの規則は、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 では実行できないことの一例です。 この場合は、モデルの規則を使用する以外に方法はありません。

モデル ベースの規則を使用して生成済みの列を構成する方法の例としては、識別子列の命名方法のカスタマイズがあります。 モデル内の "Discriminator" という名前の列をすべて "EntityType" という名前に変更する単純なモデル ベースの規則の例を次に示します。 ここには、開発者が単純に "Discriminator" と名付けた列が含まれています。 "Discriminator" 列は生成された列なので、S 空間で実行する必要があります。

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 の名前変更規則

実際のモデル ベースの規則のもう 1 つのより複雑な例としては、独立した関連付け (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 検出規則よりも優先されますが、"key" プロパティが見つからない場合は、"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 で使用される規則の多くは、カスタム モデル ベースの規則を初めて使用する場合に適しています。