Query Object模式
Query Object:可以在領(lǐng)域服務(wù)層構(gòu)造查詢?nèi)缓髠鹘o資源庫(kù)使用,并使用某種查詢翻譯器將對(duì)象查詢(Query)翻譯成底層數(shù)據(jù)庫(kù)持久化框架可以理解的查詢(即翻譯成一條Sql 語(yǔ)句)。而Query Object即可以理解為表示數(shù)據(jù)庫(kù)查詢的對(duì)象。且可以構(gòu)造任意查詢,然后傳給Repository。Query Object模式的主要好處是它完全將底層的數(shù)據(jù)庫(kù)查詢語(yǔ)言抽象出來(lái)。
如果沒(méi)有某種查詢機(jī)制,我們的持久化層可能會(huì)這樣定義方法:
public interface IOrderRepository{IEnumerable<Order>
FindAll(Query query);IEnumerable<Order>
FindAllVipCustomer();IEnumerable<Order>
FindOrderBy(Guid customerId);IEnumerable<Order>
FindAllCustomersWithOutOrderId();} 很明顯,可以看出持久化層很不簡(jiǎn)潔,Repository將充滿大量檢索方法,而我們希望我們的持久化層盡量簡(jiǎn)潔些,根據(jù)傳入?yún)?shù)能夠動(dòng)態(tài)的翻譯成數(shù)據(jù)庫(kù)查詢語(yǔ)言,就像下面寫的這樣:
public interface IOrderRepository{ IEnumerable<Order>
FindBy(Query query);IEnumerable<Order> FindBy(Query query,
int index,
int count); } 這個(gè)Query就是核心——一個(gè)表示數(shù)據(jù)庫(kù)查詢的對(duì)象,好處是顯而易見(jiàn)的:完全將底層的數(shù)據(jù)庫(kù)查詢語(yǔ)言抽象出來(lái),因此將數(shù)據(jù)持久化和檢索的基礎(chǔ)設(shè)施關(guān)注點(diǎn)從業(yè)務(wù)層中分離出來(lái)。
Query Object模式的架構(gòu)
- 添加一個(gè)枚舉,CriteriaOperator:
public enum CriteriaOperator{Equal,//=LessThanOrEqual,
// <=NotApplicable
//≠// TODO: 省略了其他的操作符,可繼續(xù)添加}
- 接著添加Criterion類,表示構(gòu)成查詢的過(guò)濾器部分:指定一個(gè)實(shí)體屬性(OR ?Mapping)、要比較的值以及比較方式:
public class Criterion{private string _propertyName;
//實(shí)體屬性private object _value;
//進(jìn)行比較的值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表達(dá)式樹:創(chuàng)建一個(gè)過(guò)濾器/// </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;}} - 為了避免在構(gòu)建查詢時(shí)出現(xiàn)令人畏懼的魔幻字符串,我們創(chuàng)建一個(gè)輔助方法,使用表達(dá)式參數(shù)。
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);}} 這樣就可以像查詢中添加一個(gè)新的查詢條件:
query.Add(Criterion.Create<Order>(c=>c.CustomerId,customerId,CriteriaOperator.Equal));
而不是使用魔幻字符串:
query.Add(
new Criterion(
"CustomerId", customerId, CriteriaOperator.Equal));
- 下面要?jiǎng)?chuàng)建表示查詢的排序?qū)傩?#xff1a;
public class OrderByClause{public string PropertyName {
get;
set; }public bool Desc {
get;
set; }} - 接著,創(chuàng)建另一個(gè)枚舉,確定如何各個(gè)Criterion進(jìn)行評(píng)估:
public enum QueryOperator{And,Or } - 有時(shí)候的復(fù)雜非常難以創(chuàng)建,在這些情況下,可以使用指向數(shù)據(jù)庫(kù)視圖或存儲(chǔ)過(guò)程的命名查詢,添加一個(gè)QueryName來(lái)存放查詢列表:
public enum QueryName{ Dynamic =
0,
//動(dòng)態(tài)創(chuàng)建RetrieveOrdersUsingAComplexQuery =
1//使用已經(jīng)創(chuàng)建好了的存儲(chǔ)過(guò)程、視圖、特別是查詢比較復(fù)雜時(shí)使用存儲(chǔ)過(guò)程}
- 最后,添加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>/// 判斷該查詢是否已經(jīng)動(dòng)態(tài)生成或與Repository中某個(gè)預(yù)先建立的查詢相關(guān)/// </summary>/// <returns></returns>public bool IsNamedQuery(){return Name !=
QueryName.Dynamic;}public IEnumerable<Criterion>
Criteria{get {
return _criteria ;}} public void Add(Criterion criterion){if (!IsNamedQuery())
// 動(dòng)態(tài)查詢
_criteria.Add(criterion);elsethrow new ApplicationException(
"You cannot add additional criteria to named queries");}public QueryOperator QueryOperator {
get;
set; }public OrderByClause OrderByProperty {
get;
set; }} - 最后創(chuàng)建一個(gè)工廠類,提供已存在的查詢:
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在服務(wù)層的運(yùn)用
- 建立領(lǐng)域模型和領(lǐng)域服務(wù)類:
public class Order{public Guid Id {
get;
set; }public bool HasShipped {
get;
set; }public DateTime OrderDate {
get;
set; }public Guid CustomerId {
get;
set; }} 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));//輸入魔幻字符串,容易出錯(cuò)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個(gè)方法,他們將創(chuàng)建的查詢傳遞給Repository。FindAllCustomersOrdersBy和FindAllCustomersOrdersWithInOrderDateBy方法通過(guò)Criterion和OrderByClaus添加來(lái)創(chuàng)建動(dòng)態(tài)查詢。FindAllCustomersOrdersUsingAComplexQueryWith是命名查詢,使用NamedQueryFactory來(lái)創(chuàng)建要傳給Repository的Query Object。
- 最后創(chuàng)建一個(gè)翻譯器:QueryTranslator,將查詢對(duì)象翻譯成一條可在數(shù)據(jù)庫(kù)上運(yùn)行的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 ";elsereturn "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.");}}} - 建立簡(jiǎn)單倉(cāng)儲(chǔ)對(duì)象:
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(); } } 測(cè)試
[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);}}
轉(zhuǎn)載于:https://www.cnblogs.com/OceanEyes/archive/2012/11/14/think-in-design-pattern-query-object.html
總結(jié)
以上是生活随笔為你收集整理的Thinking In Design Pattern——Query Object模式的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
如果覺(jué)得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。