模型型慣例
注意
僅限 EF6 及更新版本 - Entity Framework 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>();
}
}
您也可以使用 Convention.AddBefore <> 或 Convention.AddAfter <> 方法,將慣例新增至另一個慣例。 如需 Entity Framework 所套用慣例的詳細資訊,請參閱附注一節。
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Conventions.AddAfter<IdKeyDiscoveryConvention>(new MyModelBasedConvention());
}
範例:歧視性模型慣例
重新命名 EF 所產生的資料行是您無法使用其他慣例 API 執行的範例。 這是使用模型慣例是唯一選項的情況。
如何使用模型型慣例來設定產生的資料行的範例,就是自訂具名歧視性資料行的方式。 以下是簡單模型型慣例的範例,該慣例會將名為 「Discriminator」 之模型中的每個資料行重新命名為 「EntityType」。 這包括開發人員直接命名為 「Discriminator」 的資料行。 由於 「歧視性」資料行是產生的資料行,因此必須在 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) 命名的方式。 這是模型慣例適用的情況,因為 EF 會產生 IA,而且不存在於 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 已套用至模型之一慣例的慣例,您一律可以擴充該慣例,以避免必須從頭重寫。 其中一個範例是將現有的識別碼比對慣例取代為自訂識別碼。 覆寫金鑰慣例的新增優點是,只有在尚未偵測到或明確設定金鑰時,才會呼叫覆寫的方法。 您可以在這裡取得 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,此慣例仍會優先于識別碼探索慣例,因為它會先執行,但在找不到 「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 檔中提供 Entity Framework 目前套用的慣例清單: http://msdn.microsoft.com/library/system.data.entity.modelconfiguration.conventions.aspx 。 此清單會直接從我們的原始程式碼提取。 Entity Framework 6 的原始程式碼可在 GitHub 上使用 ,而 Entity Framework 所使用的許多慣例都是自訂模型型慣例的良好起點。