文档章节

Thinking In Design Pattern——Query Object模式

 木宛城主
发布于 2015/03/02 19:38
字数 1627
阅读 7
收藏 0
点赞 0
评论 0

Query Object模式

Query Object:可以在领域服务层构造查询然后传给资源库使用,并使用某种查询翻译器将对象查询(Query)翻译成底层数据库持久化框架可以理解的查询(即翻译成一条Sql 语句)。而Query Object即可以理解为表示数据库查询的对象。且可以构造任意查询,然后传给Repository。Query Object模式的主要好处是它完全将底层的数据库查询语言抽象出来。

如果没有某种查询机制,我们的持久化层可能会这样定义方法:

public interface IOrderRepository
    {
        IEnumerable<Order> FindAll(Query query);
        IEnumerable<Order> FindAllVipCustomer();
        IEnumerable<Order> FindOrderBy(Guid customerId);
        IEnumerable<Order> FindAllCustomersWithOutOrderId();
    }

很明显,可以看出持久化层很不简洁,Repository将充满大量检索方法,而我们希望我们的持久化层尽量简洁些,根据传入参数能够动态的翻译成数据库查询语言,就像下面写的这样:

public interface IOrderRepository
    {       
         IEnumerable<Order> FindBy(Query query);
         IEnumerable<Order> FindBy(Query query, int index, int count);         
    }

这个Query就是核心——一个表示数据库查询的对象,好处是显而易见的:完全将底层的数据库查询语言抽象出来,因此将数据持久化和检索的基础设施关注点从业务层中分离出来。

Query Object模式的架构

  • 添加一个枚举,CriteriaOperator:
public enum CriteriaOperator
    {
        Equal,//=
        LessThanOrEqual,// <=
        NotApplicable//// TODO: 省略了其他的操作符,可继续添加
    }
  • 接着添加Criterion类,表示构成查询的过滤器部分:指定一个实体属性(OR  Mapping)、要比较的值以及比较方式
public class Criterion
    {
        private string _propertyName;//实体属性
        private object _value;//进行比较的值
        private CriteriaOperator _criteriaOperator;//何种比较方式

        public Criterion(string propertyName, object value, CriteriaOperator criteriaOperator)
        {
            _propertyName = propertyName;
            _value = value;
            _criteriaOperator = criteriaOperator;
        }

        public string PropertyName 
        {
            get { return _propertyName; }
        }

        public object Value
        {
            get { return _value; }
        }

        public CriteriaOperator criteriaOperator
        {
            get { return _criteriaOperator; }
        }
        /// <summary>
        /// Lambda表达式树:创建一个过滤器
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="expression"></param>
        /// <param name="value"></param>
        /// <param name="criteriaOperator"></param>
        /// <returns></returns>
        public static Criterion Create<T>(Expression<Func<T, object>> expression, Object value, CriteriaOperator criteriaOperator)
        {
            string propertyName = PropertyNameHelper.ResolvePropertyName<T>(expression);
            Criterion myCriterion = new Criterion(propertyName, value, criteriaOperator);
            return myCriterion;
        }
    }
  • 为了避免在构建查询时出现令人畏惧的魔幻字符串,我们创建一个辅助方法,使用表达式参数。
public static class PropertyNameHelper
    {
        
        public static string ResolvePropertyName<T>(Expression<Func<T, object>> expression)
        {
            var expr = expression.Body as MemberExpression;
            if (expr==null)
            {
                var u = expression.Body as UnaryExpression;
                expr = u.Operand as MemberExpression;
            }
            return expr.ToString().Substring(expr.ToString().IndexOf(".")+1);
        }
    }

这样就可以像查询中添加一个新的查询条件:

query.Add(Criterion.Create<Order>(c=>c.CustomerId,customerId,CriteriaOperator.Equal));

而不是使用魔幻字符串:

query.Add(new Criterion("CustomerId", customerId, CriteriaOperator.Equal));
  • 下面要创建表示查询的排序属性:
public class OrderByClause
    {
        public string PropertyName { get; set; }
        public bool Desc { get; set; }
    }
  • 接着,创建另一个枚举,确定如何各个Criterion进行评估:
public enum QueryOperator
    {
        And,
        Or            
    }
  • 有时候的复杂非常难以创建,在这些情况下,可以使用指向数据库视图或存储过程的命名查询,添加一个QueryName来存放查询列表:
public enum QueryName
    {       
        Dynamic = 0,//动态创建
        RetrieveOrdersUsingAComplexQuery = 1//使用已经创建好了的存储过程、视图、特别是查询比较复杂时使用存储过程
    }
  • 最后,添加Query类,将Query Object模式组合在一起:
public class Query
    {
        private QueryName _name;
        private IList<Criterion> _criteria;

        public Query()
            : this(QueryName.Dynamic, new List<Criterion>())
        { }

        public Query(QueryName name, IList<Criterion> criteria)
        { 
            _name = name;
            _criteria = criteria;
        }

        public QueryName Name
        {
            get { return _name; }
        }
        /// <summary>
        /// 判断该查询是否已经动态生成或与Repository中某个预先建立的查询相关
        /// </summary>
        /// <returns></returns>
        public bool IsNamedQuery()
        {
            return Name != QueryName.Dynamic;
        }

        public IEnumerable<Criterion> Criteria
        {
            get {return _criteria ;}
        }          

        public void Add(Criterion criterion)
        {
            if (!IsNamedQuery())// 动态查询
                _criteria.Add(criterion);
            else
                throw new ApplicationException("You cannot add additional criteria to named queries");
        }

        public QueryOperator QueryOperator { get; set; }

        public OrderByClause OrderByProperty { get; set; }
    }
  • 最后创建一个工厂类,提供已存在的查询:
public static class NamedQueryFactory
    {
        public static Query CreateRetrieveOrdersUsingAComplexQuery(Guid CustomerId)
        {
            IList<Criterion> criteria = new List<Criterion>();
            Query query = new Query(QueryName.RetrieveOrdersUsingAComplexQuery, criteria);

            criteria.Add(new Criterion ("CustomerId", CustomerId, CriteriaOperator.NotApplicable));

            return query;
        }
    }

Query Object在服务层的运用

  • 建立领域模型和领域服务类:
public class Order
    {
        public Guid Id { get; set; }
        public bool HasShipped { get; set; }
        public DateTime OrderDate { get; set; }
        public Guid CustomerId { get; set; }
    }
  • 添加Repository接口:
public interface IOrderRepository
    {       
         IEnumerable<Order> FindBy(Query query);
         IEnumerable<Order> FindBy(Query query, int index, int count);         
    }
  • 建立领域服务层:
public class OrderService
    {
        private IOrderRepository _orderRepository;

        public OrderService(IOrderRepository orderRepository)
        {
            _orderRepository = orderRepository;
        }

        public IEnumerable<Order> FindAllCustomersOrdersBy(Guid customerId)
        {
            IEnumerable<Order> customerOrders = new List<Order>();

            Query query = new Query();
            //推介使用这种
            query.Add(Criterion.Create<Order>(c=>c.CustomerId,customerId,CriteriaOperator.Equal));
            //输入魔幻字符串,容易出错
            query.Add(new Criterion("CustomerId", customerId, CriteriaOperator.Equal));
            query.OrderByProperty = new OrderByClause { PropertyName = "CustomerId", Desc = true };

            customerOrders = _orderRepository.FindBy(query); 

            return customerOrders;
        }

        public IEnumerable<Order> FindAllCustomersOrdersWithInOrderDateBy(Guid customerId, DateTime orderDate)
        {
            IEnumerable<Order> customerOrders = new List<Order>();

            Query query = new Query();
            query.Add(new Criterion("CustomerId", customerId, CriteriaOperator.Equal));
            query.QueryOperator = QueryOperator.And; 
            query.Add(new Criterion("OrderDate", orderDate, CriteriaOperator.LessThanOrEqual));
            query.OrderByProperty = new OrderByClause { PropertyName = "OrderDate", Desc = true };

            customerOrders = _orderRepository.FindBy(query);

            return customerOrders;
        }

        public IEnumerable<Order> FindAllCustomersOrdersUsingAComplexQueryWith(Guid customerId)
        {
            IEnumerable<Order> customerOrders = new List<Order>();

            Query query = NamedQueryFactory.CreateRetrieveOrdersUsingAComplexQuery(customerId);

            customerOrders = _orderRepository.FindBy(query);

            return customerOrders;
        }
    }

OrderService类包含3个方法,他们将创建的查询传递给Repository。FindAllCustomersOrdersBy和FindAllCustomersOrdersWithInOrderDateBy方法通过CriterionOrderByClaus添加来创建动态查询。FindAllCustomersOrdersUsingAComplexQueryWith是命名查询,使用NamedQueryFactory来创建要传给Repository的Query Object。

  • 最后创建一个翻译器:QueryTranslator,将查询对象翻译成一条可在数据库上运行的Sql命令:
public static class OrderQueryTranslator
    {
        private static string baseSelectQuery = "SELECT * FROM Orders ";

        public static void TranslateInto(this Query query, SqlCommand command)
        {
            if (query.IsNamedQuery())
            {
                command.CommandType = CommandType.StoredProcedure;
                command.CommandText = query.Name.ToString();

                foreach (Criterion criterion in query.Criteria)
                {
                    command.Parameters.Add(new SqlParameter("@" + criterion.PropertyName, criterion.Value));
                }
            }
            else
            {
                StringBuilder sqlQuery = new StringBuilder();
                sqlQuery.Append(baseSelectQuery);

                bool _isNotfirstFilterClause = false;

                if (query.Criteria.Count() > 0)
                    sqlQuery.Append("WHERE ");   

                foreach (Criterion criterion in query.Criteria)
                {
                    if (_isNotfirstFilterClause)
                        sqlQuery.Append(GetQueryOperator(query));                                            

                    sqlQuery.Append(AddFilterClauseFrom(criterion));

                    command.Parameters.Add(new SqlParameter("@" + criterion.PropertyName, criterion.Value));

                    _isNotfirstFilterClause = true;
                }

                sqlQuery.Append(GenerateOrderByClauseFrom(query.OrderByProperty));

                command.CommandType = CommandType.Text; 
                command.CommandText = sqlQuery.ToString();
            }
        }

        private static string GenerateOrderByClauseFrom(OrderByClause orderByClause)
        {
            return String.Format("ORDER BY {0} {1}",
                FindTableColumnFor(orderByClause.PropertyName), orderByClause.Desc ? "DESC" : "ASC");          
        }

        private static string GetQueryOperator(Query query)
        {
            if (query.QueryOperator == QueryOperator.And)
                return "AND ";
            else
                return "OR ";
        }

        private static string AddFilterClauseFrom(Criterion criterion)
        {
            return string.Format("{0} {1} @{2} ", FindTableColumnFor(criterion.PropertyName), FindSQLOperatorFor(criterion.criteriaOperator), criterion.PropertyName);
        }

        private static string FindSQLOperatorFor(CriteriaOperator criteriaOperator)
        {
            switch (criteriaOperator)
            { 
                case CriteriaOperator.Equal:
                    return "=";
                case CriteriaOperator.LessThanOrEqual:
                    return "<=";
                default:
                    throw new ApplicationException("No operator defined.");
            }
        }

        private static string FindTableColumnFor(string propertyName)
        {
            switch (propertyName)
            {
                case "CustomerId":
                    return "CustomerId";
                case "OrderDate":
                    return "OrderDate";
                default:
                    throw new ApplicationException("No column defined for this property.");
            }
        }
    }
  • 建立简单仓储对象:
public class OrderRepository : IOrderRepository 
    {        
        private string _connectionString;

        public OrderRepository(string connectionString)
        {
            _connectionString = connectionString;
        }
      
        public IEnumerable<Order> FindBy(Query query)
        {
            // Move to method below with Index and count

            IList<Order> orders = new List<Order>();

            using (SqlConnection connection =
                      new SqlConnection(_connectionString))
            {
                SqlCommand command = connection.CreateCommand();
               
                query.TranslateInto(command);              
                connection.Open();

                using (SqlDataReader reader = command.ExecuteReader())
                {
                    while (reader.Read())
                    {
                        orders.Add(new Order
                        {
                            CustomerId = new Guid(reader["CustomerId"].ToString()),
                            OrderDate = DateTime.Parse(reader["OrderDate"].ToString()),
                            Id = new Guid(reader["Id"].ToString())                            
                        });
                    
                     }
                 }                
            }

                return orders;
        }

        public IEnumerable<Order> FindBy(Query query, int index, int count)
        {
            throw new NotImplementedException();            
        }       
    }

测试

 [TestFixture]
    public class SQLQueryTranslatorTests
    {
        [Test]
        public void The_Translator_Should_Produce_Valid_SQL_From_A_Query_Object()
        {
            int customerId = 9;
            string expectedSQL = "SELECT * FROM Orders WHERE CustomerId = @CustomerId ORDER BY CustomerId DESC";

            Query query = new Query();
            query.Add(new Criterion("CustomerId", customerId, CriteriaOperator.Equal));

            //query.Add(Criterion.Create<Order>(c => c.CustomerId, customerId, CriteriaOperator.Equal));
            query.OrderByProperty = new OrderByClause { PropertyName = "CustomerId", Desc = true };

            SqlCommand command = new SqlCommand();
            query.TranslateInto(command);
            Assert.AreEqual(expectedSQL, command.CommandText);
            
        }
    }

© 著作权归作者所有

共有 人打赏支持
粉丝 2
博文 222
码字总数 199010
作品 0
黄浦
软件人员推荐书目

软件人员推荐书目(一) 大师篇 一、 科学哲学和管理哲学 【1】 "程序开发心理学"(The Psychology of Computer Programming : Silver Anniversary Edition) 【2】 "系统化思维导论"(An Introd...

LsDimplex ⋅ 2016/12/06 ⋅ 0

Patterns in Large Scale JavaScript Applications, Part 2

It is clear from Part 1 of this series what a pattern is in the software development process, why we use software design patterns, and the factors for using design patterns in J......

Puneet Sharma ⋅ 2017/12/15 ⋅ 0

Thinking in Patterns #1: The pattern concept

Thinking in Patterns 1: The pattern concept What is a pattern? Initially, you can think of a pattern as an especially clever and insightfulway of solving a particular class of p......

LuXing ⋅ 2014/06/10 ⋅ 0

《Learing PHP design pattern》

a class should have only a single responsibility. one of the most important features of design patterns is reuse of the objects. One of the main functions of OOP and design patt......

hustnzj ⋅ 2016/11/01 ⋅ 0

软件工程师必读技术书籍推荐

书籍推荐——按角色划分 一、软件工程师 --Clean Code《代码整洁之道》 --Implementation Patterns《实现模式》 --Code Complete《代码大全》 --Refactoring: Improving the Design of Exist...

vakinge ⋅ 2013/12/05 ⋅ 1

历年 JOLT 获奖书籍

1991 Annotated C++ Reference Manual. by Margaret Ellis, Bjarne Stroustrup The Art of Human-Computer Interface Design. by Brenda Laurel Programming Windows, 2nd Edition by Charle......

轻风抚翼 ⋅ 2011/01/24 ⋅ 0

基于docker的设计模式

一. 面向对象和设计模式 设计模式(Design pattern)代表了最佳的实践,通常被有经验的面向对象的软件开发人员所采用。使用设计模式的目的:为了代码可重用性、让代码更容易被他人理解、保证...

skywalker ⋅ 2017/10/10 ⋅ 0

设计模式Java版,Gitbook开源电子书,含源码

Sunny在CSDN技术博客中陆续发表了100多篇与设计模式学习相关的文章,涵盖了七个面向对象设计原则和24个设计模式(23个GoF设计模式 + 简单工厂模式),为了方便大家学习,http://quanke.name ...

quanke_ ⋅ 2016/05/03 ⋅ 5

[转]Design Rules for Model-View-Presenter

原文地址:http://kjellsj.blogspot.com/2008/05/design-rules-for-model-view-presenter.html In my current project the MVP pattern is used in the supervising controller mode. The MV......

长平狐 ⋅ 2012/09/04 ⋅ 0

常见的几种设计模式

工厂模式 代替new操作的一种模式,可以屏蔽掉一些(不适合放在构造函数中的)初始化的细节(如赋值,查询数据库等),同时也易于后期代码的统一维护 单例模式 适用于在整个jvm中只需要有一个实...

skyfly ⋅ 2016/06/19 ⋅ 0

没有更多内容

加载失败,请刷新页面

加载更多

下一页

tcp/ip详解-链路层

简介 设计链路层的目的: 为IP模块发送和接收IP数据报 为ARP模块发送ARP请求和接收ARP应答 为RARP模块发送RARP请求和接收RARP应答 TCP/IP支持多种链路层协议,如以太网、令牌环往、FDDI、RS-...

loda0128 ⋅ 54分钟前 ⋅ 0

spring.net aop代码例子

https://www.cnblogs.com/haogj/archive/2011/10/12/2207916.html

whoisliang ⋅ 今天 ⋅ 0

发送短信如何限制1小时内最多发送11条短信

发送短信如何限制1小时内最多发送11条短信 场景: 发送短信属于付费业务,有时为了防止短信攻击,需要限制发送短信的频率,例如在1个小时之内最多发送11条短信. 如何实现呢? 思路有两个 截至到当...

黄威 ⋅ 昨天 ⋅ 0

mysql5.7系列修改root默认密码

操作系统为centos7 64 1、修改 /etc/my.cnf,在 [mysqld] 小节下添加一行:skip-grant-tables=1 这一行配置让 mysqld 启动时不对密码进行验证 2、重启 mysqld 服务:systemctl restart mysql...

sskill ⋅ 昨天 ⋅ 0

Intellij IDEA神器常用技巧六-Debug详解

在调试代码的时候,你的项目得debug模式启动,也就是点那个绿色的甲虫启动服务器,然后,就可以在代码里面断点调试啦。下面不要在意,这个快捷键具体是啥,因为,这个keymap是可以自己配置的...

Mkeeper ⋅ 昨天 ⋅ 0

zip压缩工具、tar打包、打包并压缩

zip 支持压缩目录 1.在/tmp/目录下创建目录(study_zip)及文件 root@yolks1 study_zip]# !treetree 11└── 2 └── 3 └── test_zip.txt2 directories, 1 file 2.yum...

蛋黄Yolks ⋅ 昨天 ⋅ 0

聊聊HystrixThreadPool

序 本文主要研究一下HystrixThreadPool HystrixThreadPool hystrix-core-1.5.12-sources.jar!/com/netflix/hystrix/HystrixThreadPool.java /** * ThreadPool used to executed {@link Hys......

go4it ⋅ 昨天 ⋅ 0

容器之上传镜像到Docker hub

Docker hub在国内可以访问,首先要创建一个账号,这个后面会用到,我是用126邮箱注册的。 1. docker login List-1 Username不能使用你注册的邮箱,要用使用注册时用的username;要输入密码 ...

汉斯-冯-拉特 ⋅ 昨天 ⋅ 0

SpringBoot简单使用ehcache

1,SpringBoot版本 2.0.3.RELEASE ①,pom.xml <parent><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-parent</artifactId><version>2.0.3.RELE......

暗中观察 ⋅ 昨天 ⋅ 0

Spring源码解析(八)——实例创建(下)

前言 来到实例创建的最后一节,前面已经将一个实例通过不同方式(工厂方法、构造器注入、默认构造器)给创建出来了,下面我们要对创建出来的实例进行一些“加工”处理。 源码解读 回顾下之前...

MarvelCode ⋅ 昨天 ⋅ 0

没有更多内容

加载失败,请刷新页面

加载更多

下一页

返回顶部
顶部