2013-02-16 20 views
4

對於實體框架中代碼優先的數據模型的用途,我有點困惑。因爲EF會自動從頭開始爲你生成一個數據庫,如果它只用了數據模型(包括數據註釋和Fluent API的東西,在DbContext.OnModelCreating),它就不存在了,我假定數據模型應該完全描述你的數據庫的結構,並且在那之後你不需要修改任何基本的東西。EF代碼優先模型是否旨在完整地描述數據庫的結構?

但是,我遇到了this Codeplex issue,其中一位EF Triage團隊成員建議在數據遷移中添加自定義索引,但不能將其作爲數據模型字段的註釋或Fluent API代碼。

但這並不意味着從頭開始自動生成數據庫的任何人都不會將這些自定義索引添加到他們的數據庫中嗎?這個假設似乎是,一旦你開始使用數據遷移,你永遠不會再從頭開始創建數據庫。如果你在一個團隊中工作,並且新的團隊成員伴隨着新的SQL Server安裝,該怎麼辦?你是否期望從另一個團隊成員複製數據庫?如果你想開始使用新的DBMS,比如Postgres呢?我認爲關於EF的一個很酷的事情是它獨立於數據庫管理系統,但如果你不再能夠從頭開始創建數據庫,那麼你就不能再以獨立於數據庫管理系統的方式做事了。

由於我上面概述的原因,不會在數據遷移中添加自定義索引,但不會在數據模型中添加自定義索引是個壞主意?對於這個問題,不會在遷移中添加任何數據庫結構更改,但不會在數據模型中添加更改是個壞主意?

+0

當然不是。 SQL Server數據庫首先提供的不僅僅是EF代碼的通用功能。 – usr 2013-02-16 18:25:13

回答

3

是EF代碼優先模型,是爲了完全描述數據庫的結構嗎?

沒有,他們並不完全描述數據庫結構或schema.Still有方法,使數據庫使用EF充分說明。它們如下:

您可以在Database類上使用新的CTP5的ExecuteSqlCommand方法,該方法允許對數據庫執行原始SQL命令。

最好的地方調用SqlCommand方法用於此目的是Seed方法已經在自定義Initializer類中重寫內。例如:

protected override void Seed(EntityMappingContext context) 
{ 
    context.Database.ExecuteSqlCommand("CREATE INDEX IX_NAME ON ..."); 
} 

您甚至可以通過這種方式添加唯一約束。 這不是一種解決方法,但會在生成數據庫時執行。

OR

如果你急需的屬性,然後在這裏它去

[AttributeUsage(AttributeTargets.Property, Inherited = false, AllowMultiple = true)] 
public class IndexAttribute : Attribute 
{ 
    public IndexAttribute(string name, bool unique = false) 
    { 
     this.Name = name; 
     this.IsUnique = unique; 
    } 

    public string Name { get; private set; } 

    public bool IsUnique { get; private set; } 
} 

在此之後,你將有一個初始化,你會在你的OnModelCreating方法如下調用:

public class IndexInitializer<T> : IDatabaseInitializer<T> where T : DbContext 
{ 
    private const string CreateIndexQueryTemplate = "CREATE {unique} INDEX {indexName} ON {tableName} ({columnName});"; 

    public void InitializeDatabase(T context) 
    { 
     const BindingFlags PublicInstance = BindingFlags.Public | BindingFlags.Instance; 
     Dictionary<IndexAttribute, List<string>> indexes = new Dictionary<IndexAttribute, List<string>>(); 
     string query = string.Empty; 

     foreach (var dataSetProperty in typeof(T).GetProperties(PublicInstance).Where(p => p.PropertyType.Name == typeof(DbSet<>).Name)) 
     { 
      var entityType = dataSetProperty.PropertyType.GetGenericArguments().Single(); 
      TableAttribute[] tableAttributes = (TableAttribute[])entityType.GetCustomAttributes(typeof(TableAttribute), false); 

      indexes.Clear(); 
      string tableName = tableAttributes.Length != 0 ? tableAttributes[0].Name : dataSetProperty.Name; 

      foreach (PropertyInfo property in entityType.GetProperties(PublicInstance)) 
      { 
       IndexAttribute[] indexAttributes = (IndexAttribute[])property.GetCustomAttributes(typeof(IndexAttribute), false); 
       NotMappedAttribute[] notMappedAttributes = (NotMappedAttribute[])property.GetCustomAttributes(typeof(NotMappedAttribute), false); 
       if (indexAttributes.Length > 0 && notMappedAttributes.Length == 0) 
       { 
        ColumnAttribute[] columnAttributes = (ColumnAttribute[])property.GetCustomAttributes(typeof(ColumnAttribute), false); 

        foreach (IndexAttribute indexAttribute in indexAttributes) 
        { 
         if (!indexes.ContainsKey(indexAttribute)) 
         { 
          indexes.Add(indexAttribute, new List<string>()); 
         } 

         if (property.PropertyType.IsValueType || property.PropertyType == typeof(string)) 
         { 
          string columnName = columnAttributes.Length != 0 ? columnAttributes[0].Name : property.Name; 
          indexes[indexAttribute].Add(columnName); 
         } 
         else 
         { 
          indexes[indexAttribute].Add(property.PropertyType.Name + "_" + GetKeyName(property.PropertyType)); 
         } 
        } 
       } 
      } 

      foreach (IndexAttribute indexAttribute in indexes.Keys) 
      { 
       query += CreateIndexQueryTemplate.Replace("{indexName}", indexAttribute.Name) 
       .Replace("{tableName}", tableName) 
       .Replace("{columnName}", string.Join(", ", indexes[indexAttribute].ToArray())) 
       .Replace("{unique}", indexAttribute.IsUnique ? "UNIQUE" : string.Empty); 
      } 
     } 

     if (context.Database.CreateIfNotExists()) 
     { 
      context.Database.ExecuteSqlCommand(query); 
     } 
    } 

    private string GetKeyName(Type type) 
    { 
     PropertyInfo[] propertyInfos = type.GetProperties(BindingFlags.FlattenHierarchy | BindingFlags.Instance | BindingFlags.Public); 
     foreach (PropertyInfo propertyInfo in propertyInfos) 
     { 
      if (propertyInfo.GetCustomAttribute(typeof(KeyAttribute), true) != null) 
      return propertyInfo.Name; 
     } 
     throw new Exception("No property was found with the attribute Key"); 
    } 
} 

然後在你的DbContext

超載 OnModelCreating
protected override void OnModelCreating(DbModelBuilder modelBuilder) 
{ 
    Database.SetInitializer(new IndexInitializer<MyContext>()); 
    base.OnModelCreating(modelBuilder); 
} 

將index屬性應用於您的實體類型,使用此解決方案,您可以在同一個索引中使用相同的名稱和唯一的多個字段。

OR

你可以做遷移以後。

說明: 我從here採取了很多此代碼。

+0

通常這裏的建議是可靠的。所以,從我+1。林好奇你爲什麼建議在一個選項種子覆蓋作爲創建索引的好地方。考慮:http://msdn.microsoft.com/en-us/data/jj591621一種基於MigrateDatabaseToLatestVersion的方法?將嘗試每次構建索引。這是一個便捷的方式。但它對生產性數據庫有影響。因此,您的最後一點「或」您可以稍後進行遷移......大概您意指包管理器控制檯中高度管理的方法Update-Database命令。 – 2013-02-17 11:46:33

+0

我不明白你的意思是「稍後進行遷移」。一旦EF創建了最新的數據庫模型,應用遷移就沒有意義了。 – Jez 2013-03-19 11:04:44

+0

@Jez爲什麼根據你的理解沒有意義?否則,你將不得不摧毀你的數據庫並創建新的...我認爲你不會喜歡... – 2013-03-22 18:24:36

0

問題似乎是,如果在中途添加遷移時存在價值,或者這些問題會導致未來數據庫在不同機器上初始化時出現問題。

創建的初始遷移還包含整個數據模型(因爲它存在),所以通過添加遷移(包管理器控制檯中的enable-migrations),實際上是爲數據庫創建內置機制爲其他開發者創造了道路。

如果你這樣做,我建議修改數據庫初始化策略以運行所有現有的遷移,以免EF啓動並導致下一個dev的數據庫不同步。

像這樣的工作:

Database.SetInitializer(new MigrateDatabaseToLatestVersion<YourNamespace.YourDataContext, Migrations.Configuration>()); 

所以,不,這不會本質上介紹了今後工作/開發者的問題。請記住,遷移只是變成了針對數據庫執行的有效SQL ......您甚至可以使用腳本模式輸出根據您創建的遷移中的任何內容進行數據庫修改所需的TSQL。

乾杯。