1. 程式人生 > 其它 >ORM查詢語言(OQL)簡介--例項篇

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個顯著特點:

  1. 抽象的SQL,遮蔽了具體資料庫的差異,因此支援所有資料庫;
  2. 物件化的“SQL”,寫OQL程式碼能夠獲得IDE的智慧提示;
  3. 沒有使用.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開源專案 開發技術團隊,做最輕最快的資料框架!