ORM查詢語言(OQL)簡介--例項篇
相關文章內容索引:
ORM查詢語言(OQL)簡介--概念篇
ORM查詢語言(OQL)簡介--例項篇
ORM查詢語言(OQL)簡介--高階篇:脫胎換骨
ORM查詢語言(OQL)簡介--高階篇(續):廬山真貌
[概念回顧]
我們在前一篇《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物件使用方式已經過時,請看後續相關篇章。 執行查詢會生成如下複雜的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程式碼都是類似的。
四、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開源專案 開發技術團隊,做最輕最快的資料框架!