Query Object

如果不采用某种查询机制,则Repository将充斥着大量的检索方法。

    public interface ICustomerRepository {
        IEnumerable<Customer> FindAll();
        IEnumerable<Customer> FindAllVIPCustomers();
        IEnumerable<Customer> FindByOrder(Guid ID);
        IEnumerable<Customer> FindAllCustomersThatHaveOutstandingOrders();
        ……
    }

如果采用了Query Object模式,就能够构造出任意查询,然后传给Repository。

Query Object模式的主要好处是它完全的将底层的数据库查询语言抽象出来,因此将数据持久化和检索的基础设施的关注点从业务层中分离出来。但有时候,需要创建原始的数据库查询语言。这可以通过使用数据库特有的QueryTranslator(传入Query Object参数并将其转换成数据库语言)来实现。

namespace Chap7.QueryObject.Infrastructure
{
    //在这个demo中,只定义如下的几个操作,如果需要更多的操作,还需要额外的定义它们
    public enum CriteriaOperator
    {
        Equal,
        LessThanOrEqual,
        NotApplicable
    }
}
CriteriaOperator.cs

 

namespace Chap7.QueryObject.Infrastructure
{
    /// <summary>
    /// Criterion标识构成查询的过滤部分:指定一个实体属性,进行比较的值以及比较的方式。
    /// </summary>
    public class Criterion
    {
        string _propertyName;
        object _value;
        CriteriaOperator _criteriaOperator;
        public Criterion(string propertyName, object value, CriteriaOperator criteriaOperator)
        {
            _propertyName = propertyName;
            _value = value;
            _criteriaOperator = criteriaOperator;
        }

        public string PorpertyName { get { return _propertyName; } }

        public object Value { get { return _value; } }

        public CriteriaOperator criteriaOperator { get { return _criteriaOperator; } }
    }
}
Criterion.cs

 

namespace Chap7.QueryObject.Infrastructure
{
   /// <summary>
    /// OrderByClause类标识用于查询的排序属性。
   /// </summary>
    public class OrderByClause
    {
        public string PropertyName { get; set; }
        public bool Desc { get; set; }
    }
}
OrderByClause.cs

 

namespace Chap7.QueryObject.Infrastructure
{
    /// <summary>
    /// 使用另一个枚举来确定如何将Criterion对象一起评估。
    /// </summary>
    public enum QueryOperator
    {
        And, Or
    }
}
QueryOperator.cs

 

namespace Chap7.QueryObject.Infrastructure
{
    /// <summary>
    /// 有时复杂的查询非常难以创建,在这种情况下,可以使用指向数据库视图或存储过程的命名查询。这些命名查询将以枚举的形式添加。
    /// </summary>
    public enum QueryName
    {
        Dynamic = 0,
        RetrieveOrderUsingAComplexQuery = 1
    }
}
QueryName.cs

 

using System;
using System.Collections.Generic;

namespace Chap7.QueryObject.Infrastructure
{
    /// <summary>
    /// Query类将Query Object模式组合在一起。在这个类中,包含了一组Criterion对象,一个QueryName对象以及一个QueryOperator值。Query类也包含了一个IsNamedQuery方法,用来判断该查询是否已经动态生成或与
    /// Repository中某个预先建立的查询相关。
    /// </summary>
    public class Query
    {
        QueryName _name;
        IList<Criterion> _criteria;
        public Query()
            : this(QueryName.Dynamic, new List<Criterion>())
        {

        }

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

        public QueryName Name { get { return _name; } }

        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();
            }
        }

        public QueryOperator QueryOperator { get; set; }
        public OrderByClause OrderByClause { get; set; }

        public void TranslateInto(System.Data.SqlClient.SqlCommand command)
        {
            
        }
    }
}
Query.cs

 

using System;
using System.Collections.Generic;

namespace Chap7.QueryObject.Infrastructure
{
    /// <summary>
    /// NamedQueryFactory类为命名查询创建一个Query Object。Query Translator能够检查这个Query Object,以确定它是不是一个命名查询并将Criterion用作存储的数据库查询的值。
    /// </summary>
    public class NamedQueryFactory
    {
        public static Query CreateRetrieveOrdersUsingAComplexQuery(Guid CustomerId)
        {
            List<Criterion> criteria = new List<Criterion>();
            Query query = new Query(QueryName.RetrieveOrderUsingAComplexQuery, criteria);
            criteria.Add(new Criterion("CustomerId", CustomerId, CriteriaOperator.NotApplicable));
            return query;
        }
    }
}
NamedQueryFactory.cs

依托上面的这几个类,就完成了Query Object模式的实现。

注意:在Query Objec模式中并没有子查询的概念。为了提供子查询,只需要将一组Query Object对象添加到Query Object中。但是如果需要用到子查询或其他稍微复杂的查询,通常更好的做法是使用Repository或数据库中的存储查询。

 

创建一个简单的领域模型来演示如何使用所创建的Query Object实现。

using System;

namespace Chap7.QueryObject.Infrastructure
{
    public class Order
    {
        public Guid CustomerId { get; set; }

        public DateTime OrderDate { get; set; }

        public Guid Id { get; set; }
    }
}
Order.cs

 

    /// <summary>
    /// 为之前创建的Order类,创建仓储接口
    /// </summary>
    public interface IOrderRepository
    {
        IEnumerable<Order> FindBy(Query query);
    }
IOrderRepository.cs

 

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

        /// <summary>
        /// 通过Criterion和OrderByClause添加来创建动态查询。
        /// </summary>
        /// <param name="customerId"></param>
        /// <returns></returns>
        public IEnumerable<Order> FindAllCustomerOrderBy(Guid customerId)
        {
            IEnumerable<Order> customerOrders = new List<Order>();
            Query query = new Query();
            query.Add(new Criterion("CustomerId", customerId, CriteriaOperator.Equal));
            query.OrderByClause = new OrderByClause { PropertyName = "CustomerId", Desc = true };
            customerOrders = _orderRepository.FindBy(query);
            return customerOrders;
        }
        /// <summary>
        /// 通过Criterion和OrderByClause添加来创建动态查询。
        /// </summary>
        /// <param name="customerId"></param>
        /// <param name="orderDate"></param>
        /// <returns></returns>
        public IEnumerable<Order> FindAllCustomerOrdersWithInOrderDateBy(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.OrderByClause = new OrderByClause { PropertyName = "OrderDate", Desc = true };
            customerOrders = _orderRepository.FindBy(query);
            return customerOrders;
        }
        /// <summary>
        /// 命名查询,使用NamedQueryFactory来创建要传给Repository和Query Object。
        /// </summary>
        /// <param name="customerId"></param>
        /// <returns></returns>
        public IEnumerable<Order> FindAllCustomersOrdersUsingAComplexQueryWith(Guid customerId)
        {
            IEnumerable<Order> customerOrders = new List<Order>();
            Query query = NamedQueryFactory.CreateRetrieveOrdersUsingAComplexQuery(customerId);
            customerOrders = _orderRepository.FindBy(query);
            return customerOrders;
        }
    }
OrderService.cs

上面的这三个类属于实体的范畴,所以在Model下创建这些类。

 

using System;
using System.Linq;
using System.Text;
using Chap7.QueryObject.Infrastructure;
using System.Data.SqlClient;
using System.Data;

namespace Chap7.QueryObject.Repository
{
    public static class OrderQueryTranslator
    {
        static string baseSelectQuery = "select * from orders";

        /// <summary>
        /// TranslateInfo方法首先要标识Query Object是否为命名查询,如果是:则将该命令设置为一个存储过程,该存储过程的名称为查询枚举名称。然后Query和Criterion提供存储过程所需要的所有参数。
        /// 如果是动态创建的。则需要遍历所有Criterion并构建SQL语句,使用各种方法将Order实体的属性名称转化为Order列表的列名。在该Demo中,数据表列和实体属性之间是存在一对一映射的。实际中可能不是这样的,
        /// 领域模型可能随时会变。
        /// </summary>
        /// <param name="query"></param>
        /// <param name="command"></param>
        public static void TranslateInfo(this Query query, SqlCommand command)
        {
            if (query.IsNamedQuery())
            {
                command.CommandType = CommandType.StoredProcedure;
                command.CommandText = query.Name.ToString();
            }
            else
            {
                StringBuilder sqlQuery = new StringBuilder();
                sqlQuery.Append(baseSelectQuery);
                bool _isNonfirstFilterClause = false;
                if (query.Criteria.Count() > 0)
                {
                    sqlQuery.Append(" where ");
                }
                foreach (Criterion criterion in query.Criteria)
                {
                    if (!_isNonfirstFilterClause)
                        sqlQuery.Append(GetQueryOperator(query));
                    sqlQuery.Append(AddFilterClauseFrom(criterion));

                    _isNonfirstFilterClause = true;
                }
                sqlQuery.Append(GenerateOrderByClauseFrom(query.OrderByClause));
                command.CommandType = CommandType.Text;
                command.CommandText = sqlQuery.ToString();
            }
        }

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

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

        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");
            }
        }

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

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

 

    public class OrdeRepository : IOrderRepository
    {
        string _conStr;
        public OrdeRepository(string conStr)
        {
            _conStr = conStr;
        }
        public IEnumerable<Order> FindBy(Query query)
        {
            IList<Order> orders = new List<Order>();
            using (SqlConnection connection = new SqlConnection(_conStr))
            {
                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;
        }
    }
OrdeRepository.cs

 

posted @ 2020-06-14 22:00  水墨晨诗  阅读(504)  评论(0编辑  收藏  举报