优化OEA中的聚合SQL

简介:

   本文中的内容与前面几篇的内容、与OEA框架中的内容相关性比较大,有兴趣的朋友可以关注CodePlex中的项目:《OpenExpressApp

 

结果对比


    优化前的代码,在前面的文章中已经有所展示。这里主要看一下优化过后的代码:

最简单的聚合SQL生成:

1
2
3
4
var  sqlSimple = AggregateSQL.Instance.GenerateQuerySQL<PBS>(
     option => option.LoadChildren(pbs => pbs.PBSBQItems),
     pbsTypeId
     );

 

这样就生成了如下SQL:

 

SELECT
pbs0.pid as PBS_pid, pbs0.pbstypeid as PBS_pbstypeid, pbs0.code as PBS_code, pbs0.name as PBS_name, pbs0.fullname as PBS_fullname, pbs0.description as PBS_description, pbs0.pbssubjectid as PBS_pbssubjectid, pbs0.orderno as PBS_orderno, pbs0.id as PBS_id,
pbsbqi1.pbsid as PBSBQItem_pbsid, pbsbqi1.code as PBSBQItem_code, pbsbqi1.name as PBSBQItem_name, pbsbqi1.unit as PBSBQItem_unit, pbsbqi1.bqdbid as PBSBQItem_bqdbid, pbsbqi1.id as PBSBQItem_id
FROM PBS AS pbs0
    LEFT OUTER JOIN PBSBQItem AS pbsbqi1 ON pbsbqi1.PBSId = pbs0.Id
WHERE pbs0.PBSTypeId = '084a7db5-938a-4c7b-8d6a-612146ad87f9'
ORDER BY pbs0.Id, pbsbqi1.Id

 

该SQL用于加载聚合根对象PBSType下的所有PBS子对象,同时每个PBS的子对象PBSBQItems也都被同时查询出来。

再进一步,我们还可以直接使用聚合关系加载出对象,而不需要SQL,如:

1
2
3
4
var  pbsList = AggregateSQL.Instance.LoadEntities<PBS>(
     option => option.LoadChildren(pbs => pbs.PBSBQItems),
     pbsTypeId
     );
这样,API内部会生成聚合SQL,并进行聚合对象的加载。相对以前的模式,易用性提高了许多。这里,再给出一个目前支持的比较完整的API示例:
1
2
3
4
5
6
var  projectPBSs = AggregateSQL.Instance.LoadEntities<ProjectPBS>(loadOptions =>
     loadOptions.LoadChildren(pp => pp.ProjectPBSPropertyValues)
     .Order<ProjectPBSPropertyValue>().By(v => v.PBSProperty.OrderNo)
     .LoadFK(v => v.PBSProperty).LoadChildren(p => p.PBSPropertyOptionalValues),
     criteria.ProjectId
     );
表示:加载ProjectPBS的对象列表时:同时加载它每一个ProjectPBS的子对象列表ProjectPBSPropertyValues,并把ProjectPBSPropertyValues按照外键PBSProperty的OrderNo属性进行排序;同时,加载ProjectPBSPropertyValue.PBSProperty、加载PBSProperty.PBSPropertyOptionalValues。(其中,Order方法需要使用泛型方法指明类型是因为目前的实体列表都是非泛型的,不能进行类型推断。)

 

总体设计


 

    本次设计,主要是以提高模块的易用性为目的。

    在原有的设计中,主要有两个步骤,生成聚合SQL 和 从大表中加载聚合对象。这两个过程是比较独立的。它们之间耦合的地方有两个。首先,是为表生成什么样的列名,生成SQL时按照这种列名的约定进行生成,加载对象时则在大表中找对应列的数据。其次,它们还隐含耦合一些说明性的数据,这些数据指明了需要加载哪些子属性或者外键,什么样的加载关系,对应一个什么样的聚合SQL,也就对应加载出来的对象。

    也就是说,上述两个过程需要完整的封装起来,我们需要管理好这两个部分。而列名的生成在原来的模式中已经使用了“表名+列名”的格式进行了约定,所以现在我们只需要把“描述如何加载的描述性数据”进行管理就可以了。有了这些数据,则可以在框架内部生成聚合SQL,在框架内部按照它们进行大表到聚合对象的加载。以下,我将这些数据称为聚合对象的“加载选项”。

    同时,考虑到聚合SQL生成的复杂性及使用的2/8原则,这次的聚合SQL自动生成和加载只处理比较简单的情况:只处理简单的链式的加载。例如:A对象作为Root的子对象,它还有子对象B、C,B有子对象D、E,D有外键引用对象F、F有子对象G,那么,只处理链式的加载意味着,最多可以在加载某个Root对象的A集合的同时,带上A.B、B.C、C.D、D.F、F.G。

image

如上图所示,在加载A.B的时候,不支持加载A.C;同理,加载B.D的时候,不支持加载B.E。其实在实际运用当中,这样的局限性在使用的时候并没有太大的问题,一是较多的使用场景不需要同时加载所有的子,二是可以分两条线加载对象后,再使用对象进行数据的融合。

 

核心数据结构 - 加载选项


 

    上面已经说明了加载选项是整个聚合SQL加载的描述数据,描述如何生成SQL,描述如何加载对象。它其实也就是整个过程中的核心对象,由于时间有限(预计只有一天时间完成整个设计及代码实现),而且这个对象并不会直接暴露在外面,所以这直接使用了最简单的链表类型来表示链式的加载选项。(老实说,这个设计的扩展性并不好。)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
/// <summary>
/// 聚合加载描述器。
///
/// 目前只包含一些聚合加载选项“AggregateSQLItem”
/// </summary>
internal  class  AggregateDescriptor
{
     private  LinkedList<LoadOptionItem> _items = new  LinkedList<LoadOptionItem>();
 
     /// <summary>
     /// 所有的AggregateSQLItem
     /// </summary>
     internal  LinkedList<LoadOptionItem> Items
     {
         get
         {
             return  _items;
         }
     }
 
     /// <summary>
     /// 直接加载的实体类型
     /// </summary>
     internal  Type DirectlyQueryType
     {
         get
         {
             return  this ._items.First.Value.OwnerType;
         }
     }
 
     /// <summary>
     /// 追加一个聚合加载选项
     /// </summary>
     /// <param name="item"></param>
     internal  void  AddItem(LoadOptionItem item)
     {
         this ._items.AddLast(item);
     }
}

 

而它包含的每一个元素 LoadOptionItem 则表示一个加载项,它主要包含一个属性的元数据,用于表示要级联加载的子对象集合属性或者外键引用对象属性。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
/// <summary>
/// 生成聚合SQL的加载项中的某一项
/// </summary>
[DebuggerDisplay( "{OwnerType.Name}.{PropertyEntityType.Name}" )]
internal  class  LoadOptionItem
{
     private  Action<Entity, Entity> _fkSetter;
 
     /// <summary>
     /// 加载这个属性。
     /// </summary>
     internal  IPropertyInfo PropertyInfo { get ; private  set ; }
 
     internal  Func<Entity, object > OrderBy { get ; set ; }
 
     /// <summary>
     /// 指标这个属性是一般的实体
     /// </summary>
     internal  AggregateLoadType LoadType
     {
         get
         {
             return  this ._fkSetter == null  ? AggregateLoadType.Children : AggregateLoadType.ReferenceEntity;
         }
     }
 
     //.......
}
 
/// <summary>
/// 属性的加载类型
/// </summary>
internal  enum  AggregateLoadType
{
     /// <summary>
     /// 加载子对象集合属性
     /// </summary>
     Children,
 
     /// <summary>
     /// 加载外键引用实体。
     /// </summary>
     ReferenceEntity
}

 

对象加载


 

    按照上面的加载选项的链式设计,SQL生成其实就比较简单了:列名生成还是使用原有的方法,其它部分则只需要按照元数据进行链式生成就行了。花些时间就搞定了。

    框架中对象的聚合加载的实现,和手写时一样,也是基于原有的ReadFromTable方法的,也不复杂,贴下代码,不再一一描述:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
/// <summary>
/// 聚合实体的加载器
/// </summary>
internal  class  AggregateEntityLoader
{
     private  AggregateDescriptor _aggregateInfo;
 
     internal  AggregateEntityLoader(AggregateDescriptor aggregate)
     {
         if  (aggregate == null ) throw  new  ArgumentNullException( "aggregate" );
         if  (aggregate.Items.Count < 1) throw  new  InvalidOperationException( "aggregate.Items.Count < 2 must be false." );
 
         this ._aggregateInfo = aggregate;
     }
 
     /// <summary>
     /// 通过聚合SQL加载整个聚合对象列表。
     /// </summary>
     /// <param name="sql"></param>
     /// <returns></returns>
     internal  EntityList Query( string  sql)
     {
         IGTable dataTable = null ;
 
         IDbFactory dbFactory = this ._aggregateInfo.Items.First.Value.OwnerRepository;
         using  ( var  db = dbFactory.CreateDb())
         {
             dataTable = db.QueryTable(sql);
         }
 
         //使用dataTable中的数据 和 AggregateDescriptor 中的描述信息,读取整个聚合列表。
         var  list = this .ReadFromTable(dataTable, this ._aggregateInfo.Items.First);
 
         return  list;
     }
 
     /// <summary>
     /// 根据 optionNode 中的描述信息,读取 table 中的数据组装为对象列表并返回。
     ///
     /// 如果 optionNode 中指定要加载更多的子/引用对象,则会递归调用自己实现聚合加载。
     /// </summary>
     /// <param name="table"></param>
     /// <param name="optionNode"></param>
     /// <returns></returns>
     private  EntityList ReadFromTable(IGTable table, LinkedListNode<LoadOptionItem> optionNode)
     {
         var  option = optionNode.Value;
         var  newList = option.OwnerRepository.NewList();
         newList.ReadFromTable(table, (row, subTable) =>
         {
             var  entity = option.OwnerRepository.Convert(row);
 
             EntityList listResult = null ;
 
             //是否还有后继需要加载的对象?如果是,则递归调用自己进行子对象的加载。
             var  nextNode = optionNode.Next;
             if  (nextNode != null )
             {
                 listResult = this .ReadFromTable(subTable, nextNode);
             }
             else
             {
                 listResult = this .ReadFromTable(subTable, option.PropertyEntityRepository);
             }
 
             //是否需要排序?
             if  (listResult.Count > 1 && option.OrderBy != null )
             {
                 listResult = option.PropertyEntityRepository.NewListOrderBy(listResult, option.OrderBy);
             }
 
             //当前对象是加载类型的子对象还是引用的外键
             if  (option.LoadType == AggregateLoadType.Children)
             {
                 listResult.SetParentEntity(entity);
                 entity.LoadCSLAProperty(option.CslaPropertyInfo, listResult);
             }
             else
             {
                 if  (listResult.Count > 0)
                 {
                     option.SetReferenceEntity(entity, listResult[0]);
                 }
             }
 
             return  entity;
         });
 
         return  newList;
     }
 
     /// <summary>
     /// 简单地从table中加载指定的实体列表。
     /// </summary>
     /// <param name="table"></param>
     /// <param name="repository"></param>
     /// <returns></returns>
     private  EntityList ReadFromTable(IGTable table, EntityRepository repository)
     {
         var  newList = repository.NewList();
 
         newList.ReadFromTable(table, (row, subTable) => repository.Convert(row));
 
         return  newList;
     }
}

 

美化的API


 

    基于以上的基础,我们需要一个流畅的API来定义加载选项。这一点对于一个框架设计人员来说,往往很重要,只有流畅、易用的API才能对得起你的客户:框架使用者。以下我只把给出几个为达到流畅API而特别设计的类。其中,用到了《小技巧 - 简化你的泛型API》中提到的设计原则。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
/// <summary>
/// 存储了加载选项项
/// </summary>
public  abstract  class  LoadOptionSelector
{
     internal  LoadOptionSelector(AggregateDescriptor descriptor)
     {
         _descriptor = descriptor;
     }
 
     private  AggregateDescriptor _descriptor;
 
     internal  AggregateDescriptor InnerDescriptor
     {
         get
         {
             return  _descriptor;
         }
     }
}
 
/// <summary>
/// 属性选择器
/// </summary>
/// <typeparam name="TEntity"></typeparam>
public  class  PropertySelector<TEntity> : LoadOptionSelector
     where  TEntity : Entity
{
     internal  PropertySelector(AggregateDescriptor descriptor) : base (descriptor) { }
 
     /// <summary>
     /// 需要同时加载外键
     /// </summary>
     /// <typeparam name="TFKEntity"></typeparam>
     /// <param name="fkEntityExp">
     /// 需要加载的外键实体属性表达式
     /// </param>
     /// <returns></returns>
     public  PropertySelector<TFKEntity> LoadFK<TFKEntity>(Expression<Func<TEntity, TFKEntity>> fkEntityExp)
         where  TFKEntity : Entity
     {
         var  entityPropertyName = GetPropertyName(fkEntityExp);
         var  propertyName = entityPropertyName + "Id" ;
 
         IEntityInfo entityInfo = ApplicationModel.GetBusinessObjectInfo( typeof (TEntity));
         var  propertyInfo = entityInfo.BOPropertyInfos.FirstOrDefault(p => p.Name == propertyName);
 
         //构造一个临时代理方法,实现:TEntity.EntityProperty = TFKEntity
         var  pE = System.Linq.Expressions.Expression.Parameter( typeof (TEntity), "e" );
         var  pEFK = System.Linq.Expressions.Expression.Parameter( typeof (TFKEntity), "efk" );
         var  propertyExp = System.Linq.Expressions.Expression.Property(pE, entityPropertyName);
         var  body = System.Linq.Expressions.Expression.Assign(propertyExp, pEFK);
         var  result = System.Linq.Expressions.Expression.Lambda<Action<TEntity, TFKEntity>>(body, pE, pEFK);
         var  fkSetter = result.Compile();
 
         var  option = new  LoadOptionItem(propertyInfo, (e, eFK) => fkSetter(e as  TEntity, eFK as  TFKEntity));
 
         //避免循环
         if  ( this .InnerDescriptor.Items.Any(i => i.OwnerType == option.PropertyEntityType))
         {
             throw  new  InvalidOperationException( "有循环的实体设置。" );
         }
 
         this .InnerDescriptor.AddItem(option);
 
         return  new  PropertySelector<TFKEntity>( this .InnerDescriptor);
     }
 
     /// <summary>
     /// 需要同时加载孩子
     /// </summary>
     /// <typeparam name="TChildren"></typeparam>
     /// <param name="propExp">
     /// 需要加载的孩子属性表达式
     /// </param>
     /// <returns></returns>
     public  ChildrenSelector LoadChildren<TChildren>(Expression<Func<TEntity, TChildren>> propExp)
         where  TChildren : EntityList
     {
         var  propertyName = GetPropertyName(propExp);
         IEntityInfo entityInfo = ApplicationModel.GetBusinessObjectInfo( typeof (TEntity));
         var  propertyInfo = entityInfo.BOsPropertyInfos.FirstOrDefault(p => p.Name == propertyName);
 
         this .InnerDescriptor.AddItem( new  LoadOptionItem(propertyInfo));
 
         return  new  ChildrenSelector( this .InnerDescriptor);
     }
 
     private  static  string  GetPropertyName<TProperty>(Expression<Func<TEntity, TProperty>> propExp)
     {
         var  member = propExp.Body as  MemberExpression;
         var  property = member.Member as  PropertyInfo;
         if  (property == null ) throw  new  ArgumentNullException( "property" );
         var  propertyName = property.Name;
 
         return  propertyName;
     }
}
 
/// <summary>
/// 孩子选择器
/// </summary>
/// <typeparam name="TEntity"></typeparam>
public  class  ChildrenSelector : LoadOptionSelector
{
     internal  ChildrenSelector(AggregateDescriptor descriptor) : base (descriptor) { }
 
     public  OrderByLoadOption<TEntity> Order<TEntity>()
         where  TEntity : Entity
     {
         return  new  OrderByLoadOption<TEntity>( this .InnerDescriptor);
     }
 
     /// <summary>
     /// 把孩子集合转换为实体对象,需要继续加载它的子对象
     /// </summary>
     /// <typeparam name="TEntity"></typeparam>
     /// <returns></returns>
     public  PropertySelector<TEntity> Continue<TEntity>()
         where  TEntity : Entity
     {
         return  new  PropertySelector<TEntity>( this .InnerDescriptor);
     }
}
 
public  class  OrderByLoadOption<TEntity> : LoadOptionSelector
     where  TEntity : Entity
{
     internal  OrderByLoadOption(AggregateDescriptor descriptor) : base (descriptor) { }
 
     public  PropertySelector<TEntity> By<TKey>(Func<TEntity, TKey> keySelector)
     {
         this .InnerDescriptor.Items.Last.Value
             .OrderBy = e => keySelector(e as  TEntity);
 
         return  new  PropertySelector<TEntity>( this .InnerDescriptor);
     }
}

 

小结


 

    本次重构由于只处理“链式的加载选项”,所以实现并不复杂。同时,由于把Repository都临时存放在了LoadOptionItem中,使得Repository的获取不再浪费,印证了:“一个重构后良好结构的程序,性能很有可能会有所提升。”

目录
相关文章
|
1月前
|
SQL 存储 关系型数据库
如何巧用索引优化SQL语句性能?
本文从索引角度探讨了如何优化MySQL中的SQL语句性能。首先介绍了如何通过查看执行时间和执行计划定位慢SQL,并详细解析了EXPLAIN命令的各个字段含义。接着讲解了索引优化的关键点,包括聚簇索引、索引覆盖、联合索引及最左前缀原则等。最后,通过具体示例展示了索引如何提升查询速度,并提供了三层B+树的存储容量计算方法。通过这些技巧,可以帮助开发者有效提升数据库查询效率。
70 2
|
6天前
|
SQL 存储 缓存
如何优化SQL查询性能?
【10月更文挑战第28天】如何优化SQL查询性能?
33 10
|
5天前
|
SQL 存储 缓存
SQL Server 数据太多如何优化
11种优化方案供你参考,优化 SQL Server 数据库性能得从多个方面着手,包括硬件配置、数据库结构、查询优化、索引管理、分区分表、并行处理等。通过合理的索引、查询优化、数据分区等技术,可以在数据量增大时保持较好的性能。同时,定期进行数据库维护和清理,保证数据库高效运行。
|
19天前
|
SQL 资源调度 分布式计算
如何让SQL跑快一点?(优化指南)
这篇文章主要探讨了如何在阿里云MaxCompute(原ODPS)平台上对SQL任务进行优化,特别是针对大数据处理和分析场景下的性能优化。
|
27天前
|
SQL 监控 数据库
慢SQL对数据库写入性能的影响及优化技巧
在数据库管理系统中,慢SQL(即执行缓慢的SQL语句)不仅会影响查询性能,还可能对数据库的写入性能产生显著的不利影响
|
30天前
|
SQL 关系型数据库 PostgreSQL
遇到SQL 子查询性能很差?其实可以这样优化
遇到SQL 子查询性能很差?其实可以这样优化
75 2
|
27天前
|
SQL 存储 数据库
慢SQL对数据库写入性能的影响及优化技巧
在数据库管理系统中,慢SQL(即执行缓慢的SQL语句)不仅会影响查询性能,还可能对数据库的写入性能产生显著的不利影响
|
1月前
|
SQL 数据处理 数据库
SQL语句优化与查询结果优化:提升数据库性能的实战技巧
在数据库管理和应用中,SQL语句的编写和查询结果的优化是提升数据库性能的关键环节
|
1月前
|
SQL 存储 数据库
慢SQL对数据库写入性能的影响及优化策略
在数据库管理系统中,慢SQL(即执行缓慢的SQL语句)不仅会影响查询性能,还可能对数据库的写入性能产生不利影响
|
3月前
|
Java XML Maven
跨越时代的飞跃:Struts 2 升级秘籍——从旧版本无缝迁移到最新版,焕发应用新生!
【8月更文挑战第31天】随着软件技术的发展,Struts 2 框架也在不断更新。本文通过具体案例指导开发者如何从旧版平滑升级到 Struts 2.6.x。首先更新 `pom.xml` 中的依赖版本,并执行 `mvn clean install`。接着检查 `struts.xml` 配置,确保符合新版本要求,调整包扫描器等设置。审查 Action 类及其注解,检查配置文件中的弃用项及插件。更新自定义拦截器实现,并验证日志配置。最后,通过一系列测试确保升级后的系统正常运行。通过这些步骤,可以顺利完成 Struts 2 的版本升级,提升应用的安全性和性能。
187 0