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對象處理非常復雜的條件查詢例子,

生成OQL查詢
        /// <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開源項目 開發技術團隊,做最輕最快的數據框架!


 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM