让ORM框架支持多表(多实体)连接查询

bluedoctor 2011-05-24 01:49:03
加精
很多ORM框架都说,ORM不是用来做复杂的多表查询的,主要是实现起来比较困难,另外很多大牛也说用ORM实现多表查询有违OOAD的精神,即便LINQ TO SQL出来了也被大家说成这不是心目中的ORM。说归说,做归做,我们用一个简单的方案来实现ORM多表查询的问题。

相信大家写一个多表连接的SQL语句不困难,做一个可以多个实体连接的东西也不难,难就难在查询的结果如何映射的问题,人家LINQ有“投影”机制,使用select 语句将结果投射到一个新的对象即可,如果我们自己实现一个类似的功能比较复杂,毕竟LINQ使用了编译器语法糖,除非我们自己也实现一个Linq Provider,除了这条路,还有别的方式吗?
前面说了,ORM多表查询的问题包含一个连表(连实体)查询,另外一个问题就是结果映射,由此可以得到还有一个问题就是结果的存放,要有一个容器来处理这些问题。

一般来说,操作实体类往往伴随着一个实体类集合,而这些集合就是实体类的容器,在这里我将“容器”视作一个比集合更广泛的概念,例如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)
.JoinIn(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");//不区分大小写
return e;
}
).ToList ();


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

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

原文地址:
有关PDF.NET更多信息,请查看官方地址:http://www.pwmis.com/sqlmap
...全文
19679 113 打赏 收藏 举报
写回复
113 条回复
切换为时间正序
当前发帖距今超过3年,不再开放新的回复
发表回复
bluedoctor 2012-10-15
PDF.NET已经开源了,在开源版本里面能够看到OQL的实例应用。
  • 打赏
  • 举报
回复
bluedoctor 2012-01-20
再次感谢大家对PDF.NET的支持,明天就回家去了,结贴送分,给大家拜年!
  • 打赏
  • 举报
回复
bluedoctor 2011-10-17
谢谢大家支持,最新版本已经能够比较完整的支持多表实体查询了。
  • 打赏
  • 举报
回复
qingguang_wang 2011-09-22
楼主强啊!!
  • 打赏
  • 举报
回复
y315728570 2011-09-18
  • 打赏
  • 举报
回复
bluedoctor 2011-09-14
上个月我们的项目成功的实现了从SqlServer到PostgreSQL的移植

原来程序中SQL语句满天飞,后来按照要求,把简单的SQL用OQL改写,复杂的SQL写到配置文件中作为SqlMap.config文件自动生成DAL层代码。
这样程序经过“整改”后,换个providerName就切换到另外的数据库了

所以,要实现一个真正跨数据库的系统,一套好的数据框架是很重要的!
  • 打赏
  • 举报
回复
bluedoctor 2011-08-18
[Quote=引用 106 楼 feyin 的回复:]
这个多表实现起来有点难度啊,控制不好。只用单表的。多表查询,俺最多是放到视图里,把视图当成一表来使用,O(∩_∩)O哈!
[/Quote]
但如果多表查询要传参,或者多表查询数量太多,建立视图管理起来就麻烦了,特别是视图套视图,最后视图没法使用。
  • 打赏
  • 举报
回复
feyin 2011-08-17
这个多表实现起来有点难度啊,控制不好。只用单表的。多表查询,俺最多是放到视图里,把视图当成一表来使用,O(∩_∩)O哈!
  • 打赏
  • 举报
回复
xlong224 2011-08-16
不错的
linq + NH +spring.net
  • 打赏
  • 举报
回复
bluedoctor 2011-06-10
[Quote=引用 103 楼 microtry 的回复:]
引用 100 楼 bluedoctor 的回复:
1,视图不可以在定义中带参数;
2,可能会有很多的连表查询,条件都不一样,不可能都定义成视图;
3,为连表查询都去定义一个存储过程,调用和维护都很麻烦。


sqlserver不仅仅只有无参数查询(视图),还有支持参数的查询(UDF),而且还支持动态参数的查询(SP_EXECUTESQL)
[/Quote]
这仅仅是SQlSERVER支持的功能吧?
  • 打赏
  • 举报
回复
强大的农民 2011-06-09
支持支持,学习学习,收藏收藏。
  • 打赏
  • 举报
回复
缪军 2011-06-09
[Quote=引用 100 楼 bluedoctor 的回复:]
1,视图不可以在定义中带参数;
2,可能会有很多的连表查询,条件都不一样,不可能都定义成视图;
3,为连表查询都去定义一个存储过程,调用和维护都很麻烦。
[/Quote]

sqlserver不仅仅只有无参数查询(视图),还有支持参数的查询(UDF),而且还支持动态参数的查询(SP_EXECUTESQL)
  • 打赏
  • 举报
回复
bwangel 2011-06-01
多表查询不外乎可以写成视图或存储过程嘛.只要你的ORM支持这些.都不是问题呀.
  • 打赏
  • 举报
回复
bluedoctor 2011-06-01
[Quote=引用 99 楼 bwangel 的回复:]
多表查询不外乎可以写成视图或存储过程嘛.只要你的ORM支持这些.都不是问题呀.
[/Quote]
1,视图不可以在定义中带参数;
2,可能会有很多的连表查询,条件都不一样,不可能都定义成视图;
3,为连表查询都去定义一个存储过程,调用和维护都很麻烦。

所以,为简化编程,ORM应该直接支持连表查询。
  • 打赏
  • 举报
回复
liyf_liyunfeng 2011-05-31
顶一下,谢谢!
  • 打赏
  • 举报
回复
bluedoctor 2011-05-31
[Quote=引用 89 楼 fengzi2009f 的回复:]
这些 不是java的hibernate吗
[/Quote]
PDF.NET框架借鉴了Hibernate,iBatis的思想,所以看起来有点类似,欢迎支持使用!
  • 打赏
  • 举报
回复
chongjingsky 2011-05-31
看看的。
  • 打赏
  • 举报
回复
gonganruyi 2011-05-31
标记下
  • 打赏
  • 举报
回复
做牛作码 2011-05-30
学习飘过拿分
  • 打赏
  • 举报
回复
baobei7758 2011-05-30
学习学习
  • 打赏
  • 举报
回复
加载更多回复
发帖
.NET技术前瞻
加入

1.3w+

社区成员

.NET技术 .NET技术前瞻
社区管理员
  • .NET技术前瞻社区
申请成为版主
帖子事件
创建了帖子
2011-05-24 01:49
社区公告
暂无公告