ORM查詢語(yǔ)言(OQL)簡(jiǎn)介--高級(jí)篇:脫胎換骨
相關(guān)文章內(nèi)容索引:
在寫本文之前,一直在想文章的標(biāo)題應(yīng)怎么取。在寫了《ORM查詢語(yǔ)言(OQL)簡(jiǎn)介--概念篇》、《ORM查詢語(yǔ)言(OQL)簡(jiǎn)介--實(shí)例篇》之后,覺得本篇文章應(yīng)該是前2篇的延續(xù),但又不是一般的延續(xù),因?yàn)榻裉煲獙懙倪@篇內(nèi)容,是基于對(duì)框架OQL完全重構(gòu)之后來(lái)寫的,所以加上一個(gè)副標(biāo)題:脫胎換骨!
一、OQL之前生
1.1,內(nèi)容回顧:
OQL是我設(shè)計(jì)用來(lái)處理PDF.NET開發(fā)框架的ORM查詢的,因此叫做ORM查詢語(yǔ)言。自2006年第一版以來(lái),經(jīng)歷了多次重構(gòu),到PDF.NET Ver 4.X 版本,已經(jīng)比較穩(wěn)定了,在我做的項(xiàng)目和框架用戶朋友的項(xiàng)目中得到成功應(yīng)用,基本符合一般的常規(guī)應(yīng)用需求。
OQL有下面3個(gè)顯著特點(diǎn):
- 抽象的SQL,屏蔽了具體數(shù)據(jù)庫(kù)的差異,因此支持所有數(shù)據(jù)庫(kù);
- 對(duì)象化的“SQL”,寫OQL代碼能夠獲得IDE的智能提示,能夠得到編譯時(shí)檢查確保不會(huì)寫出錯(cuò)誤的SQL;
- 沒有使用.NET的特性,比如泛型、反射、表達(dá)式樹等東西,因此理論上OQL可以跨語(yǔ)言平臺(tái),比如移植到Java,C++,VB等。
OQL的原理基于2大特性:
- 表達(dá)式的鏈?zhǔn)秸{(diào)用
- 屬性的實(shí)例調(diào)用
OQL支持4大類數(shù)據(jù)操作
- 數(shù)據(jù)查詢:
- 單實(shí)體類(單表)查詢
- 多實(shí)體類(多表)關(guān)聯(lián)查詢
- 數(shù)據(jù)修改
- 更新數(shù)據(jù)
- 刪除數(shù)據(jù)
- 統(tǒng)計(jì)、聚合運(yùn)算
- OQL分頁(yè)
1.2,老版本的局限
盡管OQL已經(jīng)可以解決80%的查詢需求,剩下的20%查詢需求我都建議框架用戶使用SQL-MAP技術(shù)來(lái)完成,但對(duì)于用戶而言,是不太愿意從ORM模式切換到SQL模式的,希望OQL能夠解決盡可能多的查詢需求。那么,PDF.NET Ver 4.X 版本的OQL有哪些不足呢?
1.2.1,自連接查詢
也稱為表自身連接查詢。OQL支持多表(實(shí)體)查詢的,但卻無(wú)法支持自連接查詢,原因是自連接查詢必須指定表的別名:
SELECT R1.readerid,R1.readername,R1.unit,R1.bookcount FROM ReaderInfo AS R1,ReaderInfo AS R2 WHERE R2.readerid=9704 AND R1.bookcount>R2.bookcount --連接關(guān)系 ORDER BY R1.bookcount
上面這個(gè)查詢是從ReaderInfo表查詢可借圖書數(shù)目比編號(hào)為9704讀者多的所有讀者信息,這里對(duì)表使用了別名來(lái)實(shí)現(xiàn)的,如果不使用別名,那么這個(gè)查詢就無(wú)法實(shí)現(xiàn)。而OQL之前的版本,是不支持表的別名的,因此,對(duì)于連接查詢,OQL生成的可能是這樣子的SQL語(yǔ)句:
SELECT teacher.*,student.* FROM teacher INNER JOIN student ON teacher.id=student.tea_id
1.2.2,子查詢
老版本OQL僅支持IN條件的子查詢,不能像SQL那么靈活的進(jìn)行各種子查詢,其實(shí)不支持的原因其中一個(gè)也是因?yàn)镺QL查詢不支持表的別名,另外一個(gè)原因是子查詢無(wú)法獲取到父查詢的表名和字段名。子查詢是一個(gè)很常用的功能,如果不能夠支持,那么就大大限制了OQL的使用范圍。
下面是來(lái)自SQLSERVER 聯(lián)機(jī)幫助的說(shuō)明:
子查詢也稱為內(nèi)部查詢或內(nèi)部選擇,而包含子查詢的語(yǔ)句也稱為外部查詢或外部選擇。
許多包含子查詢的 Transact-SQL 語(yǔ)句都可以改用聯(lián)接表示。其他問(wèn)題只能通過(guò)子查詢提出。在 Transact-SQL 中,包含子查詢的語(yǔ)句和語(yǔ)義上等效的不包含子查詢的語(yǔ)句在性能上通常沒有差別。但是,在一些必須檢查存在性的情況中,使用聯(lián)接會(huì)產(chǎn)生更好的性能。否則,為確保消除重復(fù)值,必須為外部查詢的每個(gè)結(jié)果都處理嵌套查詢。所以在這些情況下,聯(lián)接方式會(huì)產(chǎn)生更好的效果。以下示例顯示了返回相同結(jié)果集的 SELECT 子查詢和 SELECT 聯(lián)接:
/* SELECT statement built using a subquery. */ SELECT Name FROM AdventureWorks2008R2.Production.Product WHERE ListPrice = (SELECT ListPrice FROM AdventureWorks2008R2.Production.Product WHERE Name = 'Chainring Bolts' ); /* SELECT statement built using a join that returns the same result set. */ SELECT Prd1. Name FROM AdventureWorks2008R2.Production.Product AS Prd1 JOIN AdventureWorks2008R2.Production.Product AS Prd2 ON (Prd1.ListPrice = Prd2.ListPrice) WHERE Prd2. Name = 'Chainring Bolts';
1.2.3,OQL數(shù)據(jù)插入
盡管OQL可以支持實(shí)體類的批量更新與刪除,但沒有支持實(shí)體類的插入,原因是對(duì)單個(gè)實(shí)體類而言,可以直接調(diào)用EntityQuery<T>的Insert()方法實(shí)現(xiàn)。但項(xiàng)目中可能還是有需要寫SQL插入數(shù)據(jù)的情況,比如插入Int類型的值為0,如果用實(shí)體類的方式那么該列不會(huì)被插入,因?yàn)镻DF.NET的實(shí)體類認(rèn)為該屬性值沒有改變,PDF.NET的插入和更新操作,都只處理“屬性值改變過(guò)的”數(shù)據(jù)。另外,也不支持Insert....Select....From 這種批量插入方式。
INSERT INTO MySalesReason SELECT SalesReasonID, Name, ModifiedDate FROM AdventureWorks2008R2.Sales.SalesReason WHERE ReasonType = N'Marketing';
1.2.4,HAVING 子句
OQL老版本不支持該功能,盡管不是很常用,但配合分組查詢還是有用的,如下面的例子HAVING 子句從 SalesOrderDetail 表中檢索超過(guò) $100000.00 的每個(gè) SalesOrderID 的總計(jì)。
SELECT SalesOrderID, SUM(LineTotal) AS SubTotal FROM Sales.SalesOrderDetail GROUP BY SalesOrderID HAVING SUM(LineTotal) > 100000.00 ORDER BY SalesOrderID ;
二、浴火重生
現(xiàn)在流行的ORM框架很多,剛剛在寫本文的時(shí)候,還發(fā)現(xiàn)有個(gè)朋友寫了一篇各ORM對(duì)比測(cè)試的文章:
這么多ORM框架,我并不是很熟悉,PDF.NET的目標(biāo)只想在某些方面趕超MS的EF框架,據(jù)說(shuō)現(xiàn)在EF6都快出來(lái)了,EF4.5在性能上上了一個(gè)臺(tái)階。面對(duì)EF這個(gè)強(qiáng)敵,如果PDF.NET不能解決前面說(shuō)的幾大缺陷,注定距離會(huì)越來(lái)越遠(yuǎn),PDF.NET的用戶對(duì)我也是常常提出批評(píng),紛紛轉(zhuǎn)投EF去了,對(duì)此我深感壓力山大!
盡管EF是PDF.NET ORM 的強(qiáng)勁對(duì)手,但 PDF.NET ORM的查詢語(yǔ)言O(shè)QL,相對(duì)于EF的查詢語(yǔ)言Linq,還是有自己獨(dú)立的特色,OQL比Linq更接近SQL,Linq是VS的語(yǔ)法糖,本質(zhì)上VS編譯器會(huì)將它轉(zhuǎn)化成Lambda表達(dá)式,進(jìn)一步轉(zhuǎn)換成表達(dá)式樹,最后翻譯成SQL語(yǔ)句交給數(shù)據(jù)庫(kù)去執(zhí)行。所以我們會(huì)看到針對(duì)集合操作的擴(kuò)展方法,有很多都要使用 => 的調(diào)用方式,而OQL沒有使用Lambda,它是怎么獲取到查詢對(duì)應(yīng)的表名稱和字段名稱的呢?它是怎么實(shí)現(xiàn)SQL查詢的層次結(jié)構(gòu)的呢?
2.1,屬性字段的秘密
PDF.NET屬性的定義采用下面的形式:
public System.String UserName { get { return getProperty<System.String>("UserName"); } set { setProperty("UserName", value, 50); } }
因此在獲取實(shí)體類的屬性值的時(shí)候,調(diào)用了getProperty<T>("字段名") 這個(gè)方法,它里面會(huì)觸發(fā)屬性讀取事件:
/// <summary> /// 獲取屬性值 /// </summary> /// <typeparam name="T">值的類型</typeparam> /// <param name="propertyName">屬性名稱</param> /// <returns>屬性值</returns> protected T getProperty<T>(string propertyName) { this.OnPropertyGeting(propertyName); return CommonUtil.ChangeType<T>(PropertyList(propertyName)); } /// <summary> /// 屬性獲取事件 /// </summary> public event EventHandler<PropertyGettingEventArgs> PropertyGetting; /// <summary> /// 獲取屬性的時(shí)候 /// </summary> /// <param name="name"></param> protected virtual void OnPropertyGeting(string name) { if (this.PropertyGetting != null) { this.PropertyGetting(this, new PropertyGettingEventArgs(name)); } }
/// <summary> /// 屬性獲取事件 /// </summary> public class PropertyGettingEventArgs : EventArgs { private string _name; /// <summary> /// 屬性名稱 /// </summary> public string PropertyName { get { return _name; } set { _name = value; } } /// <summary> /// 以屬性名稱初始化本類 /// </summary> /// <param name="name"></param> public PropertyGettingEventArgs(string name) { this.PropertyName = name; } }
而OQL實(shí)例對(duì)象,正是訂閱了EventHandler<PropertyGettingEventArgs> 事件:
public OQL(EntityBase e) { currEntity = e; //其它代碼略 e.PropertyGetting += new EventHandler<PropertyGettingEventArgs>(e_PropertyGetting); } void e_PropertyGetting(object sender, PropertyGettingEventArgs e) { //其它代碼略 }
所以,在屬性獲取事件中,我們可以通過(guò)PropertyGettingEventArgs.PropertyName 得到實(shí)體類屬性對(duì)應(yīng)的字段名稱,因此,我們就可以方便的做到選取我們本次查詢需要的字段,例如下面的OQL查詢:
Users user = new Users(); OQL q0 = OQL.From(user) .Select(user.ID, user.UserName, user.RoleID) .END;
對(duì)應(yīng)的SQL語(yǔ)句:
SELECT [ID], [UserName], [RoleID] FROM [LT_Users]
這樣,我們無(wú)需使用委托,也不需要Lambda表達(dá)式,更不需要表達(dá)式樹,就能夠直接獲取到要查詢的表名稱和字段名稱,寫法比Linq更簡(jiǎn)潔,處理速度更快速。當(dāng)然,代價(jià)是要先實(shí)例化實(shí)體類。
2.1,屬性獲取事件的變化
在事件方法e_PropertyGetting 中,我們看看PDF.NET Ver 5.0前后的變化:
Ver 4.X 以前:
void e_PropertyGetting(object sender, PropertyGettingEventArgs e) { doPropertyGetting(((EntityBase)sender).TableName, e.PropertyName); } private void doPropertyGetting(string tableName, string propertyName) { if (isJoinOpt) { string propName = "[" + tableName + "].[" + propertyName + "]"; this.currJoinEntity.AddJoinFieldName(propName); } else { string field = this.joinedString.Length > 0 ? tableName + "].[" + propertyName : propertyName; if (!hasSelected && !selectedFields.Contains(field)) selectedFields.Add(field); } this.getingTableName = tableName; this.getingPropertyName = propertyName; }
在doPropertyGetting 方法中,區(qū)分是否有實(shí)體類連接查詢,來(lái)處理不同的表名稱和字段名稱,這里看到連接查詢的時(shí)候沒有為表加上別名,而是直接使用了“表名稱.字段名稱”這種表示字段的形式。同時(shí),將當(dāng)前獲取到的表字段名,馬上賦值給getingPropertyName 變量。這帶來(lái)了一個(gè)問(wèn)題,屬性字段名稱必須馬上被使用,否則就會(huì)出問(wèn)題。
由于不同的情況使用屬性字段的時(shí)機(jī)不一樣,為了處理這些不同的情況加入了各種Case下的處理代碼,比如將Select方法要使用的屬性字段名稱保存到列表 selectedFields 中。這種處理方法無(wú)疑大大增加了代碼的復(fù)雜度。
Ver 5.0 版本的改進(jìn)
前面說(shuō)到屬性獲取到的屬性字段名稱必須馬上被使用,否則就會(huì)出問(wèn)題。如果我們不論何種情況,都將這個(gè)屬性字段名先保存起來(lái)再使用呢?使用隊(duì)列?鏈表?堆棧?這些集合都可以,但在編譯原理中,對(duì)表達(dá)式的處理都是使用堆棧來(lái)做的,其中必有它的好處,以后會(huì)體會(huì)到。
下面是屬性獲取事件代碼:
void e_PropertyGetting(object sender, PropertyGettingEventArgs e) { TableNameField tnf = new TableNameField() { Field = e.PropertyName, Entity = (EntityBase)sender, Index=this.GetFieldGettingIndex() }; fieldStack.Push(tnf); }
這里直接將屬性字段名存在TablenameField 結(jié)構(gòu)的Field字段中,然后將這個(gè)結(jié)構(gòu)壓入堆棧對(duì)象fieldStack 中,需要的時(shí)候在從堆棧中彈出最新的一個(gè) TableNameField 結(jié)構(gòu)。這樣,不論是OQL的Select方法,Where方法還是OrderBy方法,都能夠使用統(tǒng)一的堆棧結(jié)構(gòu)來(lái)獲取方法使用的屬性字段了。
2.3,統(tǒng)一屬性獲取事件
除了OQL本身需要“屬性獲取事件”,OQL關(guān)聯(lián)的OQLCompare對(duì)象,OQLOrder對(duì)象,都需要處理屬性獲取事件,比如之前實(shí)例化OQLCompare對(duì)象:
/// <summary> /// 使用一個(gè)實(shí)體對(duì)象初始化本類 /// </summary> /// <param name="e"></param> public OQLCompare(EntityBase e) { this.CurrEntity = e; //this.CurrEntity.ToCompareFields = true; this.CurrEntity.PropertyGetting += new EventHandler<PropertyGettingEventArgs>(CurrEntity_PropertyGetting); } /// <summary> /// 使用多個(gè)實(shí)體類進(jìn)行連接查詢的條件 /// </summary> /// <param name="e"></param> /// <param name="joinedEntitys"></param> public OQLCompare(EntityBase e, params EntityBase[] joinedEntitys) { this.CurrEntity = e; this.CurrEntity.PropertyGetting += new EventHandler<PropertyGettingEventArgs>(CurrEntity_PropertyGetting); //處理多個(gè)實(shí)體類 if (joinedEntitys != null && joinedEntitys.Length > 0) { this.joinedEntityList = new List<EntityBase>(); foreach (EntityBase item in joinedEntitys) { this.joinedEntityList.Add(item); item.PropertyGetting += new EventHandler<PropertyGettingEventArgs>(CurrEntity_PropertyGetting); } } }
屬性獲取事件處理方法:
void CurrEntity_PropertyGetting(object sender, PropertyGettingEventArgs e) { if (this.joinedEntityList != null) { this.currPropName = "[" + ((EntityBase)sender).TableName + "].[" + e.PropertyName + "]"; } else { this.currPropName = "[" + e.PropertyName + "]"; //propertyList.Add(e.PropertyName); } }
之所以要在OQLCompare等對(duì)象中也要處理屬性獲取事件,是為了OQLCompare能夠獨(dú)立使用,但這帶來(lái)一些問(wèn)題:
- 各地的屬性獲取事件處理代碼類似,代碼有冗余;
- 沒有體現(xiàn)出OQL跟OQLCompare 、OQLOrder對(duì)象之見的聚合性,呈現(xiàn)出松散的結(jié)構(gòu),因此可能出現(xiàn)OQLCompare使用的實(shí)體類在OQL中沒有使用,從而產(chǎn)生錯(cuò)誤的查詢;
- OQLCompare中的的字段名與OQL缺乏相關(guān)性,因此只能通過(guò)“表名稱.字段名稱”這種形式來(lái)使用屬性字段名,無(wú)法使用別名。
Ver 5.0的解決辦法:
在OQL對(duì)象上,定義一些方法供OQL的關(guān)聯(lián)子對(duì)象來(lái)訪問(wèn)需要的屬性字段名信息:
/// <summary> /// 從堆棧上只取一個(gè)字段名 /// </summary> /// <returns></returns> protected internal string TakeOneStackFields() { //其它代碼略 TableNameField tnf = fieldStack.Pop(); return GetOqlFieldName(tnf); }
2.4,SQL的語(yǔ)法結(jié)構(gòu)
SQL是結(jié)構(gòu)化查詢語(yǔ)言,它自身也是非常結(jié)構(gòu)化的,每一個(gè)查詢都有固定的語(yǔ)法結(jié)構(gòu),以Select為例,它可以有多種形式的寫法:
SELECT Field1,Field2... FROM [Table] ----------------- SELECT Field1,Field2... FROM [Table] WHERE Condition ----------------- SELECT Field1,Field2... FROM [Table] WHERE Condition ORDER BY FieldOrder ----------------- SELECT FieldGroup FROM [Table] WHERE Condition GROUP BY FieldGroup ORDER BY FieldOrder ---------------- SELECT FieldGroup FROM [Table] WHERE Condition GROUP BY FieldGroup HAVING havingExp ORDER BY FieldOrder ----------------- SELECT Field1,Field2... FROM [Table1] JOIN [Table2] ON [Table1].PK=[Table2].FK WHERE Condition ORDER BY FieldOrder ----------------- SELECT Field1,Field2... FROM [Table1],[Table2] WHERE [Table1].PK=[Table2].FK And OtherCondition ORDER BY FieldOrder -----------------
仔細(xì)觀察,萬(wàn)變不離其宗,上面的寫法其實(shí)都是以下關(guān)鍵字所在層次結(jié)構(gòu)在不同情況下的組合而已,除了Select是必須的:
SELECT FROM JOIN ON WHERE GROUP BY HAVEING ORDER BY
2.5,OQL的層次結(jié)構(gòu)
如果要以面向?qū)ο蟮姆绞絹?lái)實(shí)現(xiàn)SQL這個(gè)關(guān)鍵字層次結(jié)構(gòu),我們必須將相關(guān)的關(guān)鍵字作為方法,定義在合適的對(duì)象中,然后靠對(duì)象的層次結(jié)構(gòu),來(lái)限定正確的“SQL”結(jié)構(gòu),為此,我們先重新來(lái)定義一下OQL使用的接口IOQL和關(guān)聯(lián)的接口的層次定義:
public interface IOQL { OQL1 Select(params object[] fields); } public interface IOQL1 : IOQL2 { //OQL End { get; } //OQL3 GroupBy(object field); //OQL4 Having(object field); //OQL4 OrderBy(object field); OQL2 Where(params object[] fields); } public interface IOQL2 : IOQL3 { //OQL End { get; } OQL3 GroupBy(object field); //OQL4 Having(object field); //OQL4 OrderBy(object field); } public interface IOQL3 : IOQL4 { OQL4 Having(object field); //OQL End { get; } //OQL4 OrderBy(object field); } public interface IOQL4 { OQL END { get; } OQLOrderType OrderBy(object field); }
然后,讓OQL實(shí)現(xiàn)IOQL,在定義其他OQL子對(duì)象來(lái)實(shí)現(xiàn)其它子接口。由于對(duì)象比較多,還是通過(guò)一個(gè)對(duì)象結(jié)構(gòu)圖來(lái)看更方便:
圖1:OQL接口層次圖
圖2:OQL體系結(jié)構(gòu)圖
2.6 OQLCompare--比較對(duì)象的組合模式
SQL的查詢條件可以很簡(jiǎn)單,也可以很復(fù)雜,比如下面的復(fù)合查詢條件:
SELECT M.*,T0.* FROM [LT_Users] M INNER JOIN [LT_UserRoles] T0 ON M.[RoleID] = T0.[ID] WHERE ( M.[UserName] = @P0 AND M.[Password] = @P1 AND T0.[RoleName] = @P2 ) OR ( ( M.[UserName] = @P3 AND M.[Password] = @P4 AND T0.[ID] IN (1,2,3) ) OR M.[LastLoginTime] > @P5 )
這個(gè)查詢條件分為2組條件,然后第二組查詢內(nèi)部又包含2組查詢,從括號(hào)層數(shù)來(lái)說(shuō),僅僅有3層,但看起來(lái)已經(jīng)夠復(fù)雜了。實(shí)際項(xiàng)目中,我曾遇到過(guò)用5000行業(yè)務(wù)代碼來(lái)構(gòu)造SQL查詢條件的情況,不要吃驚,的確是5000行業(yè)務(wù)代碼,當(dāng)然不是說(shuō)SQL條件有5000行,但也可以想象到,最終生成的SQL查詢條件的長(zhǎng)度不會(huì)小于50行。這樣復(fù)雜的查詢條件,如果用拼接SQL字符串的方式來(lái)完成,工作量是不可想象的,維護(hù)起來(lái)也是非常困難。但是,我們可以利用OQL的查詢條件對(duì)象OQLCompare來(lái)完成,因?yàn)樗鼘?shí)質(zhì)上是一個(gè)組合對(duì)象,即N多個(gè)OQLCompare組合成一個(gè)OQLCompare對(duì)象,不過(guò)為了實(shí)現(xiàn)方便,我們規(guī)定每個(gè)OQLCompare對(duì)象下面存放2個(gè)子對(duì)象,也就是建立一個(gè)二叉樹來(lái)存儲(chǔ)所有的比較對(duì)象:
public class OQLCompare { //其它代碼略 protected OQLCompare LeftNode { get; set; } protected OQLCompare RightNode { get; set; } protected CompareLogic Logic { get; set; } protected bool IsLeaf { get { return object.Equals(LeftNode, null) && object.Equals(RightNode, null); } } protected string ComparedFieldName; protected string ComparedParameterName; protected CompareType ComparedType; }
還是用一張圖來(lái)看看查詢條件的構(gòu)成比較直觀:
圖3:OQLCompare 對(duì)象樹
該圖的內(nèi)容,說(shuō)明了構(gòu)造上面的SQL條件的OQLCompare比較對(duì)象的樹型結(jié)構(gòu),我們規(guī)定,每個(gè)節(jié)點(diǎn)下面只有左節(jié)點(diǎn)和右節(jié)點(diǎn),左節(jié)點(diǎn)優(yōu)先,左右子節(jié)點(diǎn)都可以是空,如果符合該條件,則當(dāng)前節(jié)點(diǎn)為葉子結(jié)點(diǎn),否則為枝節(jié)點(diǎn)。
從上圖可以很容易發(fā)現(xiàn),其實(shí)這就是一個(gè)“組合模式”,而組合模式的每個(gè)節(jié)點(diǎn)都具有相同的行為和特性,所以,我們可以構(gòu)建非常復(fù)雜的組合體系,最終構(gòu)造超級(jí)復(fù)雜的查詢條件,而在最終使用上,一組查詢條件跟一個(gè)查詢條件的處理過(guò)程是一樣的。
2.7,條件表達(dá)式的括號(hào)問(wèn)題
括號(hào)是控制表達(dá)式計(jì)算順序的重要手段,對(duì)于邏輯表達(dá)式,使用AND,OR 來(lái)連接兩個(gè)子表達(dá)式,如果AND,OR同時(shí)出現(xiàn),則需要用括號(hào)來(lái)改變表達(dá)式元素計(jì)算的順序。C,C++,C# 對(duì)表達(dá)式都是“左求值計(jì)算”的,這是一個(gè)很重要的概念,某些程序語(yǔ)言可能是“右求值計(jì)算”的。如果表達(dá)式中有括號(hào),那么前面的計(jì)算將掛起,計(jì)算完括號(hào)內(nèi)的結(jié)果后,再繼續(xù)處理表達(dá)式的剩余部分。因此,我們可以把括號(hào)看作一個(gè)“樹枝節(jié)點(diǎn)”,而括號(hào)內(nèi)最內(nèi)層的節(jié)點(diǎn),為葉子結(jié)點(diǎn),按照我們對(duì)節(jié)點(diǎn)類型的定義和上面示例的OQLCompare條件組合樹,在輸出SQL條件字符串的時(shí)候,可能是這個(gè)樣子的:
SELECT M.*,T0.* FROM [LT_Users] M INNER JOIN [LT_UserRoles] T0 ON M.[RoleID] = T0.[ID] WHERE (( ( M.[UserName] = @P0 AND M.[Password] = @P1) AND T0.[RoleName] = @P2 ) OR ( ( (M.[UserName] = @P3 AND M.[Password] = @P4) AND T0.[ID] IN (1,2,3) ) OR M.[LastLoginTime] > @P5 ) )
假設(shè)條件表達(dá)式需要對(duì)10個(gè)字段的比較內(nèi)容進(jìn)行AND 判斷,那么將會(huì)嵌套10-1=9 層括號(hào)。
不要小看這個(gè)問(wèn)題,前面我說(shuō)到的那個(gè)5000行業(yè)務(wù)代碼構(gòu)建SQL查詢條件的事情,就曾經(jīng)發(fā)生過(guò)構(gòu)造了128層括號(hào)的事情,最終導(dǎo)致SQLSERVER報(bào)錯(cuò):
查詢條件括號(hào)嵌套太多,查詢分析器無(wú)法處理!
那么括號(hào)怎么化簡(jiǎn)呢?
這個(gè)得從表達(dá)式的邏輯語(yǔ)義上去分析:
- (A AND B) AND C <==> A AND B AND C
- (A OR B) OR C <==> A OR B OR C
- (A AND B) AND (C AND D) <==> A AND B AND C AND D
- (A OR B) OR (C OR D) <==> A OR B OR C OR D
- (A AND B) AND (C AND D) <==> A AND B AND C AND D
- (A OR B) OR C <==> A OR B OR C
所以,我們可以檢查“子樹枝節(jié)點(diǎn)”的邏輯比較類型,如果它的類型與當(dāng)前節(jié)點(diǎn)的邏輯比較類型相同,那么對(duì)子樹枝節(jié)點(diǎn)的處理就不需要使用括號(hào)了。
可以通過(guò)哦遞歸過(guò)程,處理完所有的子節(jié)點(diǎn)的括號(hào)問(wèn)題,從而最終得到我們看起來(lái)非常簡(jiǎn)單的條件表達(dá)式。
(本文篇幅太長(zhǎng),未完待續(xù))
posted on 2013-07-26 17:26 深藍(lán)醫(yī)生 閱讀(12401) 評(píng)論(28) 收藏 舉報(bào)
浙公網(wǎng)安備 33010602011771號(hào)