企业应用架构模式——QueryObject模式

场景

比如我们有个Employee实体,属性有 EmployeeID(雇员id)、NationalIDNumber(雇员标志号,如身份证)、ManagerID(雇员上司id)、Title(职位)、MaritalStatus(婚姻状态)、Gender(性别)、HireDate(雇佣日期)、SalariedFlag(工作性质,计时或月薪)。我们写数据库层时经常会遇到这样的需求:

查询标志号为"001”的雇员

public class EmployeeRepository : IRepository<Employee>
   {
      public Employee Find(object id)
      {}
   }

查询计时或月薪的雇员

public class EmployeeRepository : IRepository<Employee>
   {
      public IEnumerable<Employee> FindSalariedEmployee(SalariedFlag salariedFlag)
      {}
   }

可以看出只要我们有通过对象属性来查询对象,就要在相应的数据层增加对应的Findxxx方法,不过这只是单一的查询,如果存在组合条件的查询,Employee仓储中会充斥大量的检索方法。

QueryObject模式

QueryObject(查询对象)是一个解释器,也就是说,他是由多个对象组成的结构,该结构可以自身转化为一个sql查询。可以通过引用类的和属性来创建对应数据库表和列的查询。

查询对象的好处

1、完全将底层数据库的查询语言抽象出来,因此将数据库持久化和检索的基础设施关注点从业务层中分离出来。

2、对于多个数据库,通过设计查询对象,根据运行的数据库来产生不同的sql,屏蔽了各种不同sql语法中的差别。

3、消除对数据库的查询冗余,如果在先前的会话中运行过相同的查询,可以使用查询对象从标志映射中选取对象。

QueryObject 模型 类图

实现QueryObject 模式

首先创建一个枚举 CriteriaOperator 用来处理查询条件的映射

   1:  public enum CriteriaOperator
   2:  {
   3:      Equal,// =
   4:      LessThanOrEqual,// <=
   5:      NotApplicable, // ≠
   6:      LessThan, // <
   7:      GreaterThan, // >
   8:      GreaterThanOrEqual, // >=
   9:      Like // %
  10:  }

创建查询过滤器的类Criterion

   1:  /// <summary>
   2:  /// 查询过滤器
   3:  /// </summary>
   4:  public class Criterion
   5:  {
   6:      private string _propertyName;//实体属性
   7:      private object _value;//进行比较的值
   8:      private CriteriaOperator _criteriaOperator;//比较操作符 
   9:   
  10:      public Criterion(string propertyName, object value, CriteriaOperator criteriaOperator)
  11:      {
  12:          _propertyName = propertyName;
  13:          _value = value;
  14:          _criteriaOperator = criteriaOperator;
  15:      }
  16:   
  17:      public string PropertyName
  18:      {
  19:          get { return _propertyName; }
  20:      }
  21:   
  22:      public object Value 
  23:      {
  24:          get { return _value; }
  25:      }
  26:      public CriteriaOperator CriteriaOperator
  27:      {
  28:          get { return _criteriaOperator; }
  29:      }
  30:   
  31:  }

创建一个用于表示排序属性的类OrderByClause

   1:  public class OrderByClause
   2:  {
   3:      public string PropertyName { get; set; }//属性名对应数据库表的列名
   4:   
   5:      public bool Desc { get; set; }//desc or asc
   6:  }

创建另一个枚举QueryOperator,用来表示条件的连字符 and 或者 or

   1:  public enum QueryOperator
   2:  {
   3:      And,
   4:      Or
   5:  }

有时候复杂的查询对象难以构建,我们会通过存储过程或视图来处理此种情况,需要构建一个枚举QueryName用来指示是存储过程(视图)还是构建动态的sql语句

   1:  public enum QueryName
   2:  {
   3:      Dynamic = 0, //动态创建
   4:      RetrieveOrdersUsingAComplexQuery = 1//使用已经创建好了的存储过程、视图、特别是查询比较复杂时使用存储过程
   5:  }

最后创建Query类,用来组合查询条件(Criterion)、查询条件连字符(QueryOperator),排序(OrderByClause)

   1:  public class Query
   2:  {
   3:      private QueryName _name;
   4:   
   5:      private IList<Criterion> _criteria;//查询条件
   6:   
   7:      public Query():this(QueryName.Dynamic,new List<Criterion>())
   8:      { }
   9:      public Query(QueryName name, IList<Criterion> criteria)
  10:      {
  11:          _name = name;
  12:          _criteria = criteria;
  13:      }
  14:   
  15:      public QueryName Name
  16:      {
  17:          get { return _name; }
  18:      }
  19:   
  20:      /// <summary>
  21:      /// 判断是存储过程还是动态sql
  22:      /// </summary>
  23:      /// <returns></returns>
  24:      public bool IsNamedQuery()
  25:      {
  26:          return Name != QueryName.Dynamic;
  27:      }
  28:   
  29:      public IEnumerable<Criterion> Criteria
  30:      {
  31:          get { return _criteria; }
  32:      }
  33:   
  34:      public void Add(Criterion criterion)
  35:      {
  36:          if (!IsNamedQuery())
  37:          {
  38:              _criteria.Add(criterion);
  39:          }
  40:          else
  41:          {
  42:              throw new ApplicationException("You cannot add additional criteria to named queries");
  43:          }
  44:      }
  45:      //查询条件连字符
  46:      public QueryOperator QueryOperator
  47:      {
  48:          get;
  49:          set;
  50:      }
  51:      //排序列
  52:      public OrderByClause OrderByProperty
  53:      {
  54:          get;
  55:          set;
  56:      }
  57:   
  58:  }

使用QueryObject模式

仓储层(Repository)接口

   1:  public interface IRepository<T>
   2:  {
   3:      int Add(T entity);
   4:      int Save(T entity);
   5:      void Delete(T entity);
   6:      T Find(object id);
   7:      T Find(Query query);
   8:      IEnumerable<T> FindBy(Query query);
   9:      IEnumerable<T> FindBy(Query query, int pageSize, int pageCount);
  10:  }

创建一个翻译器,将查询对象翻译成sql语句

   1:  public static class AdmissionTicketOrderQueryTranslator
   2:  {
   3:      private static readonly string baseSelectQuery = "SELECT * FROM AdmissionTicketOrder";
   4:   
   5:      public static void TranslateInfo(this Query query, SqlCommand cmd)
   6:          {
   7:              if (query.IsNamedQuery())
   8:              {
   9:                  cmd.CommandType = CommandType.StoredProcedure;
  10:                  cmd.CommandText = query.Name.ToString();
  11:                  foreach (Criterion criteria in query.Criteria)
  12:                  {
  13:                      cmd.Parameters.Add(new SqlParameter("@" + criteria.PropertyName, criteria.Value));
  14:                  }
  15:              }
  16:              else
  17:              {
  18:                  StringBuilder sqlQuery = new StringBuilder();
  19:                  sqlQuery.Append(baseSelectQuery);
  20:                  bool _isNotfirstFilterClause = false;
  21:   
  22:                  if (query.Criteria.Count()>0)
  23:                  {
  24:                      sqlQuery.Append(" WHERE ");
  25:                  }
  26:   
  27:                  foreach (Criterion criterion in query.Criteria)
  28:                  {
  29:                      if (_isNotfirstFilterClause)
  30:                      {
  31:                          sqlQuery.Append(GetQueryOperator(query));
  32:                      }
  33:                      sqlQuery.Append(AddFilterClauseFrom(criterion));
  34:                      cmd.Parameters.Add(new SqlParameter("@" + criterion.PropertyName, criterion.Value));
  35:                      _isNotfirstFilterClause = true;
  36:                  }
  37:                  sqlQuery.Append(GenerateOrderByClauseFrom(query.OrderByProperty));
  38:                  cmd.CommandType = CommandType.Text;
  39:                  cmd.CommandText = sqlQuery.ToString();
  40:              }
  41:          }
  42:   
  43:      private static string GenerateOrderByClauseFrom(OrderByClause orderByClause)
  44:          {
  45:              return String.Format(" ORDER BY {0} {1}",
  46:                  FindTableColumnFor(orderByClause.PropertyName), orderByClause.Desc ? "DESC" : "ASC");
  47:          }
  48:   
  49:      /// <summary>
  50:          /// 
  51:          /// </summary>
  52:          /// <param name="query"></param>
  53:          /// <returns></returns>
  54:      private static string GetQueryOperator(Query query)
  55:          {
  56:              if (query.QueryOperator==QueryOperator.And)
  57:              {
  58:                  return " AND ";
  59:              }
  60:              else
  61:              {
  62:                  return " OR ";
  63:              }
  64:          }
  65:   
  66:      private static string AddFilterClauseFrom(Criterion criterion)
  67:          {
  68:              return string.Format("{0} {1} @{2} ", FindTableColumnFor(criterion.PropertyName), FindSQLOperatorFor(criterion.CriteriaOperator), criterion.PropertyName);
  69:          }
  70:   
  71:      private static string FindSQLOperatorFor(CriteriaOperator criteriaOperator)
  72:          {
  73:              switch (criteriaOperator)
  74:              {
  75:                  case CriteriaOperator.Equal:
  76:                      return "=";
  77:                  case CriteriaOperator.LessThanOrEqual:
  78:                      return "<=";
  79:                  case CriteriaOperator.NotApplicable:
  80:                      return "<>";
  81:                  case CriteriaOperator.LessThan:
  82:                      return "<";
  83:                  case CriteriaOperator.GreaterThan:
  84:                      return ">";
  85:                  case CriteriaOperator.GreaterThanOrEqual:
  86:                      return ">=";
  87:                  default:
  88:                      throw new ApplicationException("No operator defined.");
  89:              }
  90:          }
  91:   
  92:      private static string FindTableColumnFor(string propertyName)
  93:          {
  94:              switch (propertyName)
  95:              {
  96:                  case "ID":
  97:                      return "ID";
  98:                  case "OrderID":
  99:                      return "OrderID";
 100:                  case "TicketId":
 101:                      return "TicketId";
 102:                  case "OrderUserName":
 103:                      return "OrderUserName";
 104:                  case "OrderUserSex":
 105:                      return "OrderUserSex";
 106:                  case "OrderUserPhone":
 107:                      return "OrderUserPhone";
 108:                  case "ProviderContactName":
 109:                      return "ProviderContactName";
 110:                  case "TicetUseDateTime":
 111:                      return "TicetUseDateTime";
 112:                  case "SalePrice":
 113:                      return "SalePrice";
 114:                  case "BuyPrice":
 115:                      return "BuyPrice";
 116:                  case "BuyAmount":
 117:                      return "BuyAmount";
 118:                  case "TicketType":
 119:                      return "TicketType";
 120:                  default:
 121:                      throw new ApplicationException("No column defined for this property.");
 122:              }
 123:          }
 124:   
 125:  }
 126:   

仓储实现层

   1:  public class AdmissionTicketOrderRepository : IRepository<AdmissionTicketOrder>
   2:  {
   3:      private SqlHelper _sqlHelper;
   4:   
   5:      public AdmissionTicketOrderRepository(SqlHelper sqlHelper)
   6:      {
   7:          _sqlHelper = sqlHelper;
   8:      }
   9:   
  10:      private static readonly string insertOrderExtendSql = "sp_InsertOrUpdateAdmissionTicketOrder";
  11:      public int Add(AdmissionTicketOrder entity)
  12:          {
  13:              using (var cmd = _sqlHelper.PrepareStoredSqlCommand(insertOrderExtendSql))
  14:              {
  15:                  cmd.AddParam("@ID", entity.ID);
  16:                  cmd.AddParam("@OrderID", entity.OrderID);
  17:                  cmd.AddParam("@TicketId", entity.TicketId);
  18:                  cmd.AddParam("@OrderUserName", entity.OrderUserName);
  19:                  cmd.AddParam("@OrderUserSex", entity.OrderUserSex);
  20:                  cmd.AddParam("@OrderUserPhone", entity.OrderUserPhone);
  21:                  cmd.AddParam("@ProviderContactName", entity.ProviderContactName);
  22:                  cmd.AddParam("@ProviderContactPhone", entity.ProviderContactPhone);
  23:                  cmd.AddParam("@TicetUseDateTime", entity.TicetUseDateTime);
  24:                  cmd.AddParam("@IsNeed", entity.IsNeed);
  25:                  cmd.AddParam("@BillAddress", entity.BillAddress);
  26:                  cmd.AddParam("@BuyPrice", entity.BuyPrice);
  27:                  cmd.AddParam("@SalePrice", entity.SalePrice);
  28:                  cmd.AddParam("@BuyAmount", entity.BuyAmount);
  29:                  cmd.AddParam("@GetTicketAddress", entity.GetTicketAddress);
  30:                  cmd.AddParam("@TicketType", entity.TicketType);
  31:                  var parm = cmd.AddReturnValueParameter("ReturnValue");
  32:                  cmd.ExecuteNonQuery();
  33:                  return (int)parm.Value;
  34:              }
  35:          }
  36:   
  37:      public int Save(AdmissionTicketOrder entity)
  38:          {
  39:              return Add(entity);
  40:          }
  41:   
  42:      private static readonly string deleteOrder = "DELETE AdmissionTicketOrder WHERE ID=@Id";
  43:      public void Delete(AdmissionTicketOrder entity)
  44:          {
  45:              using (var cmd = _sqlHelper.PrepareTextSqlCommand(deleteOrder))
  46:              {
  47:                  cmd.AddParam("@Id", entity.ID);
  48:                  cmd.ExecuteNonQuery();
  49:              }
  50:          }
  51:   
  52:      private static readonly string selectOrder = "SELECT * FROM AdmissionTicketOrder WHERE ID=@Id";
  53:      public AdmissionTicketOrder Find(object id)
  54:          {
  55:              using (var cmd = _sqlHelper.PrepareTextSqlCommand(selectOrder))
  56:              {
  57:                  cmd.AddParam("@Id", id);
  58:                  using (var reader = cmd.ExecuteReader())
  59:                  {
  60:                      if (reader.Read())
  61:                      {
  62:                          return (new AdmissionTicketOrder
  63:                          {
  64:                              Id = reader.GetValue<int>("Id"),
  65:                              OrderID = reader.GetValue<int>("OrderID"),
  66:                              TicketId=reader.GetValue<int>("TicketId"),
  67:                              OrderUserName=reader.GetValue<string>("OrderUserName"),
  68:                              OrderUserSex=reader.GetValue<int>("OrderUserSex"),
  69:                              OrderUserPhone = reader.GetValue<string>("OrderUserPhone"),
  70:                              ProviderContactName=reader.GetValue<string>("ProviderContactName"),
  71:                              ProviderContactPhone=reader.GetValue<string>("ProviderContactPhone"),
  72:                              TicetUseDateTime=reader.GetValue<DateTime>("TicetUseDateTime"),
  73:                              IsNeed=reader.GetValue<bool>("IsNeed"),
  74:                              BillAddress=reader.GetValue<string>("BillAddress"),
  75:                              SalePrice = reader.GetValue<decimal>("SalePrice"),
  76:                              BuyPrice = reader.GetValue<decimal>("BuyPrice"),
  77:                              BuyAmount=reader.GetValue<decimal>("BuyAmount"),
  78:                              GetTicketAddress = reader.GetValue<string>("GetTicketAddress"),
  79:                              TicketType=reader.GetValue<string>("TicketType")
  80:                          });
  81:                      }
  82:                      return null;
  83:                  }
  84:              }
  85:          }
  86:   
  87:      public AdmissionTicketOrder Find(Query query)
  88:          {
  89:              SqlCommand cmd = _sqlHelper.PrepareSqlCommand();
  90:              query.TranslateInfo(cmd);
  91:              using (var reader = cmd.ExecuteReader())
  92:              {
  93:                  if (reader.Read())
  94:                  {
  95:                      return (new AdmissionTicketOrder
  96:                      {
  97:                          Id = reader.GetValue<int>("Id"),
  98:                          OrderID = reader.GetValue<int>("OrderID"),
  99:                          TicketId = reader.GetValue<int>("TicketId"),
 100:                          OrderUserName = reader.GetValue<string>("OrderUserName"),
 101:                          OrderUserSex = reader.GetValue<int>("OrderUserSex"),
 102:                          OrderUserPhone = reader.GetValue<string>("OrderUserPhone"),
 103:                          ProviderContactName = reader.GetValue<string>("ProviderContactName"),
 104:                          ProviderContactPhone = reader.GetValue<string>("ProviderContactPhone"),
 105:                          TicetUseDateTime = reader.GetValue<DateTime>("TicetUseDateTime"),
 106:                          IsNeed = reader.GetValue<bool>("IsNeed"),
 107:                          BillAddress = reader.GetValue<string>("BillAddress"),
 108:                          SalePrice = reader.GetValue<decimal>("SalePrice"),
 109:                          BuyPrice = reader.GetValue<decimal>("BuyPrice"),
 110:                          BuyAmount = reader.GetValue<decimal>("BuyAmount"),
 111:                          GetTicketAddress = reader.GetValue<string>("GetTicketAddress"),
 112:                          TicketType = reader.GetValue<string>("TicketType")
 113:                      });
 114:                  }
 115:                  return null;
 116:              }
 117:          }
 118:   
 119:      public IEnumerable<AdmissionTicketOrder> FindBy(Query query)
 120:          {
 121:              IList<AdmissionTicketOrder> admissionTicketOrders = new List<AdmissionTicketOrder>();
 122:              SqlCommand cmd = _sqlHelper.PrepareSqlCommand();
 123:              query.TranslateInfo(cmd);
 124:              using (SqlDataReader reader = cmd.ExecuteReader())
 125:              {
 126:                  while (reader.Read())
 127:                  {
 128:                      admissionTicketOrders.Add(
 129:                          new AdmissionTicketOrder
 130:                          {
 131:                              Id = reader.GetValue<int>("Id"),
 132:                              OrderID = reader.GetValue<int>("OrderID"),
 133:                              TicketId = reader.GetValue<int>("TicketId"),
 134:                              OrderUserName = reader.GetValue<string>("OrderUserName"),
 135:                              OrderUserSex = reader.GetValue<int>("OrderUserSex"),
 136:                              OrderUserPhone = reader.GetValue<string>("OrderUserPhone"),
 137:                              ProviderContactName = reader.GetValue<string>("ProviderContactName"),
 138:                              ProviderContactPhone = reader.GetValue<string>("ProviderContactPhone"),
 139:                              TicetUseDateTime = reader.GetValue<DateTime>("TicetUseDateTime"),
 140:                              IsNeed = reader.GetValue<bool>("IsNeed"),
 141:                              BillAddress = reader.GetValue<string>("BillAddress"),
 142:                              SalePrice = reader.GetValue<decimal>("SalePrice"),
 143:                              BuyPrice = reader.GetValue<decimal>("BuyPrice"),
 144:                              BuyAmount = reader.GetValue<decimal>("BuyAmount"),
 145:                              GetTicketAddress = reader.GetValue<string>("GetTicketAddress"),
 146:                              TicketType = reader.GetValue<string>("TicketType")
 147:                          }
 148:                      );
 149:                  }
 150:              }
 151:              return admissionTicketOrders;
 152:          }
 153:   
 154:      public IEnumerable<AdmissionTicketOrder> FindBy(Query query, int pageSize, int pageCount)
 155:      {
 156:          return FindBy(query).Skip(pageSize * pageCount).Take(pageCount);
 157:      }
 158:  }

控制台调用

   1:  static void Main(string[] args)
   2:  {
   3:      using (var sqlHelper = new SqlHelper("ConnectionString"))
   4:      {
   5:          AdmissionTicketOrderRepository ator = new AdmissionTicketOrderRepository(sqlHelper);
   6:          AdmissionTicketOrder ato = ator.Find(2);
   7:   
   8:          Query query = new Query();
   9:   
  10:          query.Add(new Criterion("SalePrice", 100, CriteriaOperator.GreaterThan));
  11:          query.QueryOperator = QueryOperator.And;
  12:          query.Add(new Criterion("TicketType", "单一票型", CriteriaOperator.Equal));
  13:          query.Add(new Criterion("BuyAmount", 250, CriteriaOperator.LessThan));
  14:          query.QueryOperator = QueryOperator.And;
  15:          query.OrderByProperty = new OrderByClause { PropertyName = "TicetUseDateTime", Desc = true };
  16:   
  17:          List<AdmissionTicketOrder> list = ator.FindBy(query).ToList();
  18:          foreach (var item in list)
  19:          {
  20:              Console.WriteLine("{0}{1}{2}{3}",item.ID,item.ProviderContactName,item.BuyPrice,item.SalePrice);
  21:          }
  22:      }
  23:      Console.Read();
  24:      
  25:  }

源代码下载

原文地址:https://www.cnblogs.com/whx1973/p/2838666.html