EF6 CodeFisrt支持Oracle

EF6 CodeFisrt支持Oracle


EF說是支持多數據庫,但真作起來太多坑了,編程這個詞之後要換換,叫填坑好了。此次把我在作EF6 CodeFisrt支持Oracle數據庫過程當中遇到的坑寫下來,給須要的人減小點填坑的痛苦。sql

先說下使用環境

  • EF6.1.3
  • CodeFirst
  • Oracle版本我用的是11.2
  • Oracle Provider用的是Oracle官方的ODP.NET, Managed Driver

Oracle官方文檔

http://docs.oracle.com/cd/E56485_01/win.121/e55744/entityCodeFirst.htm#ODPNT8309數據庫

搭建環境

這裏只說使用代碼的配置方式,App.config或Web.config配置方式參照官方文檔作就好。編程

1. 下載Oracle Provider

在vs的管理解決方案的NuGet程序包中搜索Oracle,找到ODP.NET(這是個簡寫),有兩個,忽略Unmanaged,下載帶Managed的。c#

2. 添加dll引用

在相關項目中添加下面兩個dll引用:
Oracle.ManagedDataAccess.dll
Oracle.ManagedDataAccess.EntityFramework.dll架構

3. SetProvider

定義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
{
}

4. 建立數據庫

使用下面兩段代碼之一初始化數據庫:sqlserver

Database.SetInitializer(new MigrateDatabaseToLatestVersion<MyContext, MyMigrationsConfiguration>());
using (var ctx = new MyContext())
{
    ctx.Database.Initialize(true);
}

ui

new DbMigrator(new MyMigrationsConfiguration()).Update();

網上的幾乎全部的例子都是讓人在NuGet命令行中敲命令升級數據庫,試問客戶現場的生產數據庫如何去升級?因此咱們使用自動遷移,必須作到在沒有開發人員參與下,由實施人員甚至是用戶本身去點擊個按鈕,就自動根據實體去建立或者修改數據庫。

  1. 不出意外,這時候確定會出錯,出什麼錯都有可能,我遇到的錯誤是:

System.InvalidOperationException: 序列不包含任何匹配元素

經過翻看EF的源碼,發現是實體類定義中用了ColumnAttribute.TypeName指定了SQLServer中的類型,如「NVarChar(4000)」,改用StringLength去指定長度,另外發現若是設置爲int.MaxValue,它在sqlserver上會是NVarChar(MAX)類型,在Oracle上是NCLOB。

  1. 繼續運行,出下面或者相似的錯誤:

ORA-01918: 用戶'SCOTT'不存在解決

解決:在Oracle中添加用戶,而後在MyContext類中加入下面代碼

protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
    base.OnModelCreating(modelBuilder);
    modelBuilder.HasDefaultSchema("你的Oracle用戶名");
}

注意:添加Oracle用戶時至少:設置Unlimited Tablespace、Create Session權限和resource角色;

  1. 繼續運行,錯誤:

不支持影響遷移歷史記錄系統表的位置的自動遷移(例如默認架構更改)。對於影響遷移歷史記錄系統表的位置的操做,請使用基於代碼的遷移。

翻看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

  1. 使用小寫「dbo」作Oracle用戶名

Oracle中全部名稱都默認是大寫的,若是須要區分大小寫或者說是按你輸入原文作名稱,就須要加雙引號,sql語句中也是一樣。因此建小寫「dbo」用戶名的時候加英文的雙引號就好。
使用小寫「dbo」作用戶名後,自動遷移就順利了,固然錯誤仍是會有的,根你軟件的複雜狀況有關係,比較容易出現的錯誤以下,都比較好解決:

ORA-00972: 標識符過長

Oracle限制彷佛是全部名稱30個字符,你可能有表名超過了限制。

ORA-00955: 名稱已由現有對象使用

表名等被使用了,可能的緣由是__MigrationHistory表中記錄的上一次遷移沒有某個表,但實際建立成功,出現的可能性很小,我是遇到了。

ORA-02264: 名稱已被一現有約束條件佔用

這個是由於某個表的同一個列加了多個外鍵致使,第一個外鍵約束會建立成功,第二個外鍵約束會使用相同名稱致使重名。

  1. 數據庫函數、過程等

我是在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());

5. Linq To Entities

在數據庫建立並能夠更新成功後,就開始把軟件跑起來了,這個時候出的問題最多的就是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。

相關文章
相關標籤/搜索