• 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开源项目 开发技术团队,做最轻最快的数据框架!


     

  • 相关阅读:
    面试题32_3:之字形打印二叉树
    面试题21_2:调整数组顺序使奇数位于偶数之前(各数之间的相对位置不变)
    面试题21:调整数组顺序使奇数位于偶数前面
    面试题32_2:分行从上到下打印二叉树
    面试题32:从上到下打印二叉树
    面试题31:栈的压入、弹出序列
    面试题30:包含min函数的栈
    二分图的最大匹配
    链式前向星+次短路
    次小生成树
  • 原文地址:https://www.cnblogs.com/bluedoctor/p/2992981.html
Copyright © 2020-2023  润新知