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命令行中敲命令升级数据库,试问客户现场的生产数据库如何去升级?所以我们使用自动迁移,必须做到在没有开发人员参与下,由实施人员甚至是用户自己去点击个按钮,就自动根据实体去创建或者修改数据库。
- 不出意外,这时候肯定会出错,出什么错都有可能,我遇到的错误是:
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
- 使用小写“dbo”做Oracle用户名
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());
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。
作者:Rick Carter
出处:http://pains.cnblogs.com/
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· PostgreSQL 和 SQL Server 在统计信息维护中的关键差异
· C++代码改造为UTF-8编码问题的总结
· DeepSeek 解答了困扰我五年的技术问题
· 为什么说在企业级应用开发中,后端往往是效率杀手?
· 用 C# 插值字符串处理器写一个 sscanf
· [翻译] 为什么 Tracebit 用 C# 开发
· Deepseek官网太卡,教你白嫖阿里云的Deepseek-R1满血版
· 2分钟学会 DeepSeek API,竟然比官方更好用!
· .NET 使用 DeepSeek R1 开发智能 AI 客户端
· 刚刚!百度搜索“换脑”引爆AI圈,正式接入DeepSeek R1满血版