PDF 版
http://files.cnblogs.com/xling/Oracle.pdf
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 了.
-
將 ODAC 解壓到一個固定的位置, 比如 C:\ODAC , 注意, 這個文件夾用完后不能刪除
-
以管理員身份打開一個 CMD
-
Cd C:\ODAC\ODP.NET\Managed\X64
-
運行 configure.bat ( 不要圖快,直接右鍵以管理員身份運行, 沒用! )
-
運行成功后, 會注冊相關DLL到GAC中.
-
打開 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 還有一個配置:
1 < oracle.manageddataaccess.client > 2 3 < version number ="4.121.1.0" > 4 5 < settings > 6 7 < setting name ="tns_admin" value ="c:\odac\odp.net\managed\x64\..\..\..\network\admin" /> 8 9 </ settings > 10 11 </ version > 12 13 </ oracle.manageddataaccess.client >
?
?
其中, setting tns_admin 所指的地址即 tnsnames.ora 的目錄.
-
將 D:\ODAC\network\admin\sample\tnsnames.ora 考到上一層目錄:
即 c:\ODAC\network\admin\
修改成 :
1 dev = 2 (DESCRIPTION = 3 (ADDRESS = (PROTOCOL = TCP)(HOST = 192.168.0.3)(PORT = 1521)) 4 (CONNECT_DATA = 5 (SERVER = DEDICATED) 6 (SERVICE_NAME = ORCL) 7 ) 8 )
?
?
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 的你來說, 感覺一定是吹鼻子瞪眼,抓狂的想辦法把表名變小寫.
有辦法變小寫, 不過,了解了因果之后, 你一定會想:大寫就大寫吧.
?
建表語句 中的 表名/列名 用雙引號括起來 :
1 CREATE TABLE BK."Test" 2 ( 3 "ID" NUMBER NOT NULL , 4 "Name" NUMBER NOT NULL 5 )
?
生成的表, 可以看到 表名/列表都有小寫的字母了.
但是:
必須用
SELECT * FROM "Test";
?
即雙引號括起來表名, 大小寫必須一致.
這樣雖然在 EF 里有了駝峰式, 但是寫SQL又成了一大挑戰.
?
類型轉換
SQLServer 中的類型到.NET中的類型, 基本上都有一個完美的映射, 但是 ORACLE 就不同了. 一堆 Decimal 不說, 連最基本的 bool 都沒有原生的映射.
?
要想用 bool 類型, 需要在模型項目的 App.config 中加上一段:
?
1 < oracle.manageddataaccess.client > 2 < version number ="*" > 3 < edmMappings > 4 < edmMapping dataType ="number" > 5 < add name ="bool" precision ="1" /> 6 < add name ="byte" precision ="2" /> 7 < add name ="int16" precision ="5" /> 8 </ edmMapping > 9 </ edmMappings > 10 </ version > 11 </ 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 等, 生成的效果如下:
?
1 namespace XXY.DbEntity 2 { 3 using System; 4 using System.Collections.Generic; 5 using System.Runtime.Serialization; 6 using System.ComponentModel.DataAnnotations; 7 using Newtonsoft.Json; 8 9 10 /// <summary> 11 /// 承運人運價表 12 /// </summary> 13 [Serializable,DataContract(IsReference = true ),JsonObject(IsReference = false )] 14 public partial class PRICE 15 { 16 public PRICE() 17 { 18 this .PRICE_DETAIL = new HashSet<PRICE_DETAIL> (); 19 } 20 21 /// <summary> 22 /// 價格流水號 必填項 23 /// </summary> 24 [DataMember , Required] 25 public decimal PRICE_ID { get ; set ; } 26 27 /// <summary> 28 /// 承運人流水號 必填項 29 /// </summary> 30 [DataMember , Required] 31 public decimal CARRIER_ID { get ; set ; } 32 33 /// <summary> 34 /// 承運人代碼 必填項 35 /// </summary> 36 [DataMember , Required, StringLength( 30 )] 37 public string CARRIER_CODE { get ; set ; } 38 39 /// <summary> 40 /// 承運人名稱 41 /// </summary> 42 [DataMember, StringLength( 200 )] 43 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
針對這個, 我做了擴展方法:
?
1 public static decimal GetNextVal( this System.Data.Entity.DbContext ctx, string seqName) { 2 return ctx.Database.SqlQuery< decimal >( string .Format( " SELECT {0}.NEXTVAL FROM DUAL " , seqName)).First(); 3 } 4 5 public static decimal GetNextVal<T>( this DbContext ctx, T enumValue) where T : struct , IComparable, IConvertible, IFormattable { 6 return ctx.GetNextVal(enumValue.ToString()); 7 }
?
?
使用:
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 方法, 在它里面做了一些手腳.
?
1 protected override bool ShouldValidateEntity(DbEntityEntry entityEntry) { 2 UpperConverter.Convert(entityEntry.Entity); 3 return base .ShouldValidateEntity(entityEntry); 4 } 5 6 internal static partial class UpperConverter { 7 private static Dictionary<Type, List<PropertyInfo>> TPS = new Dictionary<Type, List<PropertyInfo>> (); 8 public static void Add<T>( params Expression<Func<T, string >> [] exprs) { 9 10 foreach ( var expr in exprs) { 11 12 PropertyInfo pi = null ; 13 14 switch (expr.Body.NodeType) { 15 case ExpressionType.MemberAccess: 16 pi = (PropertyInfo)((MemberExpression)expr.Body).Member; 17 break ; 18 default : 19 throw new InvalidOperationException(); 20 } 21 22 23 if (TPS.ContainsKey(pi.DeclaringType)) 24 TPS[pi.DeclaringType].Add(pi); 25 else { 26 TPS.Add(pi.DeclaringType, new List<PropertyInfo> () { pi }); 27 } 28 } 29 } 30 31 public static void Convert( object obj) { 32 var type = obj.GetType(); 33 if (TPS.ContainsKey(type)) { 34 var ps = TPS[type]; 35 foreach ( var p in ps) { 36 var value = ( string )p.GetValue(obj); 37 if (! string .IsNullOrWhiteSpace(value)) 38 p.SetValue(obj, value.ToUpper()); 39 } 40 } 41 } 42 } 43 44 45 internal static partial class UpperConverter { 46 47 public static void Set() { 48 49 #region 審核權限 50 Add<BOOKING_ORDER_PERMISSIONS>(p => p.CARRIER_CODE, p => p.LOADING_PORT, p => p.ROUTE_CODE, p => p.SHIP_CODE, 51 p => p.VOYAGE); 52 Add<SPECIAL_PRICE_RULE>(p => p.CARRIER_CODE, p => p.ROUTE_CODE, p => p.SHIP_CODE, p => p.VOYAGE); 53 Add<SPECIAL_PRICE_RULE_CONTA>(p => p.SIZETYPE_CODE); 54 #endregion 55 …
?
?
這樣在 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 出來.
我沒有這樣做, 我做了個反向的 伴隨類 處理.
?
?
1 [AttributeUsage(AttributeTargets.Class)] 2 public class AnnoationForAttribute : Attribute { 3 public AnnoationForAttribute(Type type); 4 5 public Type ForType { 6 get ; 7 set ; 8 } 9 } 10 11 public class AnnorationHelper { 12 13 public static void AutoMap() { 14 15 var types = typeof (AnnorationHelper).Assembly.GetTypes(); 16 foreach ( var t in types) { 17 var attr = (AnnoationForAttribute)t.GetCustomAttributes( typeof (AnnoationForAttribute), false ).FirstOrDefault(); 18 if (attr != null ) 19 TypeDescriptor.AddProviderTransparent( new AssociatedMetadataTypeTypeDescriptionProvider(attr.ForType, t), attr.ForType); 20 } 21 22 } 23 24 }
?
?
反向伴隨類的聲明:
?
1 [AnnoationFor( typeof (DbEntity.PRICE))] 2 public class PRICE { 3 4 [CompareWith( " EFFECTIVE_DATE " , CompareWithOpts.Gt)] 5 public object EXPIRATION_DATE { 6 get ; 7 set ; 8 } 9 }
?
?
現在, 只需要在 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 連接起來.
?
為了一個特殊業務, 我在這個地方耗了好幾個小時, 沒有辦法, 只能通過自定義函數解決了.
??
1 CREATE OR REPLACE function BK.ToString(P NUMBER ) 2 return VARCHAR 3 is 4 V VARCHAR ( 10 ); 5 begin 6 select TO_CHAR(P) into V FROM DUAL; 7 return V; 8 end ;
?
然后更新模型, 將函數添加到模型中.
定義一個 FUNCTION 的映射.
??
1 public static class OracleFunctions { 2 [EdmFunction( " Model.Store " , " TOSTRING " )] 3 public static string ToString( decimal d) { 4 throw new InvalidOperationException( " Not to be called from client code " ); 5 } 6 }
Model.Store 和模型的命名空間一樣(未驗證不一樣是否可以)
然后 在LINQ TO SQL 中 用 拼接字符串的方法使用 :
let tmp = sp.PORT_CODE.ToUpper() + "," + OracleFunctions.ToString(s.COURSE)
?
很簡單的一個功能, 搞的很蛋疼.
?
文中所述的下載地址:
http://files.cnblogs.com/xling/OracleEF.7z
?
?
捧個場, 推個賤。
更多文章、技術交流、商務合作、聯系博主
微信掃碼或搜索:z360901061

微信掃一掃加我為好友
QQ號聯系: 360901061
您的支持是博主寫作最大的動力,如果您喜歡我的文章,感覺我的文章對您有幫助,請用微信掃描下面二維碼支持博主2元、5元、10元、20元等您想捐的金額吧,狠狠點擊下面給點支持吧,站長非常感激您!手機微信長按不能支付解決辦法:請將微信支付二維碼保存到相冊,切換到微信,然后點擊微信右上角掃一掃功能,選擇支付二維碼完成支付。
【本文對您有幫助就好】元
