zoukankan      html  css  js  c++  java
  • EF6 CodeFisrt支持Oracle

    EF6 CodeFisrt支持Oracle


    EF说是支持多数据库,但真做起来太多坑了,编程这个词以后要换换,叫填坑好了。这次把我在做EF6 CodeFisrt支持Oracle数据库过程中遇到的坑写下来,给需要的人减少点填坑的痛苦。

    先说下使用环境

    • 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的。

    2. 添加dll引用

    在相关项目中添加下面两个dll引用:
    Oracle.ManagedDataAccess.dll
    Oracle.ManagedDataAccess.EntityFramework.dll

    3. SetProvider

    定义DbMigrationsConfiguration类

    internal sealed class MyMigrationsConfiguration : DbMigrationsConfiguration<MyContext>
    {
        public DbConfiguration()
        {
            AutomaticMigrationsEnabled = true;
            AutomaticMigrationDataLossAllowed = true;
        }
        protected override void Seed(T context)
        {
            //种子数据
        }
    }
    

    定义DbConfiguration类

    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. 创建数据库

    使用下面两段代码之一初始化数据库:

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

    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。

  • 相关阅读:
    spring boot的@RequestParam和@RequestBody的区别
    SpringBoot 中常用注解@PathVaribale/@RequestParam/@GetMapping介绍
    Required String parameter is not present
    Spring Boot 日志记录 SLF4J
    400错误,Required String parameter 'paramter' is not present
    初学 Spring boot 报错 Whitelabel Error Page 404
    Powershell获取WMI设备清单
    Powershell快速入门
    perl的Sys::Syslog模块(openlog,syslog,closelog函数,setlogsock)-自定义日志
    制作Windows10政府版的小白教程
  • 原文地址:https://www.cnblogs.com/pains/p/7067218.html
Copyright © 2011-2022 走看看