打造轻量级的实体类数据容器

里有三个关键词:轻量级实体类数据容器,还有一个潜在的关键词:通用。这几个名词之间有什么联系呢?

    一般来说,操作实体类往往伴随着一个实体类集合,而这些集合就是实体类的容器,在这里我将“容器”视作一个比集合更广泛的概念,例如Entity Framework做了一个重量级的容器ObjectContext,用于与作为对象(这些对象为 EDM 中定义的实体类型的实例)的数据进行交互。

    实体类与容器没有必然关系,例如DataSet也是一个容器,它存储并操作DataTable,而DataTable也可以看做是各个单元格数据的容器...

    但是,这些“数据容器”还是显得比较重量级,里面有太多要交互的子对象,为此我在PDF.NET(PWMIS数据开发框架)中定义了一个非常轻量级的实体数据容器,它存储数据的原则很简单,就是一个object[][],外加一个对应的字段名称数组,其它诸如表的元素据等信息都没有存储,也就是下面程序中的3个私有对象:

 

复制代码
     ///  
    
///  实体数据容器
    
///  

     public  class EntityContainer
    {
         private  string[] fieldNames;
         private List< object[]> Values;
         private  object[] currValue;

    }
复制代码

 

实体容器接收一个DataReader对象,将其中的数据读入Values 数组,下面是相应的方法代码:

 

复制代码
         ///  
        
///  执行DataReader查询,并将查询结果缓存
        
///  

        
///   数据阅读器
        
///   结果行数
         public  int Execute(IDataReader reader)
        {
            List< object[]> list =  new List< object[]>();
             using (reader)
            {
                 if (reader.Read())
                {
                     int fcount = reader.FieldCount;
                    fieldNames =  new  string[fcount];
                     object[] values =  null;

                     for ( int i =  0; i < fcount; i++)
                        fieldNames[i] = reader.GetName(i);

                     do
                    {
                        values =  new  object[fcount];
                        reader.GetValues(values);
                        list.Add(values);
                    }  while (reader.Read());

                }
            }
             this.Values = list;
             return list.Count;
        }
复制代码

程序中使用 reader.GetValues(values) 方法,它不必对每列进行数据读取,所以数据读取的效率较高。

现在数据存放进去了,如何使用呢?为了做到通用,具体每个数据的使用还是交给使用者自己去处理吧,所以采用一个委托方法来处理:

 

复制代码
        ///  
        
///  采用自定义的映射方式,将数据容器中的数据映射到指定的类中 
        
///  

        
///   结果类型
        
///   处理数据的方法
        
///  
         public IEnumerable Map(Func fun)  where TResult :  classnew()
        {
             if ( this.Values !=  null &&  this.fieldNames !=  null)
            {
                 foreach ( object[] itemValues  in  this.Values)
                {
                    TResult t =  new TResult();
                     this.currValue = itemValues;
                    fun(t);
                     yield  return t;
                }
            }
             else
            {
                 throw  new Exception( " EntityContainer 错误,调用该方法前请先调用Execute 方法。 ");
            }
        }
复制代码

下面是该方法的使用示例:

 

复制代码
            EntityContainer ec =  new EntityContainer(q, db);
            ec.Execute();
            var mapUser2= ec.Map((e) => 
            {
                e.Age = ec.GetItemValue< int>( " Age ");
                e.ID = ec.GetItemValue< int>( " ID ");
                e.Name = ec.GetItemValue< string>( " name "); // 不区分大小写
                 return e; 
            }
            ).ToList ();
复制代码

 除了可以使用 GetItemValue(string fieldName) 方法来获取迭代的当前行的某列数据外,也可以使用 GetItemValue(int fieldIndex) 方法。

另外,还提供了一个将数据映射到PDF.NET实体类的方法,下面是方法的定义:

 

复制代码
         ///  
        
///  将数据从容器中映射到实体中
        
///  

        
///  
        
///  
         public IEnumerable Map()  where T : EntityBase{
             //具体代码略
        }
复制代码

上面的测试例子中,User类是一个实体类,所以可以用下面的方式直接获取该类的实例对象集合:

 

            EntityContainer ec =  new EntityContainer(q, db);
            ec.Execute();
            var mapUser1 = ec.Map< User>().ToList ();

在Map方法中,可以映射出任意PDF.NET实体类,或者其它自定义的POCO实体类,而且没有映射次数限制。看到这里聪明的你也许要问了,上面的例子可以映射User之外的实体吗?答案是完全可以!

先看一个例子,我们假设系统中还存在一个实体类 Group,我们使用PDF.NET的OQL表达式写一个支持两个实体连接查询的语句:

 

OQL q=OQL.From(user)
         . InnerJoin(group)  //连接Group实体
         .On(user.GroupID,group.ID)
         .Select(user.ID,user.Name,group.GroupName) //选 取指定的字段

下面就可以映射出两个实体集合了:

 

            EntityContainer ec =  new EntityContainer(q, db);
            ec.Execute();  //可以省略此行调用
            var mapUser1 = ec.Map< User>().ToList ();
            var mapGroup1= ec.Map< Group>().ToList();

如果觉得这样分别使用两个实体对象集合( user和group)比较麻烦,那么再自定义一个“用户机构”类即可:

 

  class UserGroup 
{
     int ID{ get; set;}
     string Name{ get; set;}
     string GroupName{ get; set;}
}

之后,可以像下面这样来使用数据:

 

复制代码
            EntityContainer ec =  new EntityContainer(q, db);
            //ec.Execute(); //可以省略此行调用

            var mapEntity= ec.Map((e) => 
            {
                e.GroupName = ec.GetItemValue< int>( " GroupName ");
                e.ID = ec.GetItemValue< int>( " ID ");
                e.Name = ec.GetItemValue< string>( " name "); // 不区分大小写
/*

//或者使用索引的方式,但必须明确上面OQL表达式Select方法里面属性的顺序 
                e.GroupName = ec.GetItemValue(2);
                e.ID = ec.GetItemValue(0);
                e.Name = ec.GetItemValue(1);

*/
                 return e; 
            }
            ).ToList ();
复制代码

  

上面的写法没有LINQ那么完美,人家LINQ是近水楼台先得月,MS自家的苗子,可以依靠“编译器语法糖”来写出优美的LINQ程序,但我们的这个实现从原理上说非常轻巧,在众多非官方的ORM框架中,真正支持了实体类的多表连接查询!

 

有关OQL的多实体连接查询仅在PDF.NET框架V4.1以后版本支持,该功能作为框架的一项重要功能扩展,已经在商业项目中开始使用,感兴趣的朋友可以一起研究。

下面的代码是实际项目中的一段代码,我们来看看完整的调用方式:

 

复制代码
public  string GetTradeTypeID( string foundAccount, string jjdm, string type)
        {
             /* 执行下面的查询将使用如下类似的SQL:
             select distinct a.tradetype tradetypeid from wft_customerfundtrade a
                 left join wft_customerfundtradedetails b on a.tradetype =b.tradetypeid
                      where   (a.fundaccount ='1185919705'  and a.jjdm ='KF0003')
                         and ( b.tradetype='定投' or b.tradetype='基金转换的记帐')
             * 
             
*/
            WFT_CustomerFundTrade trade =  new WFT_CustomerFundTrade() { FundAccount = foundAccount, JJDM = jjdm };
            WFT_CustomerFundTradeDetails detail =  new WFT_CustomerFundTradeDetails() { TradeType = type };
            OQLCompare cmpAll =  new OQLCompare(trade, detail);

            OQL q = OQL.From(trade)
                .LeftJoin(detail).On(trade.TradeType, detail.TradeTypeId)
                .Select(trade.TradeType)
                .Where(
                        (cmpAll.Comparer(trade.FundAccount) & cmpAll.Comparer(trade.JJDM)) &
                        (cmpAll.Comparer(detail.TradeType) | cmpAll.Comparer(detail.TradeType,  " = "" 基金转换的记帐 "))
                )
                .END;

            q.Distinct =  true;

            EntityContainer container =  new EntityContainer(q);
            var result = container.Map().ToList();
             if (result.Count ==  0)
                 return  "";
             else
                 return result[ 0].TradeType;
}
复制代码

由这个例子可以看出,PDF.NET的ORM框架中的实体对象查询语言--OQL,已经可以完成很复杂的查询了,包括多实体类关联查询。

你可能感兴趣的:(ASP.NET(C#))