如果你使用传统的关系型数据库来保存数据,那么基本上没有任何一款关系数据库能够像NoSQL 那样去表示记录里边有一个集合字段的概念,所以你就真的需要建立一个“任务执行者”数据库表,它有两个外键分别引用到Task的主键跟Employee的主键。关于查询,这本身就生一个业务操作程序。一个业务查询可能设计5个数据库表的10个关系运算,甚至对查询中间结果数据在内存中也做一些计算操作,最后才返回调用者需要的数据实体集合,这也完全是正常的。比如说你查询某“单个”流程的所有执行情况,返回为多个“节点”层叠链接数据结构(一个节点能并发地触发多个子节点,子节点再并发地触发多个子节点),此节点信息不但是有Task数据库表记录的信息,而且在每一个节点中都直接有一个属性是Employee[]数组类型的表示任务执行者的详细信息,这种数据实体是完全可以的,这是在你进行查询之后,在内存中再产生和填充完整的数据实体。但是查询结果数据结构,在设计数据库结构时通常并不能傻傻地直接对应。如果为了对应而过度冗余,那么当你保存和修改数据时就需要花费大量时间去维护冗余的数据关联与索引的一致性。所以数据库表的结构跟数据实体定义往往是不能照搬的。所以 EF 之类的非常高级和强大的 DAL 只是一个比较低级的建模和查询工具,它只能帮你解决“数据库表与内存对象的转换”问题,而如果你的业务查询结果所需要的数据结构高于数据库的表达能力范围(例如你希望Task包含Employee,同时这也不产生冗余),那么就需要自己写查询程序,而 EF 等工具都帮不了你。
用代码来说比较简单,类似于这样的查询节点 查询工作流节点(string 工作流编号) { 节点 x = ExecuteQuery("select top 1 * from [table 工作流] where sid ='" + 工作流编号 + ' and [是顶级节点]=1"); 填充子节点(new 节点[]{x}); return x; //一个工作流只有一个顶部节点,返回这个节点 )void 填充子节点(节点[] nodes) { foreach(var x in nodes) { Employee[] u= ExecuteQueryEmployee("select e.* from Employee as e " + "inner join [执行者] as p on e.id=p.employee where p.task='"+ y.Id +"'"); x.执行人= u; 节点[] y = ExecuteQuery任务("select * from [table 任务] where parent_id='" + x.Id +"'"); if(y !=null && y.Length>0) { 填充子节点(y); } x.Children = y; } }一个业务查询,并非简单的数据库查询,你需要在内存中使用一定的算法来生成需要返回的业务实体对象。各种生成算法,才是你所关心的“查询”问题。而只会sql语句之类的低级的语法,只能完成课堂作业,而往往不能完成真正开发需求。
代码中变量写错了,另外假设你的sql数据库没有对inner join自动索引优化机制的话也需要更明确地指明“哪一个表优先”: 节点 查询工作流节点(string 工作流编号) { 节点 x = ExecuteQuery("select top 1 * from [table 工作流] where sid ='" + 工作流编号 + ' and [是顶级节点]=1"); 填充子节点(new 节点[]{x}); return x; //一个工作流只有一个顶部节点,返回这个节点 )
void 填充子节点(节点[] nodes) { foreach(var x in nodes) { Employee[] u= ExecuteQueryEmployee("select e.* from [执行者] as p inner join Employee as e " + "on p.employee=e.id where p.task='"+ x.Id +"'"); x.执行人= u; 节点[] y = ExecuteQuery任务("select * from [table 任务] where parent_id='" + x.Id +"'"); x.Children = y; if(y !=null && y.Length>0) { 填充子节点(y); } } }
如果包含该接口后,在Task中延迟加载这些Employee,该怎么处理呢?
只有当A类对象的生命期决定了B类对象的生命期时(一个对象的销毁造成另一个组对象的销毁时),才能说是“包含”关系。Task 跟 Employee 没有直接的关联关系,更不存在包含(聚合)关系。可见所谓“一个Task可以分配多个Employee”这是非常容易造成混淆的概念。一个Task,应该包含多个“任务执行者”对象,同时一个Employee也包含多个“任务执行者”对象。如果这个“任务执行者”非常简单(比如没有“职称、工资、合同条款”等等任何属性),那么在开发中可以在Task类上直接定义一个 string[] 类型的属性,用来关联到多个 Employee,而在 Employee 端不做任何设计。这就体现了他们之间没有任何直接关系,并且这个间接关系又太简单(所以合并到与之相对更接近的Task一方),就行了。
{
节点 x = ExecuteQuery("select top 1 * from [table 工作流] where sid ='" + 工作流编号 + ' and [是顶级节点]=1");
填充子节点(new 节点[]{x});
return x; //一个工作流只有一个顶部节点,返回这个节点
)void 填充子节点(节点[] nodes)
{
foreach(var x in nodes)
{
Employee[] u= ExecuteQueryEmployee("select e.* from Employee as e " +
"inner join [执行者] as p on e.id=p.employee where p.task='"+ y.Id +"'");
x.执行人= u;
节点[] y = ExecuteQuery任务("select * from [table 任务] where parent_id='" + x.Id +"'");
if(y !=null && y.Length>0)
{
填充子节点(y);
}
x.Children = y;
}
}一个业务查询,并非简单的数据库查询,你需要在内存中使用一定的算法来生成需要返回的业务实体对象。各种生成算法,才是你所关心的“查询”问题。而只会sql语句之类的低级的语法,只能完成课堂作业,而往往不能完成真正开发需求。
节点 查询工作流节点(string 工作流编号)
{
节点 x = ExecuteQuery("select top 1 * from [table 工作流] where sid ='" + 工作流编号 + ' and [是顶级节点]=1");
填充子节点(new 节点[]{x});
return x; //一个工作流只有一个顶部节点,返回这个节点
)
void 填充子节点(节点[] nodes)
{
foreach(var x in nodes)
{
Employee[] u= ExecuteQueryEmployee("select e.* from [执行者] as p inner join Employee as e " +
"on p.employee=e.id where p.task='"+ x.Id +"'");
x.执行人= u;
节点[] y = ExecuteQuery任务("select * from [table 任务] where parent_id='" + x.Id +"'");
x.Children = y;
if(y !=null && y.Length>0)
{
填充子节点(y);
}
}
}