企业应用架构模式——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: }