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。

EF6 CodeFisrt支持Oracle的更多相关文章

  1. BI测试工具之跨数据库数据对比,支持oracle,sqlserver

    应用场景: 本周在进行SIT,我帮助仅有的一个测试妹妹对部分表进行数据质量验证,第一步需要做的就是比对source与stage表的table definition 与 数据内容的一致性. 本项目使用的 ...

  2. [VSTS]让ADO.NET Entity Framework支持Oracle数据库(转载)

    近期由于项目所需不得不研究Oracle数据库,回想上一次用Oracle还是07年的事情,实习时候做华晨宝马的项目简单接触了Oracle.这次的项目需要基于.NET平台,我个人的习惯是能用微软自带的就不 ...

  3. dapper支持oracle游标

    dapper支持oracle游标 Dapper是一个轻型的ORM类.它有啥优点.缺点相信很多朋友都知道了,园里也有很多朋友都有相关介绍,这里就不多废话. 如果玩过Oracle都知道,存储过程基本都是通 ...

  4. Mybatis Generator的model生成中文注释,支持oracle和mysql(通过实现CommentGenerator接口的方法来实现)

    自己手动实现的前提,对maven项目有基本的了解,在本地成功搭建了maven环境,可以参考我之前的文章:maven环境搭建 项目里新建表时model,mapper以及mapper.xml基本都是用My ...

  5. 让ADO.NET Entity Framework支持Oracle数据库

    Oracle最近发布了 Oracle Data Access Component(ODAC)11. 2 Rel 4,其中增加了对 Entity Framework 4.1 和4.2的支持.这让 .NE ...

  6. 支持Oracle的模糊查询和精准查询

    相信所有的软件开发者都做过页面上的查询功能,而且很多都需要既支持模糊查询的,也需要支持精准查询的,而且不需要增加多余的功能,只需要在文本框中输入包含类似*之类的符号即可. 下面的方法就是通过*来判断到 ...

  7. phalcon: 解决php7/phalcon3.2以上版本,不支持oracle数据库的方法

    解决php7/phalcon3.2以上版本,不支持oracle数据库的方法 phalcon3.2(3.0以上)版本不支持oracle的方法. https://github.com/phalcon/in ...

  8. 行云管家 V4.7产品新特性-国际化版本、支持Oracle的数据库审计、主机密码自动修改策略 发布日期:2018-11-22

    行云管家在线体验: 行云管家[官网]-领先的云计算管理平台-云安全,堡垒机,自动化运维​ 行云管家新手有礼活动: 行云管家新手有礼,新用户1元即可体验专业版-优惠券​ 发布日期:2018-11-22 ...

  9. 【翻译自mos文章】OGG的集成捕捉模式支持Oracle database标准版么?

    OGG的集成捕捉模式支持Oracle database标准版么? 来源于: Does OGG 11.2.1 Integrated Capture Work with Oracle Database S ...

随机推荐

  1. SCI 美国《科学引文索引》(Science Citation Index, 简称 SCI )

    科学引文索引 编辑锁定同义词SCI(科学引文索引)一般指科学引文索引   美国<科学引文索引>(Science Citation Index, 简称 SCI )于1957 年由美国科学信息 ...

  2. 1_python小程序之实现用户的注册登陆验证功能

    python小程序之实现用户的注册登陆验证功能 程序扼要简述:  一.程序流程:1.程序开始2.判断本地文件/数据库是否已存在用户信息,存在则跳转到登陆,否则跳转到注册,注册成功后后跳转到登陆3.判断 ...

  3. 【CentOS 6.5】 安装VNCServer及配置,注销处理

    如果没有安装VNCServer,只有在机器上登录进桌面后才可以通过VNC连接,否则连不上... 安装: yum install tigervnc-server   运行并设置密码: vncserver ...

  4. Please specify exact device preset UUID

    Please specify exact device preset UUID 重启RAD IDE,重新选择 IOS Simulator ,iphone 机型!

  5. django网页的分页功能,大家如果有疑问请留言

    url文件 from django.contrib import admin from django.conf.urls import url from app01 import views urlp ...

  6. T-SQL 之 执行顺序

    1.sql查询语句的处理步骤,代码清单 --查询组合字段 (5)select (5-2) distinct(5-3) top(<top_specification>)(5-1)<se ...

  7. addClass()使用方法

    含义:为元素添加指定的样式名 具体用法分为以下两种: 1.可以一次添加一个或者多个用空格隔开的样式名 例: <script> $("ul li").addClass(& ...

  8. ubuntu 18.04 - server版 开机启动脚本

    ubuntu 18.04 不再使用 inited 管理系统,改用 systemd systemd 默认读取 /etc/systemd/system 下的文件,该目录下的文件会链接/lib/system ...

  9. 使用JavaScript调用aspx后台代码

    方法1:js同步调用 触发: onclick="javascript:share('<%# Eval("id_File") %>')" 页面函数: ...

  10. Mesos的资源分配

    Apache Mesos能够成为最优秀的数据中心资源管理器的一个重要功能是面对各种类型的应用,它具备像交警一样的疏导能力.本文将深入Mesos的资源分配内部, 探讨Mesos是如何根据客户应用需求,平 ...