通过


基于模型的惯例

注释

EF6 及更高版本 - 实体框架 6 中介绍了本页中讨论的功能、API 等。 如果使用早期版本,某些或全部信息不适用。

模型驱动的约定是一种约定驱动的高级模型配置方法。 在大多数情况下,应使用 DbModelBuilder 上的自定义代码优先约定 API 。 在使用基于模型的约定之前,建议了解 DbModelBuilder API 的约定。

基于模型的约定允许创建影响无法通过标准约定配置的属性和表的约定。 这些示例包括每个层次结构模型表中的歧视性列和独立关联列。

创建约定

创建基于模型的规约的第一步是选择在管道的哪个阶段需要将规约应用到模型上。 有两种类型的模型约定:概念(C-Space)和存储(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 重命名约定

另一个更复杂的基于模型的约定示例是配置独立关联关系(IAs)的命名方式。 这是模型约定适用的情况,因为 IA 是由 EF 生成的,并且不存在于 DbModelBuilder API 可以访问的模型中。

当 EF 生成 IA 时,它会创建一个名为EntityType_KeyName的列。 例如,对于一个名为 Customer 的关联,其中有一个名为 CustomerId 的关键列,它将生成一个名为 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 匹配约定替换为自定义约定。 替代密钥约定的一个附加好处是,仅当尚未检测到或显式配置密钥时,重写的方法才会被调用。 实体框架使用的约定列表如下: 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>();
    }
}

备注

以下 MSDN 文档中提供了实体框架当前应用的约定列表: http://msdn.microsoft.com/library/system.data.entity.modelconfiguration.conventions.aspx 此列表直接从源代码中提取。 Entity Framework 6 的源代码在 GitHub 上提供,实体框架使用的许多约定都是基于自定义模型的约定的一个很好的起点。