Query Object--查询对象模式(下)
回顾
上一篇对模式进行了介绍,并基于ADO.NET进行了实现,虽然现在ORM框架越来越流行,但是很多中小型的公司仍然是使用ADO.NET来进行数据库操作的,随着项目的需求不断增加,业务不断变化,ADO.NET的实现方式,会使原先简单的单表操作变得尤为复杂,特别是数据库表发生改变的情况下,无法像ORM框架那样,通过修改映射来达到统一的修改,需要靠程序员检查每一段相关的SQL来排查错误,这是非常麻烦的。
不管什么样的框架,使用起来不简单不易用的话,那么就没有设计的必要了。
因此今次的文章将会基于ORM框架来进行实现,大致内容如下:
- 基于表达式的实现
- 使用NHibernate查询
- 使用BeeGo查询
基于表达式的实现
因此在如今在C#领域内,如果没有Linq风格,对于编码人员来说就显得有些复杂了,因此扩展方向上肯定是要支持Linq。上一篇文章并没有实现对Linq的支持,而是留给大家去实现了,因此文章开头,就先复习一下吧。
首先从简单的调用方式开始吧,如:
query.Add<School>(s => s.Name == "一中"); 或者 query.Add<School>(s => s.Age > 20);
分析以上两个查询条件表达式,并且跟原先的Criterion来进行对比,依然是可以通过解析表达式来生成Criterion对象的,但是由于NHibernate已经支持表达式了,因此需要重构一下Query Object模式,首先删除Criterion类,并对Query代码进行修改,代码如下:
private List<Expression> m_Criterions = new List<Expression>(); public IEnumerable<Expression> Criterions { get { return m_Expressions; } } public void Add<T>(Expression<Func<T, bool>> exp) { Add(exp as Expression); } public void Add(Expression exp) { if (exp.NodeType == ExpressionType.Lambda) Add((exp as LambdaExpression).Body); else m_Criterions.Add(exp); }
接下来,需要支持and或者or了,由于and和or涉及到子查询的问题,当父查询的QueryOperator与子查询的QueryOperator不同的情况下,表达式就需要被转换成一个子查询了,因此代码改为:
public void Add(Expression exp) { if (exp.NodeType == ExpressionType.Lambda) Add((exp as LambdaExpression).Body); else if (exp.NodeType == ExpressionType.OrElse || exp.NodeType == ExpressionType.AndAlso) AddByJunctionExpression(exp); else m_Expressions.Add(exp); } private void AddByJunctionExpression(Expression exp) { var binaryExp = exp as BinaryExpression; if ((Operator == QueryOperator.And && exp.NodeType == ExpressionType.AndAlso) || (Operator == QueryOperator.Or && exp.NodeType == ExpressionType.OrElse)) { Add(binaryExp.Left); Add(binaryExp.Right); } else { Query subQuery = new Query(exp.NodeType == ExpressionType.OrElse ? QueryOperator.Or : QueryOperator.And); subQuery.Add(binaryExp.Left); subQuery.Add(binaryExp.Right); AddSubQuery(subQuery); } }
到这里基于表达式的Query Object就改造完成了,那么接下来就要根据数据层具体的环境来讲Query转化为对应的API来查询数据了。
使用NHibernate查询
先上代码,然后分析,大致代码为:
public static NHibernate.ICriterion ToNHibernateQuery<T>(Query query) { NHibernate.Junction junction; if (query.Operator == QueryOperator.And) junction = NHibernate.Expression.Conjunction(); else junction = NHibernate.Expression.Disjunction(); if (expressions.Any()) { foreach (var exp in expressions) AppendNHibernateCriterionByExpression(junction, exp); } this.AppendNHibernateCriterionBySubQueries(junction); return junction; } private static void AppendNHibernateCriterionByExpression<T>(NHibernate.Junction junction, Expression exp) { var binaryExp = exp as BinaryExpression; var expression = Expression.Lambda<Func<T, bool>>( exp, GetParameterExpressionBy(binaryExp.Left) ?? GetParameterExpressionBy(binaryExp.Right)); junction.Add(expression); } private static ParameterExpression GetParameterExpressionBy(Expression exp) { if (exp.NodeType != ExpressionType.MemberAccess) return null; var memberExp = exp as MemberExpression; return memberExp.Expression as ParameterExpression; } private static void AppendNHibernateCriterionBySubQueries<T>(NHibernate.Junction junction, IEnumerable<Query> subQueries) { if (!subQueries.Any()) return; foreach (var subQuery in subQueries) { var subCriterion = ToNHibernateQuery<T>(subQuery); junction.Add(subCriterion); } }
由于NHibernate内部已经实现了Query Object模式,因此在转换的过程当中,只需要将And和OR条件转化为对应的NHibernate类就行了,然后利用NHibernate对于表达式的支持将条件添加进去,最后使用ICriteria.List<T>()获取结果就可以了。
上一篇文章有提到对于Query Object模式对于外联的支持是比较麻烦的,但是在NHibernate的基础下去实现是比较简单的,这里就不再做过多的介绍了,有意向的朋友要对NHbernate做深入的研究,这里推荐大家看看李永京的文章学习一下,或者买基本NHibernate的书学习。
使用BeeGo查询
GO语言出来也有一段时间了,很多大公司也都在使用它,我们也不能落后。由于最近使用BeeGo框架搭建了数据服务器,Query Object模式当然也是必须的,因此借着这篇文章,也顺带讲讲BeeGo框架上,Query Object模式的实现。
虽然现在大部分的公司使用的数据库依然是关系型数据库,但是仍然挡不住NoSQL这个新兴数据库的脚步,由于最近一段时间都是是用Nodejs来进行web开发的,因此使用NOSQL数据库能更好的进行数据库交互,毕竟可以直接使用JSON数据结构,但是由于同事对于关系型数据库比较习惯因此不得不改为关系型数据库,于是就有了上面提到的GO数据服务器了。
为了使原先查询数据的方式不用改变,因此引用了MongoDb的API结构来实现Query Object模式,ajax查询代码为:
$.ajax({ url: '/school/find', data: { $or: { name: '一中', age: { $gt: 20 } } } });
以上结构等同于select * from school where name = '一中' or age > 20,按照以前几次实现Query Object的经验,这次要实现这种结构的转换相对还是比较简单的,在Go语言当中,通用类型是interface{},它相当于C#的Object,字典也有一些差别,但是并不妨碍具体的实现,这里为了简便(使用Go的结构太复杂了),因此直接使用switch来转换成begoo的接口的,大致代码如下:
func (this *Repository) translateToCriterion(field string, criterion map[string]interface{}, isAnd bool, isNot bool) *orm.Condition { cond := orm.NewCondition() var value interface{} for k, v := range criterion { key := field value = v switch k { case "$take": continue case "$skip": continue case "$sort": continue case "$like": startsWith, endsWith := strings.HasSuffix(v.(string), "%"), strings.HasPrefix(v.(string), "%") chars := []byte(v.(string)) if startsWith && endsWith { key = key + "__icontains"; value = string(chars[1:len(chars)-1]) } else if startsWith { key = key + "__istartswith" value = string(chars[:len(chars)-1]) } else { key = key + "__iendswith" value = string(chars[1:]) } break case "$gt": key = key + "__gt" break case "$lt": key = key + "__lt" break case "$in": key = key + "__in" break case "$not": if reflect.TypeOf(v).Kind() == reflect.Map { value = this.translateToCriterion(field, v.(map[string]interface{}), isAnd, true) } else { isNot = true } break case "$and": value = this.translateToCriterion("", v.(map[string]interface{}), true, isNot) break case "$or": value = this.translateToCriterion("", v.(map[string]interface{}), false, isNot) break default: if v != nil && reflect.TypeOf(v).Kind() == reflect.Map { value = this.translateToCriterion(k, v.(map[string]interface{}), isAnd, isNot) } else if v == nil { key = k + "__isnull" value = true } else { key = k } break } subCond, isCond := value.(*orm.Condition) if isAnd { if isCond { cond = cond.AndCond(subCond) } else if isNot { cond = cond.AndNot(key, value) } else { cond = cond.And(key, value) } } else { if isCond { cond = cond.OrCond(subCond) } else if isNot { cond = cond.OrNot(key, value) } else { cond = cond.Or(key, value) } } } return cond }
这样就实现了BeeGo框架下的Query Object模式了,如果对GO有兴趣的话,也可以直接使用BeeGo框架来搭建Web应用,那么前端可以直接使用前面那样的ajax来实现数据的访问,这样还是很方便的。
结尾
Query Obejct模式虽然可以简化数据的查询,虽然对于数据权限是没有作用的,但是由于使用Query Object模式,查询接口是固定的,因此可以在查询方法内添加数据权限模块,这样可以简化数据权限实现的困难。
那么这次的文章就到这里了,如有疑问和错误请大家留言给我,谢谢。