Oracle + EF5 疑难杂症files.cnblogs.com/files/xling/Oracle.pdfDataContract IsReference = true...

17
Oracle + EF5 疑难杂症 http://www.cnblogs.com/xling/p/4236825.html Oracle 环境准备 ODAC ODAC 全称 Oracle Data Access Components 下载: ODP.NET (Oracle Data Provider) http://www.oracle.com/technetwork/database/windows/downloads/index-090165.html ODTwithODAC (ODAC With Oracle Developer Tools) http://www.oracle.com/technetwork/topics/dotnet/utilsoft-086879.html ODTWthODAC 是用于 VS 的开发工具. 安装 采用 ODAC XCopy , 就不需要安装体积庞大的 Oracle Client . 1. ODAC 解压到一个固定的位置, 比如 C:\ODAC , 注意,这个文件夹用完后不能删除 2. 以管理员身份打开一个 CMD 3. Cd C:\ODAC\ODP.NET\Managed\X64 4. 运行 configure.bat (不要图快,直接右键以管理员身份运行, 没用!)

Transcript of Oracle + EF5 疑难杂症files.cnblogs.com/files/xling/Oracle.pdfDataContract IsReference = true...

Page 1: Oracle + EF5 疑难杂症files.cnblogs.com/files/xling/Oracle.pdfDataContract IsReference = true 是为了在 WCF 中使用, 而不出现 “循环引用” 的错误. JsonObject IsReference

Oracle + EF5 疑难杂症

http://www.cnblogs.com/xling/p/4236825.html

Oracle 环境准备

ODAC

ODAC 全称 Oracle Data Access Components

下载:

ODP.NET (Oracle Data Provider)

http://www.oracle.com/technetwork/database/windows/downloads/index-090165.html

ODTwithODAC (ODAC With Oracle Developer Tools)

http://www.oracle.com/technetwork/topics/dotnet/utilsoft-086879.html

ODTWthODAC 是用于 VS 的开发工具.

安装

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

1. 将 ODAC 解压到一个固定的位置, 比如 C:\ODAC , 注意,这个文件夹用完后不能删除

2. 以管理员身份打开一个 CMD

3. Cd C:\ODAC\ODP.NET\Managed\X64

4. 运行 configure.bat (不要图快,直接右键以管理员身份运行, 没用!)

Page 2: Oracle + EF5 疑难杂症files.cnblogs.com/files/xling/Oracle.pdfDataContract IsReference = true 是为了在 WCF 中使用, 而不出现 “循环引用” 的错误. JsonObject IsReference

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 =

Page 3: Oracle + EF5 疑难杂症files.cnblogs.com/files/xling/Oracle.pdfDataContract IsReference = true 是为了在 WCF 中使用, 而不出现 “循环引用” 的错误. JsonObject IsReference

(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 测试连接:

Page 4: Oracle + EF5 疑难杂症files.cnblogs.com/files/xling/Oracle.pdfDataContract IsReference = true 是为了在 WCF 中使用, 而不出现 “循环引用” 的错误. JsonObject IsReference

注意: 这里的 Tnsnames.ora 的位置, 不是上面步骤中的位置. 如果文件不存在, 请在

COPY 一个到 admin 目录下.

用 EZ 测试连接:

Page 5: Oracle + EF5 疑难杂症files.cnblogs.com/files/xling/Oracle.pdfDataContract IsReference = true 是为了在 WCF 中使用, 而不出现 “循环引用” 的错误. JsonObject IsReference

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

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

请检查你的应用程序池

Page 6: Oracle + EF5 疑难杂症files.cnblogs.com/files/xling/Oracle.pdfDataContract IsReference = true 是为了在 WCF 中使用, 而不出现 “循环引用” 的错误. JsonObject IsReference

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

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

Entity Framework

支持

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

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

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

Page 7: Oracle + EF5 疑难杂症files.cnblogs.com/files/xling/Oracle.pdfDataContract IsReference = true 是为了在 WCF 中使用, 而不出现 “循环引用” 的错误. JsonObject IsReference

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

小写.

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

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

CREATE TABLE BK."Test"

(

"ID" NUMBER NOT NULL,

"Name" NUMBER NOT NULL

)

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

Page 8: Oracle + EF5 疑难杂症files.cnblogs.com/files/xling/Oracle.pdfDataContract IsReference = true 是为了在 WCF 中使用, 而不出现 “循环引用” 的错误. JsonObject IsReference

但是:

必须用

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 类型

Page 9: Oracle + EF5 疑难杂症files.cnblogs.com/files/xling/Oracle.pdfDataContract IsReference = true 是为了在 WCF 中使用, 而不出现 “循环引用” 的错误. JsonObject IsReference

模型属性设置

更新属性方面:

这个翻译真的很蛋疼. 意向中 这个是用来 更新 诸如 精度修改 等小细节上的, 不过, 我

这里把精度从默认的 38 改为 1 , 实体类型还是没有改为 bool, 需要手动更改, 或者把表

从模型中删除, 在添加进来.

生成时验证:

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

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

Page 10: Oracle + EF5 疑难杂症files.cnblogs.com/files/xling/Oracle.pdfDataContract IsReference = true 是为了在 WCF 中使用, 而不出现 “循环引用” 的错误. JsonObject IsReference

有一大堆错误而已.

把这个属性改为 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>

Page 11: Oracle + EF5 疑难杂症files.cnblogs.com/files/xling/Oracle.pdfDataContract IsReference = true 是为了在 WCF 中使用, 而不出现 “循环引用” 的错误. JsonObject IsReference

/// 承运人运价表

/// </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(30)]

public string CARRIER_CODE { get; set; }

/// <summary>

/// 承运人名称

/// </summary>

[DataMember, StringLength(200)]

public string CARRIER_NAME { get; set; }

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

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

Summary 部分:

因为是 Database First, 原本跟据数据生成的模型, 并不会把数据库注释写到模型中, 我写了

一个小工具, 后期把注释更新到模型中, 该工具在本文的最下方有提供下载.

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

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

Page 12: Oracle + EF5 疑难杂症files.cnblogs.com/files/xling/Oracle.pdfDataContract IsReference = true 是为了在 WCF 中使用, 而不出现 “循环引用” 的错误. JsonObject IsReference

在 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 模板 和 模型模板在本文的最下方会给出下载地址.

大小写敏感的问题

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

Page 13: Oracle + EF5 疑难杂症files.cnblogs.com/files/xling/Oracle.pdfDataContract IsReference = true 是为了在 WCF 中使用, 而不出现 “循环引用” 的错误. JsonObject IsReference

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();

Page 14: Oracle + EF5 疑难杂症files.cnblogs.com/files/xling/Oracle.pdfDataContract IsReference = true 是为了在 WCF 中使用, 而不出现 “循环引用” 的错误. JsonObject IsReference

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 之

类的

Page 15: Oracle + EF5 疑难杂症files.cnblogs.com/files/xling/Oracle.pdfDataContract IsReference = true 是为了在 WCF 中使用, 而不出现 “循环引用” 的错误. JsonObject IsReference

出现这个问题是因为某些地方用了 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;

Page 16: Oracle + EF5 疑难杂症files.cnblogs.com/files/xling/Oracle.pdfDataContract IsReference = true 是为了在 WCF 中使用, 而不出现 “循环引用” 的错误. JsonObject IsReference

}

}

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

Page 17: Oracle + EF5 疑难杂症files.cnblogs.com/files/xling/Oracle.pdfDataContract IsReference = true 是为了在 WCF 中使用, 而不出现 “循环引用” 的错误. JsonObject IsReference

文档中说会把 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)

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