EF說是支持多數據庫,但真作起來太多坑了,編程這個詞之後要換換,叫填坑好了。此次把我在作EF6 CodeFisrt支持Oracle數據庫過程當中遇到的坑寫下來,給須要的人減小點填坑的痛苦。sql
- EF6.1.3
- CodeFirst
- Oracle版本我用的是11.2
- Oracle Provider用的是Oracle官方的ODP.NET, Managed Driver
http://docs.oracle.com/cd/E56485_01/win.121/e55744/entityCodeFirst.htm#ODPNT8309數據庫
這裏只說使用代碼的配置方式,App.config或Web.config配置方式參照官方文檔作就好。編程
在vs的管理解決方案的NuGet程序包中搜索Oracle,找到ODP.NET(這是個簡寫),有兩個,忽略Unmanaged,下載帶Managed的。c#
在相關項目中添加下面兩個dll引用:
Oracle.ManagedDataAccess.dll
Oracle.ManagedDataAccess.EntityFramework.dll架構
定義DbMigrationsConfiguration類oracle
internal sealed class MyMigrationsConfiguration : DbMigrationsConfiguration<MyContext> { public DbConfiguration() { AutomaticMigrationsEnabled = true; AutomaticMigrationDataLossAllowed = true; } protected override void Seed(T context) { //種子數據 } }
定義DbConfiguration類ide
public class MyConfiguration : DbConfiguration { public MyConfiguration() { SetDefaultConnectionFactory(new OracleConnectionFactory()); SetProviderServices("Oracle.ManagedDataAccess.Client", EFOracleProviderServices.Instance); SetProviderFactory("Oracle.ManagedDataAccess.Client", new OracleClientFactory()); } }
給你的DbContext類添加Attribute函數
[DbConfigurationType(typeof(EFConfiguration))] public class MyContext : DbContext { }
使用下面兩段代碼之一初始化數據庫:sqlserver
Database.SetInitializer(new MigrateDatabaseToLatestVersion<MyContext, MyMigrationsConfiguration>()); using (var ctx = new MyContext()) { ctx.Database.Initialize(true); }
或ui
new DbMigrator(new MyMigrationsConfiguration()).Update();
網上的幾乎全部的例子都是讓人在NuGet命令行中敲命令升級數據庫,試問客戶現場的生產數據庫如何去升級?因此咱們使用自動遷移,必須作到在沒有開發人員參與下,由實施人員甚至是用戶本身去點擊個按鈕,就自動根據實體去建立或者修改數據庫。
System.InvalidOperationException: 序列不包含任何匹配元素
經過翻看EF的源碼,發現是實體類定義中用了ColumnAttribute.TypeName指定了SQLServer中的類型,如「NVarChar(4000)」,改用StringLength去指定長度,另外發現若是設置爲int.MaxValue,它在sqlserver上會是NVarChar(MAX)類型,在Oracle上是NCLOB。
ORA-01918: 用戶'SCOTT'不存在解決
解決:在Oracle中添加用戶,而後在MyContext類中加入下面代碼
protected override void OnModelCreating(DbModelBuilder modelBuilder) { base.OnModelCreating(modelBuilder); modelBuilder.HasDefaultSchema("你的Oracle用戶名"); }
注意:添加Oracle用戶時至少:設置Unlimited Tablespace、Create Session權限和resource角色;
不支持影響遷移歷史記錄系統表的位置的自動遷移(例如默認架構更改)。對於影響遷移歷史記錄系統表的位置的操做,請使用基於代碼的遷移。
翻看EF的源碼,發現它會判斷Schema是否爲默認Schema,而默認的Schema定義的是一個常量「dbo」。同時ODP.NET文檔中也寫了必須是「dbo」Schema。
Code First Automatic Migrations is limited to working with the dbo schema only. Due to this limitation it is recommended to use
code-based migrations, that is, add explicit migrations through the
Add-Migration command.
這麼問題就來了,沒法自動遷移怎麼辦,我如今的解決辦法是隻能使用小寫的「dbo」用戶,這樣很確定爲客戶現場的部署帶來未知的麻煩。我以前還準備修改EF源碼,去掉這個默認Schema的限制,從新編譯它,但又小心他們這麼作是某些硬性條件致使,因此也就沒去嘗試,若是有人知道怎麼解決這個限制還請告知。
若是你不須要自動遷移,那麼可能問題簡單不少,你能夠自由的使用Oracle用戶名。不過有可能你須要給__MigrationHistory表的實體HistoryRow設置Schema,作法很簡單,參考https://msdn.microsoft.com/en-us/library/dn456841(v=vs.113).aspx
Oracle中全部名稱都默認是大寫的,若是須要區分大小寫或者說是按你輸入原文作名稱,就須要加雙引號,sql語句中也是一樣。因此建小寫「dbo」用戶名的時候加英文的雙引號就好。
使用小寫「dbo」作用戶名後,自動遷移就順利了,固然錯誤仍是會有的,根你軟件的複雜狀況有關係,比較容易出現的錯誤以下,都比較好解決:
ORA-00972: 標識符過長
Oracle限制彷佛是全部名稱30個字符,你可能有表名超過了限制。
ORA-00955: 名稱已由現有對象使用
表名等被使用了,可能的緣由是__MigrationHistory表中記錄的上一次遷移沒有某個表,但實際建立成功,出現的可能性很小,我是遇到了。
ORA-02264: 名稱已被一現有約束條件佔用
這個是由於某個表的同一個列加了多個外鍵致使,第一個外鍵約束會建立成功,第二個外鍵約束會使用相同名稱致使重名。
我是在Seed方法中用DbContext對象的Database.ExecuteSqlCommand去建立函數、過程、觸發器等的sql腳本的,這塊出的問題比較單一了,都是sql語句的錯,自行解決就好。
ODP.NET是聲明不支持表值函數的,我在攔截器中修改sql讓它支持,作法以下:
定義攔截器類:
public class OracleInterceptor : IDbCommandInterceptor { public void ScalarExecuting(DbCommand command, DbCommandInterceptionContext<object> interceptionContext) { NReplace(command, interceptionContext.ObjectContexts.First()); } public void ReaderExecuting(DbCommand command, DbCommandInterceptionContext<DbDataReader> interceptionContext) { NReplace(command, interceptionContext.ObjectContexts.First()); } private static void NReplace(DbCommand command, ObjectContext ctx) { if (!command.CommandText.StartsWith("CREATE OR REPLACE")) { foreach (var item in ctx.MetadataWorkspace.GetItems<EdmFunction>(DataSpace.SSpace).Where(i => i.NamespaceName == "CodeFirstDatabaseSchema")) { if (item.ReturnParameter == null || item.ReturnParameter.TypeUsage.EdmType.BuiltInTypeKind != BuiltInTypeKind.CollectionType) continue; var strs = new List<string>(); var methodName = item.Name; var str = string.Format(@"""dbo"".""{0}""", methodName); var i = 0; while ((i = command.CommandText.IndexOf(str, i)) >= 0) { var j = i + str.Length; var m = 0; for (; j < command.CommandText.Length; j++) { if (command.CommandText[j] == '(') { m++; } else if (command.CommandText[j] == ')') { m--; } if (m == 0) { break; } } strs.Add(command.CommandText.Substring(i, j - i + 1)); i = j; } foreach (var s in strs) { command.CommandText = command.CommandText.Replace(s, string.Format("table({0})", s)); } } } } }
而後在MyConfiguration構造函數中加一行
AddInterceptor(new OracleInterceptor());
在數據庫建立並能夠更新成功後,就開始把軟件跑起來了,這個時候出的問題最多的就是Linq編譯出來的sql執行錯誤。我遇到的錯誤有下面幾個:
ORA-12704: 字符集不匹配
這個通常是由於,非Unicode字符串被當成Unicode字符串使用,常見於帶有單引號字符串的sql中,而且可能存在字符串與字段鏈接操做,須要將'str'改爲N'str'才行。
解決辦法是:給DbConfiguration類中添加一個攔截器,攔截器類中用正則去找出字符串,所有替換成帶前綴N。
ORA-00932: 數據類型不一致: 應爲 NCHAR, 但卻得到 NCLOB
這個錯誤也在攔截器中替換TO_NCLOB爲TO_NCHAR。
OUTER APPLY not supported by Oracle
Oracle多是沒有OUTER APPLY這樣的寫法,但Linq轉出來的Sql卻老是含有它,致使大量的Linq出錯,沒辦法,只能一點點改了,看官有好辦法麻煩告知。 這個錯誤通常都是linq中有子查詢,而且子查詢有join或者子查詢還有其餘子查詢,也可能Include方法也會致使這個問題,我如今仍是換個寫法來解決,如改爲left join。