相關文章內容索引:
[概念回顧]
我們在前一篇《ORM查詢語言(OQL)簡介--概念篇》中了解到,SQL跟ORM的關系,ORM會自動生成SQL語句並執行,但普通的ORM框架卻不能靈活的生成需要的SQL語句,我們需要一種具有SQL靈活性的的但卻能夠面向對象的ORM查詢語言(ORM Query Language)--OQL。Hibernate的HQL,MS Entity Framework的ESQL都是這樣的一種語言,雖然HQL和ESQL解決了它們框架OO使用方式的不靈活,但卻是字符串類型的查詢語句,使用起來並不便利,好在EF一般都是使用Linq表達式來編寫查詢,但Linq方式跟SQL在語法上還是有很大的差異,特別是Linq的左、右連接查詢,跟SQL差異很大。而PDF.NET框架的OQL,應該是三者跟SQL最為接近的一種查詢語言。
總結起來,OQL有下面3個顯著特點:
- 抽象的SQL,屏蔽了具體數據庫的差異,因此支持所有數據庫;
- 對象化的“SQL”,寫OQL代碼能夠獲得IDE的智能提示;
- 沒有使用.NET的特性,比如泛型、反射、表達式樹等東西,因此理論上OQL可以跨語言平台,比如移植到Java,C++,VB等。
PS:PDF.NET並不僅僅是一個ORM框架,它是一個多模式的開發框架,詳見官網說明 http://www.pwmis.com/sqlmap
在前一篇中,我們使用了巴科斯范式(NBF)來描述OQL的語法,但不少朋友不太清楚具體該如何使用,本篇我們將使用實例來說明如何使用OQL。
[OQL原理]
.表達式的鏈式調用
OQL的設計完全基於面向對象的實體查詢,OQL的使用采用對象表達式的方式,內部實現原理是一系列的“鏈式方法調用”。為了完整實現SQL的查詢過程,需要為這些表達式方法進行分級:
- 根表達式(OQL)、
- 一級表達式(OQL1)、
- 二級表達式(OQL2、OQLCompare等)
每一級表達式會生成是和使用下一級表達式,比如OQL調用返回OQL1對象的方法,而OQL1對象又調用返回OQL2級對象的方法。
將表達式按照層級划分,保證了編寫OQL語句的正確性,可以避免因SQL語法不熟悉的開發人員寫出錯誤的SQL語句,另外由於面向對象的方式,還可以避免寫錯數據庫的表和字段名,在程序的編譯階段就發現錯誤而不是等到程序運行時。
.屬性的實例調用
使用ORM,涉及到一個繞不開的問題,就是如何獲取表的字段,EF是通過Linq來進行翻譯的,本質上不是直接調用得到字段名稱,在調用的時候,都是通過泛型方式的Lambda表達式來做的,這樣是比較方便,但PDF.NET采用了另外一種方式,就是實體類的屬性調用方式,來得到字段的名稱。
為什么要使用這種方式?我主要是基於以下幾點問題考慮:
- 平台無關性:對象的屬性調用只要是面向對象的語言都可以,比如C++,VB,.NET,Java....,OQL是可以進行其它平台移植的
- .NET框架低版本支持:框架僅需.NET 2.0 支持,如果引入Linq方式,那么意味着框架需要.net 3.5及以上版本支持
- 簡化條件調用:在Where方法中直接調用實體類的屬性,不僅得到了調用的字段名,還得到了要查詢的字段值
[示例說明]
在PDF.NET的開源項目(http://pwmis.codeplex.com )中,有一個示例項目:《超市管理系統》,該項目演示了OQL的基本使用。下面我們說明下如何具體使用這些功能,詳細的實例代碼大家可以去這個開源網站下載。
一、OQL 數據查詢:
[示例1]--查詢所有收銀員:
收銀員只是雇員的一種類型,因此我們從雇員表中查找工作崗位類型名稱是收銀員的雇員信息,並且以姓名排序:
Employee emp = new Employee(); emp.JobName = "收銀員"; OQL q = OQL.From(emp) .Select(emp.WorkNumber,emp.EmployeeName) .Where(emp.JobName) .OrderBy(emp.EmployeeName, "asc") .END;
下面,我們對這段代碼中的OQL方法進行詳細的說明。
1.1、OQL根表達式
--返回OQL對象的方法或者屬性調用
1.1.1,From 方法:
是一個靜態方法,它以一個實體類對象為參數,返回值是一個OQL實例對象:
/// <summary> /// 靜態實體對象表達式 /// </summary> /// <param name="e">實體對象實例</param> /// <returns>實體對象查詢表達式</returns> public static OQL From(EntityBase e) { //... ... }
1.1.2,實例方法
除了使用From靜態方法構造OQL的實例,也可以采取直接調用構造函數的方式,比如本例這樣使用:
OQL q=new OQL(emp); q.Select(emp.WorkNumber,emp.EmployeeName) .Where(emp.JobName) .OrderBy(emp.EmployeeName, "asc");
1.1.3,End 屬性
從前面可以知道,可以靜態From方式和直接調用構造函數方式的得到OQL,前者結尾有一個 .End 屬性調用,因為 OrderBy 方法返回的對象是OQL1,而不是OQL,所以需要調用End 屬性,返回本次操作OQL的當前對象實例,下面的方法實現能夠說明這個原理:
public OQL END { get { return this.CurrOQL; } }
當我們需要在一行代碼內進行查詢的時候,調用End屬性非常方便,它可以直接把當前OQL對象返回給EntityQuery查詢對象,比如一行代碼查詢用戶列表:
var userList=OQL.From<User>().Select().End.ToList<User>();
注意: 這里用上了PDF.NET框架的OQL擴展,需要項目引用PWMIS.Core.Extensions.dll 以及在你的代碼文件上引入名字空間:
using PWMIS.Core.Extensions;
詳細例子請參看《不使用反射,“一行代碼”實現Web、WinForm窗體表單數據的填充、收集、清除,和到數據庫的CRUD》
1.2、OQL一級表達式
--返回OQL1對象的方法或者屬性調用,該級別的方法由OQL根級表達式生成並使用。
1.2.1,Select 方法:
選取查詢需要的實體類屬性,下面是方法定義:
/// <summary> /// 選取實體對象的屬性 /// </summary> /// <param name="fields">屬性字段列表</param> /// <returns>實體對象查詢基本表達式</returns> public OQL1 Select(params object[] fields) { //... ... }
比如這里選取了雇員實體類的 工號(WorkNumber)、雇員名稱(EmployeeName)兩個屬性,實際上,雇員表有多個字段:
"工號", "姓名", "性別","出生日期","入職時間","職務名稱"
但我們這里不需要這么多,只需要上面2個即可。
“字段名按需選取”應該是一個成熟的ORM框架具備的功能之一,如果需要選取全部字段,也就是SQL的*:
Select * From table
那么OQL1的Select方法不傳入參數即可:
OQL q=new OQL(emp); q.Select();
選取多個實體屬性(多表字段):
上面的例子是選取單個實體(表)的方式,選取多個實體類的屬性是類似的,Select方法的參數使用不同的實體類的屬性即可:
OQL q=OQL.From(entity1)
.InnerJoin(entity2).On(entity1.PK,entity2.FK)
.Select(entity1.Field1,entity1.Field2,entity2.Field1,entity2.Field2....)
.End;
正是因為PDF.NET的OQL的Select等方法,都是使用“實體類.屬性”調用的方式,使得操作多個實體類方便快捷,試想如果采用泛型,這個Select方法應該有多少個重載呢?
1.2.2,Where方法:
設置OQL的查詢條件。
Where方法有幾種重載,每種方法各有特點,先看看方法聲明:
1.2.2.1,直接使用多個條件屬性作為並列的Where查詢條件
適用於直接利用屬性值作為字段“=”值操作的“And”條件方式:
/// <summary> /// 獲取並列查詢條件,如 Where(u.Uid,u.Name); /// </summary> /// <param name="expression">實體屬性列表</param> /// <returns>基本表達式</returns> public OQL1 Where(params object[] expression)
比如本文的例子,查找制定工作職位名的雇員信息:
q.Select(emp.WorkNumber,emp.EmployeeName) .Where(emp.JobName) .OrderBy(emp.EmployeeName, "asc");
1.2.2.2,使用OQL2 條件對象作為參數
可以按照順序進行條件的Not,And,Or操作,方法定義:
/// <summary> /// 獲取復雜查詢條件 /// </summary> /// <param name="c">多條件表達式</param> /// <returns>基本表達式</returns> public OQL1 Where(OQL2 c)
OQL對象實例中已經有一個OQL2對象的屬性Condition,可以直接使用,下面是例子:
[示例2]--獲取所有的可售商品總數
/// <summary> /// 獲取所有的可售商品總數 /// </summary> /// <returns></returns> public int GetGoodsStockCount() { GoodsStock stock = new GoodsStock(); OQL q = new OQL(stock); q.Select() .Count(stock.Stocks, "庫存數量") .Where(q.Condition.AND(stock.Stocks, ">", 0)); stock = EntityQuery<GoodsStock>.QueryObject(q); return stock.Stocks; }
1.2.2.3,使用OQLCompare對象作為參數
OQLCompare 對象是一個組合對象,也就是N個OQLComapre可以按照各種條件組合成一個OQLCompare對象,從而構造超級復雜的Where查詢條件,支持帶括號“()”的條件組合,后面會有實例展示。
/// <summary> /// 獲取復雜查詢條件(具有邏輯優先級的復雜比較條件) /// </summary> /// <param name="compare">實體對象比較類</param> /// <returns>基本表達式</returns> public OQL1 Where(OQLCompare compare)
下面是一個使用OQLComapre對象處理非常復雜的條件查詢例子,

/// <summary> /// 生成查詢數據的OQL對象 /// </summary> /// <param name="qcItem"></param> /// <returns></returns> private OQL getOQLSelect(QueryConditionModel qcItem) { TsCarSource cs = initCarSourceFromQueryCondition(qcItem); PublishInfo ap = new PublishInfo(); // StartTime,EndTime 屬於復雜查詢 BidRecord br = new BidRecord(); TstOrder tst = new TstOrder(); OQLCompare cmpResult = getOQLCompare(qcItem, cs, ap, tst); OQL q = OQL.From(cs) .InnerJoin(ap).On(cs.CarSourceID, ap.CarSourceId) .LeftJoin(tst).On(cs.CarSourceID, tst.CarSourceID) .Select( cs.CarSourceID, cs.ProducerId, cs.BrandId, cs.CityAreaId, cs.CarSourceOwner, cs.CityId, cs.CarIdentityNumber, cs.TvaID, cs.CarSourceOwner, cs.LicenseNumber, cs.CarUseType, cs.PurchasePrice, ap.PublishId, ap.StartTime, ap.EndTime, ap.ReservationPrice, ap.StartPrice,ap.HighestBidprice,tst.BargainPrice,ap.Status,ap.BidCount,ap.BidListCount,ap.PriceEndTime,ap.StopTime,ap.PriceStopTime, cs.MasterBrandId,cs.CarTypeId,cs.CarBodyColor,cs.Mileage ) .Where(cmpResult) .OrderBy(ap.StartTime, "desc") //PublishInfo.StartTime desc .END; return q; } /// <summary> /// 生成條件比較對象 /// </summary> /// <param name="qcItem"></param> /// <param name="cs"></param> /// <param name="ap"></param> /// <param name="br"></param> /// <returns></returns> private OQLCompare getOQLCompare(QueryConditionModel qcItem, TsCarSource cs, PublishInfo ap, TstOrder tst) { OQLCompare cmp = new OQLCompare(cs, ap, tst); OQLCompare cmpResult = null; if (qcItem.AccountType.Value == 1) { cs.TvaID = qcItem.TvaId.Value; cmpResult = cmp.Comparer(cs.TvaID); } else { string tvaids = GetTvaidList(qcItem.TvaId.Value);//in cmpResult = cmp.Comparer(cs.TvaID, OQLCompare.CompareType.IN, tvaids);//in } if (qcItem.TabIndex == 1)//拍賣中 { ap.Status = 1; cmpResult = cmpResult&cmp.Comparer(ap.Status); } else //PriceStatus大於1,為拍賣結束的 { cmpResult = cmpResult&cmp.Comparer(ap.Status, OQLCompare.CompareType.Greater, 1); } if (qcItem.QueryType == 0) { //默認查詢條件 } else if (qcItem.QueryType == 1) { //按照Vin碼或者號牌進行查詢 cmpResult = cmpResult & ( cmp.Comparer(cs.LicenseNumber, OQLCompare.CompareType.Like,"%"+ qcItem.VinCodeOrPlateNumber+"%") | cmp.Comparer(cs.CarIdentityNumber, OQLCompare.CompareType.Like, "%" + qcItem.VinCodeOrPlateNumber + "%") ); } else { //其它復雜查詢條件 //處理區域條件 if (qcItem.CityId.HasValue && qcItem.CityId.Value > 0) { cmpResult = cmpResult & cmp.Comparer(cs.CityId, "=", qcItem.CityId.Value); } else if (qcItem.ProvinceId.HasValue && qcItem.ProvinceId.Value > 0) { cmpResult = cmpResult & cmp.Comparer(cs.ProvinceId, "=", qcItem.ProvinceId.Value); } else if (qcItem.BigAreaId.HasValue && qcItem.BigAreaId.Value > 0) { var provinces = new CommonDL().GetProvincesByBigAreaID(qcItem.BigAreaId.Value); if (provinces.Count > 0) { var provinceids = string.Join(",", provinces.Select(o => o.ProvinceId)); cmpResult = cmpResult & cmp.Comparer(cs.ProvinceId, OQLCompare.CompareType.IN, provinceids); } } //end if (!string.IsNullOrEmpty(qcItem.State) && qcItem.State!="0"&&qcItem.TabIndex.Value==2) cmpResult = cmpResult & cmp.Comparer(ap.Status, "=", Convert.ToByte( qcItem.State)); //if (cs.CityAreaId > 0) // cmpResult = cmpResult & cmp.Comparer(cs.CityAreaId); if(qcItem.BrandId.HasValue&&qcItem.BrandId.Value>0) cmpResult = cmpResult & cmp.Comparer(cs.MasterBrandId, "=", qcItem.BrandId.Value); if (qcItem.SerialId.HasValue&&qcItem.SerialId.Value>0) cmpResult = cmpResult & cmp.Comparer(cs.BrandId,"=",qcItem.SerialId.Value); if (qcItem.StartTime.HasValue) cmpResult = cmpResult & cmp.Comparer(ap.PriceStopTime, OQLCompare.CompareType.GreaterThanOrEqual, qcItem.StartTime.Value); if(qcItem.EndTime.HasValue) cmpResult = cmpResult & cmp.Comparer(ap.PriceStopTime, OQLCompare.CompareType.LessThanOrEqual, qcItem.EndTime.Value.AddDays(1)); if (!string.IsNullOrEmpty(qcItem.EndTimePoint)) { string[] items= qcItem.EndTimePoint.Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries); //多個時間點的OR條件組合 List<OQLCompare> OrCmp1 = new List<OQLCompare>(); foreach (string item in items) { int hour = int.Parse(item); if (hour != 0) { //今日3個時段結束的 OQLCompare cmpTemp = cmp.Comparer(ap.PriceEndTime, "=", hour, "DATEPART(hh,{0})"); OrCmp1.Add(cmpTemp); } else { //明日結束的 OQLCompare cmpOther= cmp.Comparer(ap.PriceEndTime, ">=", DateTime.Now.Date.AddDays(1)); OrCmp1.Add(cmpOther); } } cmpResult = cmpResult & cmp.Comparer(OrCmp1, OQLCompare.CompareLogic.OR); } } return cmpResult; }
PS:由於查詢比較復雜,請先看了下面有關OQL關聯實體查詢之后再看本例。這個示例中的OQLCompare對象使用方式已經過時,請看本文【2.1.1,更新指定范圍的數據】黃色標記的部分,現在OQLCompare對象不支持從實體類實例化,而是必須從OQL對象實例化。。
執行查詢會生成如下復雜的SQL語句:
SELECT cs.CarSourceID, ap.PublishId, cs.ProducerId,cs.BrandId,cs.CityAreaId, cs.ProducerId,cs.CityId,cs.CarIdentityNumber,cs.TvaID,cs.CarSourceOwner, cs.LicenseNumber,cs.CarUseType,cs.CarStatus, ap.StartTime,ap.EndTime, ap.AnticipantPrice,ap.StartPrice, br.CurrPrice FROM dbo.TranstarCarSource AS cs INNER JOIN dbo.PublishInfo AS ap ON cs.CarSourceID=ap.CarSourceId INNER JOIN dbo.BidRecord AS br ON ap.PublishId=br.PublishId WHERE cs.CarStatus=1
And (/* 這里是動態構造的查詢條件*/ )
1.2.2.4,使用QueryParameter 數組作為並列的查詢參數
適合於專門的表單查詢界面,比如指定日期字段要大於某天且要小於某天。將表單查詢頁面的控件的值收集到QueryParameter 對象即可完成此查詢。
/// <summary> /// 根據傳入的查詢參數數組,對字段名執行不區分大小寫的比較,生成查詢條件。 /// </summary> /// <param name="queryParas">查詢參數數組</param> /// <returns>條件表達式</returns> public OQL1 Where(QueryParameter[] queryParas)
1.2.3,OrderBy方法:
設置OQL的排序方式,分為2種方式:
1.2.3.1,直接指定排序屬性和方式:
/// <summary> /// 設定排序條件 /// </summary> /// <param name="field">實體對象屬性</param> /// <param name="orderType">排序類型 ASC,DESC</param> /// <returns></returns> public OQL1 OrderBy(object field, string orderType)
該方法很簡單,就是傳入一個要排序的實體類屬性和排序的方式(降序、增序),如本例:
OQL q=new OQL(emp); q.Select(emp.WorkNumber,emp.EmployeeName) .Where(emp.JobName) .OrderBy(emp.EmployeeName, "asc");
1.2.3.2,使用OQLOrder 排序對象:
public OQL1 OrderBy(OQLOrder order) { //... ... }
例如下面的使用方式,對“用戶屬性視圖”進行總成績查詢且以UID方式排序:
[示例3]--OQLOrder對象排序
UserPropertyView up = new UserPropertyView(); OQL q = new OQL(up); OQLOrder order = new OQLOrder(up); q.Select() .Where(q.Condition.AND(up.PropertyName, "=", "總成績").AND(up.PropertyValue,">",1000)) .OrderBy(order.Asc(up.UID));
二、數據修改
OQL提供了在表達式級別的數據修改、刪除數據寫入操作,數據的插入不需要使用OQL,直接調用EntityQuery<T> 對象的Inert方法即可。
2.1,更新數據
/// <summary> /// 更新實體類的某些屬性值,如果未指定條件,則使用主鍵值為條件。 /// </summary> /// <param name="fields">實體屬性列表</param> /// <returns>條件表達式</returns> public OQL1 Update(params object[] fields)
2.1.1,更新指定范圍的數據
由於方法返回的是OQL1對象,意味着OQL的更新表達式可以后續使用Where方法來限定要更新的范圍,
例如下面的例子,修改雇員“張三”的工號為“123456”:
Employee emp = new Employee(); emp.WorkNumber = "123456"; emp.EmployeeName="張三"; OQL q=OQL.From(emp) .Update(emp.WorkNumber) .Where(emp.EmployeeName) .End; EntityQuery<Employee>.Instance.ExecuteOql(q);
上面的例子只會更新一條記錄,指定相應的Where參數,OQL還可以進行復雜條件的更新或者更新多條記錄。
如果需要更復雜的更新條件,也可以在Where中使用OQLCompare對象,但由於當前版本的OQL處理機制問題,規定在Update操作的是后,OQL跟OQLCompare 不用用同樣一個實體類實例,如下面的寫法是錯誤的:
LT_Users userCmp = new LT_Users();
LT_Users userQ = new LT_Users();
//OQLCompare cmp = new OQLCompare(userCmp);//過時
OQL q = new OQL(userQ);
OQLCompare cmp = new OQLCompare(q);
OQLCompare cmpResult = cmp.Comparer(userCmp.ID, "in", "1,2,3")
& cmp.Comparer(userCmp.LastLoginIP, "=", "127.0.0.1");
q.Update(userQ.Authority).Where(cmpResult);
正確的方式應該這樣:
LT_Users user = new LT_Users();
//OQLCompare cmp = new OQLCompare(userCmp);//過時
OQL q = new OQL(user);
OQLCompare cmp = new OQLCompare(q);
OQLCompare cmpResult = cmp.Comparer(user.ID, "in", "1,2,3")
& cmp.Comparer(user.LastLoginIP, "=", "127.0.0.1");
q.Update(user.Authority).Where(cmpResult);
PS:更新單個實體對象,可以直接使用 EntityQuery而不需要使用OQL,但必須指定實體對象的主鍵值,例如上面的例子可以修改成:
Employee emp = new Employee(); emp.WorkNumber = "123456"; emp.UserID=100;//主鍵 EntityQuery<Employee>.Instance.Update(q);
2.1.2,UpdateSelf 字段自更新
用於數字型字段的值加、減一個新值得情況,比如更新會員的積分,購買物品后將會員的原有積分加上本次獲得的積分。
[示例4]--更新會員積分
AdoHelper db = MyDB.GetDBHelper(); //... 其它代碼略 SuperMarketDAL.Entitys.CustomerContactInfo ccInfo = new CustomerContactInfo(); ccInfo.CustomerID = customer.CustomerID; ccInfo.Integral = integral; OQL qc = OQL.From(ccInfo) .UpdateSelf('+', ccInfo.Integral ) .Where(ccInfo.CustomerID ) .END; EntityQuery<SuperMarketDAL.Entitys.GoodsStock>.ExecuteOql(qc, db);
采用這種方式,就不必事先進行一個查詢將積分先查詢出來,修改后再更新到數據庫,節省了一次數據查詢過程。
2.2,刪除數據
/// <summary> /// 刪除實體類,如果未指定條件,則使用主鍵值為條件。 /// </summary> /// <returns>條件表達式</returns> public OQL1 Delete()
由於方法返回的是OQL1對象,意味着OQL的刪除表達式可以后續使用Where方法來限定要刪除的范圍,
例如下面的例子,刪除雇員“張三”的記錄:
Employee emp = new Employee(); emp.EmployeeName="張三"; OQL q=OQL.From(emp) .Delete() .Where(emp.EmployeeName) .End; EntityQuery<Employee>.Instance.ExecuteOql(q);
PS:刪除單個實體對象,可以直接使用 EntityQuery而不需要使用OQL,但必須指定實體對象的主鍵值,例如上面的例子可以修改成:
Employee emp = new Employee(); emp.UserID=100;//主鍵 EntityQuery<Employee>.Instance.Delete(q);
三、統計、聚合運算
在SQL中,統計使用Count 謂詞,而其它的聚合運算還有 求平均AVG,求和SUM,求最大MAX,求最小MIN,這些OQL都支持,且用法一樣,下面看一個統計記錄數的例子:
[示例5]--獲取聯系人信息記錄數量:
public int GetContactInfoCount() { CustomerContactInfo info = new CustomerContactInfo(); OQL q = OQL.From(info) .Select() .Count(info.CustomerID, "tempField")
.END; CustomerContactInfo infoCount = EntityQuery<CustomerContactInfo>.QueryObject(q); return Convert.ToInt32(infoCount.PropertyList("tempField"));
}
這里按照客戶號進行統計,將統計結果放到SQL的列別名“tempField”中去,最后可以通過實體類的PropertyList 方法取得該值。
注:"tempField" 並不是實體類CustomerContactInfo 固有的字段,只是SQL查詢出來的一個別名字段而已,但實體類仍然可以訪問它,這就體現了PDF.NET的實體類其實是一個“數據容器”的概念。
如果不使用別名,那么隨意選取一個int ,long 類型的實體類屬性,存放結果即可,比如本例仍然使用 CustomerID :
public int GetContactInfoCount() { CustomerContactInfo info = new CustomerContactInfo(); OQL q = OQL.From(info) .Select() .Count(info.CustomerID, "") .END; CustomerContactInfo infoCount = EntityQuery<CustomerContactInfo>.QueryObject(q); return infoCount.CustomerID; }
這樣,查詢記錄總數,使用 infoCount.CustomerID 就好了,這個例子也說明了,PDF.NET SOD 實體類,就是數據的容器。
PS:類似的,將OQL的Count 方法替換成其它聚合方法,可以完成相應的SQL計算功能,OQL代碼都是類似的。
如果聚合運算同時合並分組計算,聚合函數使用的時候最好指定別名,方便選取結果,如下示例:
Table_User user = new Table_User(); OQL q = OQL.From(user) .Select().Avg(user.Height,"AvgHeight") .GroupBy(user.Sex) .END; EntityContainer ec = new EntityContainer(q); var result= ec.MapToList(() => new { //獲取聚合函數的值,用下面一行代碼的方式 AvgHeight= ec.GetItemValue<double>("AvgHeight"), Sex = user.Sex ?"男":"女" });
如上按照性別分組查詢男女的平均身高,平均身高字段指定了別名“AvgHeight”,那么在分組查詢后,用延遲指定查詢字段的方式(在MapToList里面指定,參見5.3,延遲Select指定實體類屬性 ),在方法內使用 ec.GetItemValue<double>("AvgHeight") 方法根據平均身高字段的別名獲取查詢的字段值。
注意:在MapToList方法中使用 ec.GetItemValue 的方式,需要SOD框架的PWMIS.Core.dll 文件版本是5.6.2.0607 以上。
執行上面的查詢,會生成下面的SQL語句:
SELECT [Sex] ,AVG( [Height]) AS AvgHeight FROM [Table_User] GROUP BY [Sex]
四、OQL分頁
SqlServer 2012之前並沒有直接提供分頁的關鍵詞,需要用戶自己編寫分頁SQL語句,比較麻煩,其它數據庫比如MySQL,SQLite等提供了分頁關鍵詞Limit,OQL借鑒了它的特點進行分頁,下面是例子:
[示例6]--獲取指定頁的聯系人信息
/// <summary> /// 獲取指定頁的聯系人信息 /// </summary> /// <param name="pageSize">每頁的記錄數大小</param> /// <param name="pageNumber">頁碼</param> /// <param name="allCount">總記錄數</param> /// <returns></returns> public List<CustomerContactInfo> GetContactInfoList(int pageSize, int pageNumber, int allCount) { CustomerContactInfo info = new CustomerContactInfo(); OQL q = new OQL(info); q.Select().OrderBy(info.CustomerName, "asc"); q.Limit(pageSize, pageNumber); q.PageWithAllRecordCount = allCount; return EntityQuery<CustomerContactInfo>.QueryList(q); }
注意:OQL分頁的時候除了調用Limit方法指定頁大小和頁碼之外,還必須告訴它記錄總數量,否則可能分頁不准確。
五、OQL多實體關聯查詢
在SQL中多表查詢的時候,表的關聯查詢分為內聯 Inner Join,左連接Left Join,右連接 Right Join,OQL通過對實體類進行關聯查詢實現SQL類似的操作,而且語法非常類似,如果用過Linq做表外聯結操作的朋友就知道,Linq的方式跟SQL差異很大的,這里不多說,感興趣的朋友請去查閱相關資料。
5.1,OQL實體連接方法定義:
/// <summary> /// 內連接查詢 /// </summary> /// <param name="e">要連接的實體對象</param> /// <returns>連接對象</returns> public JoinEntity Join(EntityBase e) { return Join(e, "INNER JOIN"); } /// <summary> /// 內連接查詢 /// </summary> /// <param name="e">要連接的實體對象</param> /// <returns>連接對象</returns> public JoinEntity InnerJoin(EntityBase e) { return Join(e, "INNER JOIN"); } /// <summary> /// 左連接查詢 /// </summary> /// <param name="e">要連接的實體對象</param> /// <returns>連接對象</returns> public JoinEntity LeftJoin(EntityBase e) { return Join(e, "LEFT JOIN"); } /// <summary> /// 右連接查詢 /// </summary> /// <param name="e">要連接的實體對象</param> /// <returns>連接對象</returns> public JoinEntity RightJoin(EntityBase e) { return Join(e, "RIGHT JOIN"); }
5.2,[示例7]獲取商品銷售單視圖:
該查詢需要將“商品銷售單實體”GoodsSellNote、“雇員”Employee、“客戶聯系信息”CustomerContactInfo 三個實體類進行關聯查詢得到,其中銷售員編號跟雇員工號關聯,銷售單和客戶信息的客戶編號關聯,下面給出OQL多實體類連接的實例代碼:
public IEnumerable<GoodsSellNoteVM> GetGoodsSellNote() { GoodsSellNote note = new GoodsSellNote(); Employee emp = new Employee(); CustomerContactInfo cst=new CustomerContactInfo (); OQL joinQ = OQL.From(note) .InnerJoin(emp).On(note.SalesmanID, emp.WorkNumber) .InnerJoin(cst).On(note.CustomerID, cst.CustomerID) .Select(note.NoteID, cst.CustomerName, note.ManchinesNumber, emp.EmployeeName, note.SalesType, note.SellDate) .OrderBy(note.NoteID, "desc") .END; PWMIS.DataProvider.Data.AdoHelper db = PWMIS.DataProvider.Adapter.MyDB.GetDBHelper(); EntityContainer ec = new EntityContainer(joinQ, db); ec.Execute(); //可以使用下面的方式獲得各個成員元素列表 //var noteList = ec.Map<GoodsSellNote>().ToList(); //var empList = ec.Map<Employee>().ToList(); //var cstList = ec.Map<CustomerContactInfo>().ToList(); //直接使用下面的方式獲得新的視圖對象 var result = ec.Map<GoodsSellNoteVM>(e => { e.NoteID = ec.GetItemValue<int>(0); e.CustomerName = ec.GetItemValue<string>(1); e.ManchinesNumber = ec.GetItemValue<string>(2); e.EmployeeName = ec.GetItemValue<string>(3); e.SalesType = ec.GetItemValue<string>(4); e.SellDate = ec.GetItemValue<DateTime>(5); return e; } ); return result; }
上面的例子中,先在OQL表達式的Select方法指定要查詢的實體屬性,然后在EntityContainer的Map方法內采用 GetItemValue 方法獲取要查詢的結果,查詢的時候GetItemValue 方法參數可以為Select方法指定的實體類屬性的索引順序,也可以是實體類屬性對應的字段名。
5.3,延遲Select指定實體類屬性
上面的例子我們發現在Select方法和Map方法內多次指定了字段/屬性信息,代碼量比較重復,因此在后續版本中,支持將Select方法的實體屬性選擇推遲到Map方法內,所以上面的例子可以改寫成下面這樣子:
/// <summary> /// 獲取商品銷售價格信息 /// </summary> /// <returns></returns> public IEnumerable<GoodsSaleInfoVM> GetGoodsSaleInfo() { GoodsBaseInfo bInfo = new GoodsBaseInfo(); GoodsStock stock = new GoodsStock(); /* * Select采用指定詳細實體類屬性的方式: OQL joinQ = OQL.From(bInfo) .Join(stock).On(bInfo.SerialNumber, stock.SerialNumber) .Select( bInfo.GoodsName, bInfo.Manufacturer, bInfo.SerialNumber, stock.GoodsPrice, stock.MakeOnDate, bInfo.CanUserMonth, stock.Stocks, stock.GoodsID) .OrderBy(bInfo.GoodsName, "asc") .END; */ //Select 方法不指定具體要選擇的實體類屬性,可以推遲到EntityContainer類的MapToList 方法上指定 OQL joinQ = OQL.From(bInfo) .Join(stock).On(bInfo.SerialNumber, stock.SerialNumber) .Select() .OrderBy(bInfo.SerialNumber , "asc").OrderBy(bInfo.GoodsName, "asc") .END; joinQ.Limit(3, 3); PWMIS.DataProvider.Data.AdoHelper db = PWMIS.DataProvider.Adapter.MyDB.GetDBHelper(); EntityContainer ec = new EntityContainer(joinQ, db); /* * 如果OQL的Select方法指定了詳細的實體類屬性,那么映射結果,可以采取下面的方式: var result = ec.Map<GoodsSaleInfoVM>(e => { e.GoodsName = ec.GetItemValue<string>(0); e.Manufacturer = ec.GetItemValue<string>(1); e.SerialNumber = ec.GetItemValue<string>(2); e.GoodsPrice = ec.GetItemValue<decimal>(3); e.MakeOnDate = ec.GetItemValue<DateTime>(4); e.CanUserMonth = ec.GetItemValue<int>(5); e.Stocks = ec.GetItemValue<int>(6); e.GoodsID = ec.GetItemValue<int>(7); return e; } ); */ var result = ec.MapToList<GoodsSaleInfoVM>(() => new GoodsSaleInfoVM() { GoodsName = bInfo.GoodsName, Manufacturer=bInfo.Manufacturer, SerialNumber=bInfo.SerialNumber, GoodsPrice=stock.GoodsPrice, MakeOnDate=stock.MakeOnDate, CanUserMonth=bInfo.CanUserMonth, Stocks=stock.Stocks, GoodsID=stock.GoodsID, ExpireDate = stock.MakeOnDate.AddMonths(bInfo.CanUserMonth) }); return result; }
5.4,映射匿名查詢結果
如果是局部使用多實體類的查詢結果,可以不用定義這個“ViewModel”,在 MapToList方法中,直接使用匿名類型,例如下面的例子:
OQL q=OQL.From(entity1) .Join(entity2).On(entity1.PK,entity2.FK) //.Select(entity1.Field1,entity2.Field2) //不再需要指定查詢的屬性
.Select() .End; EntityContainer ec=new EntityContainer(q); var list=ec.MapToList(()=> { return new { Property1=entity1.Field1, Property2=entity2.Field2 }; }); foreache(var item in list) { Console.WriteLine("Property1={0},Property2={1}",item.Property1,item.Property2); }
有關OQL進行多實體類關聯查詢的原理介紹的信息,請參考這篇文章《打造輕量級的實體類數據容器》
我們再來看看Linq的左、右連接,比較下哪個跟SQL最為接近:

var LeftJoin = from emp in ListOfEmployees join dept in ListOfDepartment on emp.DeptID equals dept.ID into JoinedEmpDept from dept in JoinedEmpDept.DefaultIfEmpty() select new { EmployeeName = emp.Name, DepartmentName = dept != null ? dept.Name : null };

var RightJoin = from dept in ListOfDepartment join employee in ListOfEmployees on dept.ID equals employee.DeptID into joinDeptEmp from employee in joinDeptEmp.DefaultIfEmpty() select new { EmployeeName = employee != null ? employee.Name : null, DepartmentName = dept.Name };
[后記]
PDF.NET框架的很多用戶朋友都在QQ里面跟我說出一份框架的詳細使用手冊,但框架涵蓋的內容面比較大,功能點很多,框架的每個方法背后都有實際的項目代碼例子,換句話說框架完全是從各個實際的項目中總結、提煉出來的。身為“一線碼農”,框架的每個方法使用都歷歷在目,但廣大PDF.NET的用戶朋友或許並不知道這些方法的原理是什么,怎么使用,各種使用方法有什么區別,這些問題成為了前來咨詢我框架使用的每個框架用戶的問題,而我在QQ里面往往不可能回答得很具體全面,所以也就有了概念篇之后的這篇實例篇。盡管寫這篇文章花了我1個周末,但還是感覺還有很多沒有說仔細的地方,只有大家遇到問題的時候我們一起來討論總結了!
最后,再一次感謝廣大支持PDF.NET開發框架的朋友們,感謝無私的捐助會員用戶們,是你們的支持讓我們能夠不斷進步!
--------------分解線-----------------------------------------
歡迎加入PDF.NET開源項目 開發技術團隊,做最輕最快的數據框架!