这里有三个关键词:轻量级,实体类,数据容器,还有一个潜在的关键词:通用。这几个名词之间有什么联系呢?
一般来说,操作实体类往往伴随着一个实体类集合,而这些集合就是实体类的容器,在这里我将“容器”视作一个比集合更广泛的概念,例如Entity Framework做了一个重量级的容器ObjectContext,用于与作为对象(这些对象为 EDM 中定义的实体类型的实例)的数据进行交互。
实体类与容器没有必然关系,例如DataSet也是一个容器,它存储并操作DataTable,而DataTable也可以看做是各个单元格数据的容器...
但是,这些“数据容器”还是显得比较重量级,里面有太多要交互的子对象,为此我在PDF.NET(PWMIS数据开发框架)中定义了一个非常轻量级的实体数据容器,它存储数据的原则很简单,就是一个object[][],外加一个对应的字段名称数组,其它诸如表的元素据等信息都没有存储,也就是下面程序中的3个私有对象:
///
<summary>
///
实体数据容器
///
</summary>
public
class
EntityContainer
{
private
string
[] fieldNames;
private
List
<
object
[]
>
Values;
private
object
[] currValue;
}
实体容器接收一个DataReader对象,将其中的数据读入Values 数组,下面是相应的方法代码:
///
<summary>
///
执行DataReader查询,并将查询结果缓存
///
</summary>
///
<param name="reader">
数据阅读器
</param>
///
<returns>
结果行数
</returns>
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) 方法,它不必对每列进行数据读取,所以数据读取的效率较高。
现在数据存放进去了,如何使用呢?为了做到通用,具体每个数据的使用还是交给使用者自己去处理吧,所以采用一个委托方法来处理:
///
<summary>
///
采用自定义的映射方式,将数据容器中的数据映射到指定的类中
///
</summary>
///
<typeparam name="TResult">
结果类型
</typeparam>
///
<param name="fun">
处理数据的方法
</param>
///
<returns></returns>
public
IEnumerable
<
TResult
>
Map
<
TResult
>
(Func
<
TResult
>
fun)
where
TResult :
class
,
new
()
{
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
<
User
>
((e)
=>
{
e.Age
=
ec.GetItemValue
<
int
>
(
"
Age
"
);
e.ID
=
ec.GetItemValue
<
int
>
(
"
ID
"
);
e.Name
=
ec.GetItemValue
<
string
>
(
"
name
"
);
//
不区分大小写
return
e;
}
).ToList ();
除了可以使用 GetItemValue<T>(string fieldName) 方法来获取迭代的当前行的某列数据外,也可以使用 GetItemValue<T>(int fieldIndex) 方法。
另外,还提供了一个将数据映射到PDF.NET实体类的方法,下面是方法的定义:
///
<summary>
///
将数据从容器中映射到实体中
///
</summary>
///
<typeparam name="T"></typeparam>
///
<returns></returns>
public
IEnumerable
<
T
>
Map
<
T
>
()
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
<
UserGroup
>
((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<strng>(2);
e.ID = ec.GetItemValue<int>(0);
e.Name = ec.GetItemValue<string>(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
<
WFT_CustomerFundTrade
>
().ToList();
if
(result.Count
==
0
)
return
""
;
else
return
result[
0
].TradeType;
}
由这个例子可以看出,PDF.NET的ORM框架中的实体对象查询语言--OQL,已经可以完成很复杂的查询了,包括多实体类关联查询。