這個問題主要源自筆者目前某一個客戶的問題,因為該客戶使用 Oracle,而且目前使用的是模式為 Database-First,但同一個角色的 Table 有可能存在不同的 Oracle Schema 中,且在 Oracle 中透過 Schema 管理授權是常有的事情
緣起
這個問題主要源自筆者目前某一個客戶的問題,因為該客戶使用 Oracle,而且目前使用的是模式為 Database-First,但同一個角色的 Table 有可能存在不同的 Oracle Schema 中,且在 Oracle 中透過 Schema 管理授權是常有的事情,而我們也都知道,如果使用 Code-First 可以在 DbContext 建立 (OnModelCreating) 事件哩,透過 ToTable 方法動態改變存取 Schema 名稱,如下:
但是,很遺憾,如果你使用 Database-First 的話, OnModelCreating 事件並不會被觸發,就算會觸發,ToTable 也不適用這裡,因為 Database-First 是將 Schema 定義在 EDMX 描述檔中,EDMX 由 T4 引擎自動產生,在視覺化環境可在屬性視窗中修改,如下圖:
或者是在 EDMX 的原始檔案中修改,如下圖:
但不管你使用哪一種方式,當 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) 的描述檔案。
詳細程式碼,與實作說明參考下圖:
如此一來,我們便能真正做到在 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/
如果文章對您有用,幫我點一下讚,或是點一下『我要推薦』,這會讓我更有動力的為各位讀者撰寫下一篇文章。
非常謝謝各位的支持與愛護,小弟在此位各位說聲謝謝!!! ^_^