[Entity Framework] 使用 Database-First 時,如何動態修改 Schema 名稱?

這個問題主要源自筆者目前某一個客戶的問題,因為該客戶使用 Oracle,而且目前使用的是模式為 Database-First,但同一個角色的 Table 有可能存在不同的 Oracle Schema 中,且在 Oracle 中透過 Schema 管理授權是常有的事情

緣起

這個問題主要源自筆者目前某一個客戶的問題,因為該客戶使用 Oracle,而且目前使用的是模式為 Database-First,但同一個角色的 Table 有可能存在不同的 Oracle Schema 中,且在 Oracle 中透過 Schema 管理授權是常有的事情,而我們也都知道,如果使用 Code-First 可以在 DbContext 建立 (OnModelCreating) 事件哩,透過 ToTable 方法動態改變存取 Schema 名稱,如下:

SNAGHTML6ca23d

但是,很遺憾,如果你使用 Database-First 的話, OnModelCreating 事件並不會被觸發,就算會觸發,ToTable 也不適用這裡,因為 Database-First 是將 Schema 定義在 EDMX 描述檔中,EDMX 由 T4 引擎自動產生,在視覺化環境可在屬性視窗中修改,如下圖:

image

 

或者是在 EDMX 的原始檔案中修改,如下圖:

SNAGHTML7accf3

但不管你使用哪一種方式,當 T4 引擎偵測到有修改,不好意思,所有檔案 StudentModel.Context.cs、StudentModel.Designer.cs、Student.cs,全部自動重新產生,剛剛改的 Schema 呢?拍謝,也沒了。

所以實務上根本不能這樣做,就算你去改 Visual Studio 2013 安裝在 Common7 資料夾下的內建範本,可是你如果這樣下去,以後建立 ADO.NET 實體資料模型時都會變成擬修改的內容,不是所有的建立都需要如此做,而且更動到內建的範本,要馬你增加你新增的範本,所以實務上不太可能這樣做,且客戶的需求是,希望起碼將 Schema 放置在 web.config 中,這麼一來也能達到動態修改的目的。

所以,這表示 Entity Framework 要在建立執行個體時,動態讀取 weg.config 中的 Schema 名稱,並套用進去,那該怎麼做呢?

 

解決方法

解決方式有點小小複雜,因為牽涉到 EDMX 的 Metadata 的運作方式,且 Schema 名稱的決定是由建立 EntityConnection 時決定的。

所幸筆者在網路上有找到有人已經寫好了,YA

 

第一步

在 StudentModel.Context.cs 檔案中增加傳入 existingConnection 與 contextOwnsConnection 這兩個引數的 Constructor

 

第二步

讀取 web.config 中的連線字串,並加以拆解出 csdl 與 ssdl 的描述檔案 Metdata 名稱,以及使用的 provider 為何。撰寫程式碼如下:

   1:  public static EntityConnection BuildConnection()
   2:          {
   3:              string EntityConnectionString = ConfigurationManager.ConnectionStrings["StudentEntities"].ConnectionString;
   4:   
   5:              string[] ConnStringArray = EntityConnectionString.Split(';');
   6:              
   7:              BuildConnectionParams buildConnectionParams = new BuildConnectionParams()
   8:              {
   9:                  ProviderName = ConnStringArray[1].Split('=')[1],
  10:                  SchemaName = ConfigurationManager.AppSettings["DBSchema"]
  11:              };
  12:   
  13:              var providerString = string.Format("{1}", ConnStringArray[2].Split('=')[1], GetConnectionStringByArray(ConnStringArray, 2)).Replace("\"", "").Replace("provider connection string=", "");
  14:              var entityBuilder = new EntityConnectionStringBuilder
  15:              {
  16:                  Provider = buildConnectionParams.ProviderName,
  17:                  ProviderConnectionString = providerString,
  18:                  Metadata = ConnStringArray[0].Split('=')[1]
  19:              };
  20:              buildConnectionParams.ModelName = Path.GetFileNameWithoutExtension(entityBuilder.Metadata.Split('|')[0].Replace("res://*", "").Split('/')[1]);
  21:   
  22:              return CreateConnection(buildConnectionParams.SchemaName, entityBuilder, buildConnectionParams.ModelName);
  23:          }

 

如上程式碼可以發現,Schema 名稱就在這裡被讀出來,並隨者後面產生出來的 EntityConnectionStringBuilder 物件傳入 CreateConnection() 方法中,CreateConnection() 會傳回真正的 EntityConnection 物件,所以關鍵就在最後的 CreateConnection() 方法。

 

第三步

從目前的 Assembly 中使用 GetManifestResourceStream 的方式讀取組件當中的清單資源,直接動態修改編譯在 Assembly 中的 (.csdl/.ssdl/.msl) 的描述檔案。

 

詳細程式碼,與實作說明參考下圖:

image

 

如此一來,我們便能真正做到在 Runtime 的時候動態修改 Schema,程式也是立即讀取我們餵給他的 Schema 的 Table。

 

相關參考資料如下:

Changing schema name on runtime - Entity Framework

http://stackoverflow.com/questions/2663164/changing-schema-name-on-runtime-entity-framework

Entity framework with dynamic schema changes, using Database-First approach

http://chriseelmaa.com/entity-framework-dynamic-schema-changes-using-database-first-approach/


 

簽名:

學習是一趟奇妙的旅程

這當中,有辛苦、有心酸、也有成果。有時也會有瓶頸。要能夠繼續勇往直前就必須保有一顆最熱誠的心。

軟體開發之路(FB 社團)https://www.facebook.com/groups/361804473860062/

Gelis 程式設計訓練營(粉絲團)https://www.facebook.com/gelis.dev.learning/


 

如果文章對您有用,幫我點一下讚,或是點一下『我要推薦,這會讓我更有動力的為各位讀者撰寫下一篇文章。

非常謝謝各位的支持與愛護,小弟在此位各位說聲謝謝!!! ^_^