• Query Object--查询对象模式(上)


    回顾

      上两篇文章主要讲解了我对于数据层的Unit Of Work(工作单元模式)的理解,其中包括了CUD的操作,那么今天就来谈谈R吧,文章包括以下几点:

    • 什么是Query Object
    • 基于SQL的实现

    什么是Query Object

      Query Object从语义就能看出它的作用,就是将查询封装成对象,并在内部转换成SQL语句或ORM框架的语法来实现查询操作。

      既然提到了这个模式,肯定是有它的好处的,但是光用语言来说明,可能不好体会,那么就从代码的角度来看看这个模式的好处吧。

      首先,项目数据层接口都会包含根据ID和根据分页获取数据的方法,大致代码如下:

    IEnumerable<T> FindAll();
    IEnumerable<T> FindById<T>(string id);
    IEnumerable<T> FindByPage<T>(int pageIndex, int pageSize);
    

      然后其他的业务可能还会提供诸如以下几种方法,代码如下:

    IEnumerable<School> FindByName(string name);
    IEnumerable<School> FindByAge(int age);
    

      光从以上几个方法就能看出查询的方式多种多样,不统一,这样子在不管在维护还是在代码的重用都会相对比较麻烦。

      但是有了Query Object模式,就可以得出一个统一的接口,代码如下:

    IEnumerable<T> FindBy(Query query);
    IEnumerable<T> FindBy(Query query, int pageIndex, int pageSize);
    

      光是接口就从原来的4-5个变成了2个,程序员在使用的时候都是同样的Query Object,而不需要内部是使用SQL还是NHibernate,又或者EF。

      研发人员只需要对相应框架实现对应的Query Object翻译类就可以轻松搞定查询任务了,那么事不宜迟,让我们一起来分析、实现吧。

    基于SQL的实现

      首先我们分析一下查询条件,如:select * from school where id = '001',条件包含一个名字、值和条件符号,那么可以通过定义一个条件符号枚举和条件类来存储,代码如下:

    public enum CriteriaOperator
    {
        None,
        Eq,
        Lt,
        Gt
    }
    
    public class Criterion
    {
        private string m_PropertyName = null;
        public string PropertyName { get { return m_PropertyName; } }
    
        private CriteriaOperator m_Operator = CriteriaOperator.None;
        public CriteriaOperator Operator
        {
            get { return m_Operator; }
            set { m_Operator = value; }
        }
    
        private object m_Value = null;
        public object Value { get { return m_Value; } }
    
        private Criterion(string propertyName, object value, CriteriaOperator criteriaOperator)
        {
            m_PropertyName = propertyName;
            m_Value = value;
            m_Operator = criteriaOperator;
        }
    
        public static Criterion Create(string propertyName, object value, CriteriaOperator criteriaOperator)
        {
            return new Criterion(propertyName, value, criteriaOperator);
        }
    }
    

      接下来需要一个更复杂的条件,如:select * from school where age > 20 and (age < 50 or name = "一中"),查询中包含了子条件,那么需要定义额外的枚举来表示and和or,其次就是查询内部要能支持子查询条件,大致代码如下:

    public class Query
    {
        private List m_Criterions = new List();
        public IEnumerable Criterions { get { return m_Criterions; } }
    
        private List m_SubQueries = new List();
        public IEnumerable SubQueries { get { return m_SubQueries; } }
    
        private QueryOperator m_Operator = QueryOperator.And;
        public QueryOperator Operator { get { return m_Operator; } }
    
        private Query() : this(QueryOperator.And) { }
    
        private Query(QueryOperator queryOperator)
        {
            m_Operator = queryOperator;
        }
    
        public void Add(Criterion criterion)
        {
            m_Criterions.Add(criterion);
        }
    
        public void AddSubQuery(Query subQuery)
        {
            m_SubQueries.Add(subQuery);
        }
    
        public static Query Create()
        {
            return new Query();
        }
    
        public static Query Create(QueryOperator queryOperator)
        {
            return new Query(queryOperator);
        }
    }
    

      我们从SQL推导出需要实现该功能的类,接下来要从功能类如果翻译成具体的SQL,回头看一下Query、Criterion,其实我们实现的是where后面的SQL语句而已,因此翻译类能完成的功能就是将Query转化为where后面的语句以及包含的参数,大致代码如下:

    private static Tuple<string, List> Translate(Query query, int parameterCount)
    {
        List tempSQLs = new List();
        List parameters = new List();
        if (query.Criterions.Any())
        {
            tempSQLs = query.Criterions.Select(c =>
            {
                string oper = "";
                switch (c.Operator)
                {
                    case CriteriaOperator.None:
                        return null;
                    case CriteriaOperator.Eq:
                        oper = "=";
                        break;
                    case CriteriaOperator.Lt:
                        oper = "<";
                        break;
                    case CriteriaOperator.Gt:
                        oper = ">";
                        break;
                }
                string parameterName = string.Format("@param{0}", parameterCount + parameters.Count);
                parameters.Add(new SqlParameter(parameterName, c.Value));
                return string.Format("{0} {1} {2}", c.PropertyName, oper, parameterName);
            }).Where(sql => !string.IsNullOrEmpty(sql)).ToList();
            parameterCount += parameters.Count;
        }
        if (query.SubQueries.Any())
        {
            foreach (var subQuery in query.SubQueries)
            {
                var subResult = Translate(subQuery, parameterCount);
                if (subResult == null)
                    continue;
    
                tempSQLs.Add(string.Format("({0})", subResult.Item1));
                parameters.AddRange(subResult.Item2);
                parameterCount += subResult.Item2.Count;
            }
        }
        if (tempSQLs.Count == 0)
            return null;
    
        return new Tuple<string, List>(
            string.Join(query.Operator == QueryOperator.And ? " and " : " or ", tempSQLs),
            parameters);
    }

    结尾

      到这里我们就将基于SQL的Query Object模式实现了,不过用起来还是稍微有点复杂的,这时候大家可以用Lambda表达式对以上的代码进行改造,改造成为如下格式:

    var query = Query.Create();
    query.Add(s => s.Age > 20 && (s.Age < 50 || s.Name == "一中"));

      具体实现的代码,我就不给出了,毕竟了解System.Linq.Expression也是有很多好处的,特别是在项目扩展方面,大家趁此机会锻炼一下自己的编码。

      看到这里相信大家也看到了,对于多表查询的情况,Query Object模式对它的支持也是比较困难的,特别是实现起来,嵌套层数太多,因此当SQL存在多表的情况下,还是得额外进行实现。

      由于时间的问题,今天就到这里了,下一篇文章我会继续Unit Of Work的方式,会基于NHibernate来实现,可能还会加上对于BeGoo的扩展,敬请期待。

      再次提醒各位,以上代码均为示例代码,虽然可以编译通过,但是最好不要直接应用到项目当中,需根据项目具体情况进行重构后使用。

      如果文章有任何错误和问题,欢迎留言,谢谢大家。

  • 相关阅读:
    Java 8简明教程
    ASCII码
    正则 取反匹配
    Eclipse 常用快捷键
    MongoDb基本操作
    Mac下eclipse的快捷键
    oracle的字符集设置与乱码
    Java7、Java8 安装卸载问题
    Oracle | PL/SQL Check约束用法详解
    浅谈数据库中的触发器
  • 原文地址:https://www.cnblogs.com/ahl5esoft/p/4030334.html
Copyright © 2020-2023  润新知