原文:Oracle + EF5 疑难杂症

PDF 版

http://files.cnblogs.com/xling/Oracle.pdf

Oracle 环境准备

ODAC

ODAC 全称 Oracle Data Access Components

下载:

ODTWthODAC 是用于VS的开发工具.

安装

采用ODAC XCopy 版, 就不需要安装体积庞大的 Oracle Client 了.

  1. 将 ODAC 解压到一个固定的位置, 比如 C:\ODAC , 注意,这个文件夹用完后不能删除
  2. 以管理员身份打开一个 CMD
  3. Cd C:\ODAC\ODP.NET\Managed\X64
  4. 运行 configure.bat (不要图快,直接右键以管理员身份运行, 没用!)

  5. 运行成功后, 会注册相关DLL到GAC中.
  6. 打开 Machine.config (C:\Windows\Microsoft.NET\Framework64\v4.0.30319\Config\machine.config)

    可以看到加入了一个 setion oracle.manageddataaccess.client,

    在 DbProviderFactories 下加入了 ODP.NET, Managed Driver

    对应 section oracle.manageddataaccess.client 还有一个配置:

     <oracle.manageddataaccess.client>
    
     <version number="4.121.1.0">
    
     <settings>
    
     <setting name="tns_admin" value="c:\odac\odp.net\managed\x64\..\..\..\network\admin" />
    
     </settings>
    
     </version>
    
     </oracle.manageddataaccess.client>

    其中, setting tns_admin 所指的地址即 tnsnames.ora 的目录.

  7. 将 D:\ODAC\network\admin\sample\tnsnames.ora 考到上一层目录:

    即 c:\ODAC\network\admin\

    修改成 :

     dev =
    (DESCRIPTION =
    (ADDRESS = (PROTOCOL = TCP)(HOST = 192.168.0.3)(PORT = 1521))
    (CONNECT_DATA =
    (SERVER = DEDICATED)
    (SERVICE_NAME = ORCL)
    )
    )

    DEV 即别名, 不区分大小写

    HOST 即 ORACLE 服务器地址

    PORT 为指定的监听端口

    SERVICE_NAME 即ORACLE 实例名

    如果需要 32位的, 可以重复上面的步骤, 不过运行的是 X86目录下面的 configure.bat 而已.

    ODTWithODAC 一路 NEXT 即可.

    在 VS 中连接数据库, 即可看到多出一个 ODP.NET 托管驱动程序选项:

    用TNS 测试连接:

    注意: 这里的 Tnsnames.ora 的位置, 不是上面步骤中的位置. 如果文件不存在, 请在 COPY一个到 admin 目录下.

    用 EZ 测试连接:

网站运行可能出现的错误:

无法读取配置节 Oracle.manageddataaccess.client 因为它缺少节声明

请检查你的应用程序池

A, 如果 "启用32位应用程序"为 True , 请改为 False , 因为只安装了 64 位的 ODAC

B, 也可以把 32 位的 ODAC 安装上.

Entity Framework

支持

当前EF6 不支持 Oracle, 而且只能是 Database First.

表名/字段名 大小写的问题

在Oracle 中建的表, 在更新模型的时候, 全是大写, 表字段也全是大写:

这个问题对于习惯于 SQLServer 的你来说, 感觉一定是吹鼻子瞪眼,抓狂的想办法把表名变小写.

有办法变小写, 不过,了解了因果之后, 你一定会想:大写就大写吧.

建表语句 中的 表名/列名 用双引号括起来:

 CREATE TABLE BK."Test"
(
"ID" NUMBER NOT NULL,
"Name" NUMBER NOT NULL
)

生成的表, 可以看到 表名/列表都有小写的字母了.

但是:

必须用

SELECT * FROM "Test";

即双引号括起来表名, 大小写必须一致.

这样虽然在 EF 里有了驼峰式, 但是写SQL又成了一大挑战.

类型转换

SQLServer 中的类型到.NET中的类型, 基本上都有一个完美的映射, 但是 ORACLE 就不同了. 一堆 Decimal 不说, 连最基本的 bool 都没有原生的映射.

要想用 bool 类型, 需要在模型项目的 App.config 中加上一段:

   <oracle.manageddataaccess.client>
<version number="*">
<edmMappings>
<edmMapping dataType="number">
<add name="bool" precision="1" />
<add name="byte" precision="2" />
<add name="int16" precision="5" />
</edmMapping>
</edmMappings>
</version>
</oracle.manageddataaccess.client>

即:

精度为 1 的 NUMBER 字段,在模型中为 bool 类型

精度为 2 的 NUMBER 字段在模型中为 byte 类型

模型属性设置

更新属性方面:

这个翻译真的很蛋疼. 意向中 这个是用来 更新 诸如 精度修改 等小细节上的, 不过, 我这里把精度从默认的 38 改为 1 , 实体类型还是没有改为 bool, 需要手动更改, 或者把表从模型中删除, 在添加进来.

生成时验证:

假如这个选项为 True, 在你编译的时候, 出现以下情况:

全部生成成功, 但是有错误. 模型兼容性错误. 这个错误不引响程序的正常运行, 就是看着有一大堆错误而已.

把这个属性改为 False , 即可以在编译的时候屏蔽这样的错误.

TT模板

模型模板

这个结构是将 DbContext 和 实体分成两个不同的DLL, 好处不言而喻.

Model1.tt 是从 XXX.DbContext 下剪切出来的.

Model1.Context.tt 只做了少许修改, 加入了 XXX.DbEntity 的引用, 变化不大.

Model1.tt 中加入了字段注释, Required / StringLength 等, 生成的效果如下:

 namespace XXY.DbEntity
{
using System;
using System.Collections.Generic;
using System.Runtime.Serialization;
using System.ComponentModel.DataAnnotations;
using Newtonsoft.Json; /// <summary>
/// 承运人运价表
/// </summary>
[Serializable,DataContract(IsReference = true),JsonObject(IsReference = false)]
public partial class PRICE
{
public PRICE()
{
this.PRICE_DETAIL = new HashSet<PRICE_DETAIL>();
} /// <summary>
/// 价格流水号 必填项
/// </summary>
[DataMember , Required]
public decimal PRICE_ID { get; set; } /// <summary>
/// 承运人流水号 必填项
/// </summary>
[DataMember , Required]
public decimal CARRIER_ID { get; set; } /// <summary>
/// 承运人代码 必填项
/// </summary>
[DataMember , Required, StringLength()]
public string CARRIER_CODE { get; set; } /// <summary>
/// 承运人名称
/// </summary>
[DataMember, StringLength()]
public string CARRIER_NAME { get; set; }

StringLength 读取的是模型中的字段最大长度.

Required 是根据模型中的字段是否可为 Null

Summary 部分:

因为是 Database First, 原本跟据数据生成的模型, 并不会把数据库注释写到模型中, 我写了一个小工具, 后期把注释更新到模型中, 该工具在本文的最下方有提供下载.

DataContract IsReference = true 是为了在 WCF 中使用, 而不出现 "循环引用" 的错误.

JsonObject IsReference = false 是为了生成的 Json 不出现 $1 之类的东西.

在 Model1.tt 的第 6行:

const string inputFile = @"..\XXX.DbContext\Model1.edmx";

用于指定该模板依赖的 edmx 文件的位置.

SEQUENCE

Oracle 中没有自增长, 只有 SEQUENCE.

看网上有很多文章都是说新建一个触发器, 当插入数的时候, 取一个 SQUENCE 出来.

不是说触发器不好, 我不乐意使用触发器.

这里,我写了一个 TT模板, 从数据库中把所有的当前数据库的 SEQUENCE 名称取出来, 写入到一个枚举中.

用法其实就是执行一个SQL语句, 从指定SEQUENCE 中取下一个值.

SELECT XXX.NEXTVAL FROM DUAL

针对这个, 我做了扩展方法:

 public static decimal GetNextVal(this System.Data.Entity.DbContext ctx, string seqName) {
return ctx.Database.SqlQuery<decimal>(string.Format("SELECT {0}.NEXTVAL FROM DUAL", seqName)).First();
} public static decimal GetNextVal<T>(this DbContext ctx, T enumValue) where T : struct, IComparable, IConvertible, IFormattable {
return ctx.GetNextVal(enumValue.ToString());
}

使用:

price.PRICE_ID = db.GetNextVal(Sequences.PRICES_SEQ);

在 Sequence.ttinclude 文件的 29/30 行

const string ConnectionString = @"Persist Security Info=True;Data Source=192.168.0.3;User ID=BK;password=bk;";

const string SQL = @"SELECT * FROM all_sequences WHERE SEQUENCE_OWNER = 'BK'";

ConnectionString 就不用说了, 改成自己的数据库连接字符串.

将 SQL 中的 SEQUENCE_OWNER 改成你的数据库登陆用户(该用户要能看到SEQUENCE)

SEQUENCE 模板 和 模型模板在本文的最下方会给出下载地址.

大小写敏感的问题

这里说的大小写每感,不同于上面说的表名/字段名大小写的问题.

ORACLE 中,默认的 字段的值 是 大小写敏感 的.

搜了一下ORACLE 的忽略大小写的方法, 大部分都是用 UPPER 或是更改会话设置:

alter session set NLS_COMP=LINGUISTIC;

alter session set NLS_SORT=BINARY_CI;

第一种对应到EF 里就是用 ToUpper, 但是这样一来或多或少的影响查询性能.

第二种不方便实现, 且这样改动可能会造成其它方面的问题.

用第一种办法,略显繁琐.

我改了一下 模型文件 的模板(Model1.Context.tt), 重载了 ShouldValidateEntity 方法, 在它里面做了一些手脚.

 protected override bool ShouldValidateEntity(DbEntityEntry entityEntry) {
UpperConverter.Convert(entityEntry.Entity);
return base.ShouldValidateEntity(entityEntry);
} internal static partial class UpperConverter {
private static Dictionary<Type, List<PropertyInfo>> TPS = new Dictionary<Type, List<PropertyInfo>>();
public static void Add<T>(params Expression<Func<T, string>>[] exprs) { foreach (var expr in exprs) { PropertyInfo pi = null; switch (expr.Body.NodeType) {
case ExpressionType.MemberAccess:
pi = (PropertyInfo)((MemberExpression)expr.Body).Member;
break;
default:
throw new InvalidOperationException();
} if (TPS.ContainsKey(pi.DeclaringType))
TPS[pi.DeclaringType].Add(pi);
else {
TPS.Add(pi.DeclaringType, new List<PropertyInfo>() { pi });
}
}
} public static void Convert(object obj) {
var type = obj.GetType();
if (TPS.ContainsKey(type)) {
var ps = TPS[type];
foreach (var p in ps) {
var value = (string)p.GetValue(obj);
if (!string.IsNullOrWhiteSpace(value))
p.SetValue(obj, value.ToUpper());
}
}
}
} internal static partial class UpperConverter { public static void Set() { #region 审核权限
Add<BOOKING_ORDER_PERMISSIONS>(p => p.CARRIER_CODE, p => p.LOADING_PORT, p => p.ROUTE_CODE, p => p.SHIP_CODE,
p => p.VOYAGE);
Add<SPECIAL_PRICE_RULE>(p => p.CARRIER_CODE, p => p.ROUTE_CODE, p => p.SHIP_CODE, p => p.VOYAGE);
Add<SPECIAL_PRICE_RULE_CONTA>(p => p.SIZETYPE_CODE);
#endregion

这样在 db.SaveChange 的时候, 就会针对 Add<XXX>(…) 中列出的属性做大写转换.

这样一来, 就可以在 EF 中少写很多 ToUpper , 以优化查询性能.

事务报错

该部分原来已发过博文: http://www.cnblogs.com/xling/p/3900222.html

这里把它摘录过来:

错误:未能加载 Oracle.ManagedDataAccessDTC.dll 或它的依赖项

本地WIN7/8.1运行一点问题都没有。打包到 WIN 2008 上,解决了一堆环境问题后,一个大难题出现了:

Could not load file or assembly 'Oracle.ManagedDataAccessDTC.dll',什么 PSPManager..ctor 之类的

出现这个问题是因为某些地方用了 TransactionScope 。

把驱动卸掉,重装了N回,重启了N回,于事无补。

把这个DLL放到 Bin 下,运行网站直接就报错,还是无法加载。

Oracle 官方文档中只说不要直接引用这个DTC.dll ,会由 ManagedDataAccess 自动去调用,要区分 32位和64位,其它的基本没提。

GOOGLE上、BING上可以搜到几个相关的贴子,但是都是没有结果。度娘就更不用提了。

跟据报的那什么 PSPManager..Ctor 用反编译工具查看了一下,跟本就没有那个类。

不过有个 Microsoft.VisualC 的引用。

本地GAC (C:\Windows\Microsoft.Net\assembly\GAC_MSIL\Microsoft.VisualC)下有个11.0.xxx 版本的,

对照那台测试服务器,发现只有个8.XXX的版本。

尝试把本地的考过去,运行结果一样,没有用处。

眼看加班都3个半小时了,加上一下午时间,都整了快8个小时,还没整好这玩意,心里急的冒火。

顺手搜了一下C++运行库,下了个64位的

Microsoft Visual C++ 2010 SP1 Redistributable Package (x64)

http://www.microsoft.com/zh-cn/download/confirmation.aspx?id=13523

安装,重启网站,在测试,通过!

反向伴随类

由于是 Database First , 实体都是自动生成的, 任何手工修改都是无效的. 要想对某个实体的某个属性加个 DataAnnoation , 就需要写一个 Partial 出来.

我没有这样做, 我做了个反向的 伴随类 处理.

 [AttributeUsage(AttributeTargets.Class)]
public class AnnoationForAttribute : Attribute {
public AnnoationForAttribute(Type type); public Type ForType {
get;
set;
}
} public class AnnorationHelper { public static void AutoMap() { var types = typeof(AnnorationHelper).Assembly.GetTypes();
foreach (var t in types) {
var attr = (AnnoationForAttribute)t.GetCustomAttributes(typeof(AnnoationForAttribute), false).FirstOrDefault();
if (attr != null)
TypeDescriptor.AddProviderTransparent(new AssociatedMetadataTypeTypeDescriptionProvider(attr.ForType, t), attr.ForType);
} } }

反向伴随类的声明:

 [AnnoationFor(typeof(DbEntity.PRICE))]
public class PRICE { [CompareWith("EFFECTIVE_DATE", CompareWithOpts.Gt)]
public object EXPIRATION_DATE {
get;
set;
}
}

现在, 只需要在 Global 里调用:

AnnorationHelper.AutoMap();

即可把反向伴随类注册到实体类上.

OracleFunction ?

使用 SqlServer + EF , 可以用 SqlFunctions, 但是Oracle 并没有提供类似的功能.

Oracle 的函数到 LINQ 的函数映射可以参考:

http://docs.oracle.com/cd/E11882_01/win.112/e23174/canonical_map.htm#ODPNT7777

文档中说会把 concat 转化为 "xx"||"xx" 或 CONCAT 方法的调用, 但是在实际中, 却无法将 STRING 和 DECIMAL 用 String.Concat 连接起来.

为了一个特殊业务, 我在这个地方耗了好几个小时, 没有办法, 只能通过自定义函数解决了.

 CREATE OR REPLACE function BK.ToString(P NUMBER)
return VARCHAR
is
V VARCHAR(10);
begin
select TO_CHAR(P) into V FROM DUAL;
return V;
end;

然后更新模型, 将函数添加到模型中.

定义一个 FUNCTION 的映射.

 public static class OracleFunctions {
[EdmFunction("Model.Store", "TOSTRING")]
public static string ToString(decimal d) {
throw new InvalidOperationException("Not to be called from client code");
}
}

Model.Store 和模型的命名空间一样(未验证不一样是否可以)

然后 在LINQ TO SQL 中 用 拼接字符串的方法使用 :

let tmp = sp.PORT_CODE.ToUpper() + "," + OracleFunctions.ToString(s.COURSE)

很简单的一个功能, 搞的很蛋疼.

文中所述的下载地址:

http://files.cnblogs.com/xling/OracleEF.7z

捧个场, 推个贱。

Oracle + EF5 疑难杂症的更多相关文章

  1. [部署]MVC4.0+EF5.0+ODT+ORACLE相关注意事项

    摘要 项目开发工具:VS2012旗舰版(.NetFrameWork4.5.1),WIN7 64bit,Oracle 11g 服务器环境:Windows Server2008 R2 64bit,.Net ...

  2. oracle疑难杂症问题

    在虚拟机中安装了oracle10g,由于虚拟机的空间有限,看到磁盘空间快没了,就手贱把oracle目录中的空文件夹(E:\oracle\product\10.2.0\flash_recovery_ar ...

  3. OAF_开发系列26_实现OAF中Java类型并发程式开发oracle.apps.fnd.cp.request(案例)

    20150730 Created By BaoXinjian

  4. 用事实说话,成熟的ORM性能不是瓶颈,灵活性不是问题:EF5.0、PDF.NET5.0、Dapper原理分析与测试手记

    [本文篇幅较长,可以通过目录查看您感兴趣的内容,或者下载格式良好的PDF版本文件查看] 目录 一.ORM的"三国志"    2 1,PDF.NET诞生历程    2 2,Linq2 ...

  5. 实体类的枚举属性--原来支持枚举类型这么简单,没有EF5.0也可以

    通常,我们都是在业务层和界面层使用枚举类型,这能够为我们编程带来便利,但在数据访问层,不使用枚举类型,因为很多数据库都不支持,比如我们现在用的SqlServer2008就不支持枚举类型的列,用的时候也 ...

  6. Entity Framework6 with Oracle(可实现code first)

    Oracle 与2个月前刚提供对EF6的支持.以前只支持到EF5.EF6有很多有用的功能 值得升级.这里介绍下如何支持Oracle   一.Oracle 对.net支持的一些基础知识了解介绍. 1.早 ...

  7. Entity Framework6 with Oracle

    Entity Framework6 with Oracle(可实现code first) Oracle 与2个月前刚提供对EF6的支持.以前只支持到EF5.EF6有很多有用的功能 值得升级.这里介绍下 ...

  8. ORACLE透明网关访问SQL Server配置总结

      透明网关概念 ORACLE透明网关(Oracle Transparent Gateway)可以解决ORACLE数据库和非ORACLE数据库交互数据的需求.在一个异构的分布式环境中,通过ORACLE ...

  9. 用EF的三种方式(SqlServer数据库和Oracle数据库)

    SqlServer数据库 1.DB First 现有DB,生成edmx文件 贴一下生成的model //------------------------------------------------ ...

随机推荐

  1. solr与.net课程(七)solr主从复制

    既然solr是解决大量数据全文索引的方案,因为高并发的问题,我们就要考虑solr的负载均衡了,solr提供很easy的主从复制的配置方法,那么以下我们就来配置一下solr的主从复制 如果我们在192. ...

  2. dwz 照片回头处理

    我的要求.要选择封面文章,回头一看,实现,查找回头功能bringBack代码中发现的,它们朝着input 标签处理,所以img总是标签不能显示,这么dwz源所做的更改,于dwz.databases.j ...

  3. Swift和C#的基本语法对比

    Recently, Apple announced and released a beta version of the new Swift programming language for buil ...

  4. Socket方法LAN多线程文件传输

    1.思维:为了实现各种文件的大小可以被发送和接收的,它可以被设置为发送和接收缓冲器环.并记录文件的位置读取,假设读入缓冲区的字节的特定数目大于缓冲区的大小较小.然后该文件被发送,退出发送周期,关闭连接 ...

  5. 初学者应学会如何加快seo

    新手学习如何加快seo 介绍: 应该如何初学者学习SEO,前弯路.真正的高手SEO知识. 作为一个新人,学习如何加快seo知识吧?        多人天天都在学习seo知识.看别人的文章.看多了就感觉 ...

  6. 【翻译自mos文章】rman 备份时报:ORA-02396: exceeded maximum idle time

    rman 备份时报:ORA-02396: exceeded maximum idle time 參考原文: RMAN backup faling with ORA-02396: exceeded ma ...

  7. Android应用公布的准备——生成渠道包

    我们须要使用一个变量标明该app的渠道.通常我们能够在manifest中的application节点下声明.例如以下. <meta-data android:name="CHANNEL ...

  8. GoldenGate组态(四)它veridata组态

    GoldenGate组态(四)它veridata组态 环境: Item Source System Target System Platform Red Hat Enterprise Linux Se ...

  9. jQuery -&gt; 获取各种滤芯(filter)

    按顺序选择 依次选择过滤器(filter)有着 :first 第一元件 :last 最后一个元素 :even 序号为偶数的元素 :odd 序号为奇数的元素 :eq(n) 序号等于n的元素 :lt(n) ...

  10. Golang基于学习总结

    1.不支持继承 重载 ,比方C++Java的接口,接口的改动会影响整个实现改接口的类行为的改动,Go 设计者觉得这一特点也许根本没用. 2.必不论什么函数定义必须花括号跟在函数声明后面而不能换行 如 ...