回顧
上兩篇文章主要講解了我對於數據層的Unit Of Work(工作單元模式)的理解,其中包括了CUD的操作,那么今天就來談談R吧,文章包括以下幾點:
- 什么是Query Object
- 基於SQL的實現
什么是Query Object
Query Object從語義就能看出它的作用,就是將查詢封裝成對象,並在內部轉換成SQL語句或ORM框架的語法來實現查詢操作。
既然提到了這個模式,肯定是有它的好處的,但是光用語言來說明,可能不好體會,那么就從代碼的角度來看看這個模式的好處吧。
首先,項目數據層接口都會包含根據ID和根據分頁獲取數據的方法,大致代碼如下:
IEnumerable<T> FindAll(); IEnumerable<T> FindById<T>(string id); IEnumerable<T> FindByPage<T>(int pageIndex, int pageSize);
然后其他的業務可能還會提供諸如以下幾種方法,代碼如下:
IEnumerable<School> FindByName(string name); IEnumerable<School> FindByAge(int age);
光從以上幾個方法就能看出查詢的方式多種多樣,不統一,這樣子在不管在維護還是在代碼的重用都會相對比較麻煩。
但是有了Query Object模式,就可以得出一個統一的接口,代碼如下:
IEnumerable<T> FindBy(Query query); IEnumerable<T> FindBy(Query query, int pageIndex, int pageSize);
光是接口就從原來的4-5個變成了2個,程序員在使用的時候都是同樣的Query Object,而不需要內部是使用SQL還是NHibernate,又或者EF。
研發人員只需要對相應框架實現對應的Query Object翻譯類就可以輕松搞定查詢任務了,那么事不宜遲,讓我們一起來分析、實現吧。
基於SQL的實現
首先我們分析一下查詢條件,如:select * from school where id = '001',條件包含一個名字、值和條件符號,那么可以通過定義一個條件符號枚舉和條件類來存儲,代碼如下:
public enum CriteriaOperator
{
None,
Eq,
Lt,
Gt
}
public class Criterion
{
private string m_PropertyName = null;
public string PropertyName { get { return m_PropertyName; } }
private CriteriaOperator m_Operator = CriteriaOperator.None;
public CriteriaOperator Operator
{
get { return m_Operator; }
set { m_Operator = value; }
}
private object m_Value = null;
public object Value { get { return m_Value; } }
private Criterion(string propertyName, object value, CriteriaOperator criteriaOperator)
{
m_PropertyName = propertyName;
m_Value = value;
m_Operator = criteriaOperator;
}
public static Criterion Create(string propertyName, object value, CriteriaOperator criteriaOperator)
{
return new Criterion(propertyName, value, criteriaOperator);
}
}
接下來需要一個更復雜的條件,如:select * from school where age > 20 and (age < 50 or name = "一中"),查詢中包含了子條件,那么需要定義額外的枚舉來表示and和or,其次就是查詢內部要能支持子查詢條件,大致代碼如下:
public class Query
{
private List m_Criterions = new List();
public IEnumerable Criterions { get { return m_Criterions; } }
private List m_SubQueries = new List();
public IEnumerable SubQueries { get { return m_SubQueries; } }
private QueryOperator m_Operator = QueryOperator.And;
public QueryOperator Operator { get { return m_Operator; } }
private Query() : this(QueryOperator.And) { }
private Query(QueryOperator queryOperator)
{
m_Operator = queryOperator;
}
public void Add(Criterion criterion)
{
m_Criterions.Add(criterion);
}
public void AddSubQuery(Query subQuery)
{
m_SubQueries.Add(subQuery);
}
public static Query Create()
{
return new Query();
}
public static Query Create(QueryOperator queryOperator)
{
return new Query(queryOperator);
}
}
我們從SQL推導出需要實現該功能的類,接下來要從功能類如果翻譯成具體的SQL,回頭看一下Query、Criterion,其實我們實現的是where后面的SQL語句而已,因此翻譯類能完成的功能就是將Query轉化為where后面的語句以及包含的參數,大致代碼如下:
private static Tuple<string, List> Translate(Query query, int parameterCount)
{
List tempSQLs = new List();
List parameters = new List();
if (query.Criterions.Any())
{
tempSQLs = query.Criterions.Select(c =>
{
string oper = "";
switch (c.Operator)
{
case CriteriaOperator.None:
return null;
case CriteriaOperator.Eq:
oper = "=";
break;
case CriteriaOperator.Lt:
oper = "<";
break;
case CriteriaOperator.Gt:
oper = ">";
break;
}
string parameterName = string.Format("@param{0}", parameterCount + parameters.Count);
parameters.Add(new SqlParameter(parameterName, c.Value));
return string.Format("{0} {1} {2}", c.PropertyName, oper, parameterName);
}).Where(sql => !string.IsNullOrEmpty(sql)).ToList();
parameterCount += parameters.Count;
}
if (query.SubQueries.Any())
{
foreach (var subQuery in query.SubQueries)
{
var subResult = Translate(subQuery, parameterCount);
if (subResult == null)
continue;
tempSQLs.Add(string.Format("({0})", subResult.Item1));
parameters.AddRange(subResult.Item2);
parameterCount += subResult.Item2.Count;
}
}
if (tempSQLs.Count == 0)
return null;
return new Tuple<string, List>(
string.Join(query.Operator == QueryOperator.And ? " and " : " or ", tempSQLs),
parameters);
}
結尾
到這里我們就將基於SQL的Query Object模式實現了,不過用起來還是稍微有點復雜的,這時候大家可以用Lambda表達式對以上的代碼進行改造,改造成為如下格式:
var query = Query.Create(); query.Add(s => s.Age > 20 && (s.Age < 50 || s.Name == "一中"));
具體實現的代碼,我就不給出了,畢竟了解System.Linq.Expression也是有很多好處的,特別是在項目擴展方面,大家趁此機會鍛煉一下自己的編碼。
看到這里相信大家也看到了,對於多表查詢的情況,Query Object模式對它的支持也是比較困難的,特別是實現起來,嵌套層數太多,因此當SQL存在多表的情況下,還是得額外進行實現。
由於時間的問題,今天就到這里了,下一篇文章我會繼續Unit Of Work的方式,會基於NHibernate來實現,可能還會加上對於BeGoo的擴展,敬請期待。
再次提醒各位,以上代碼均為示例代碼,雖然可以編譯通過,但是最好不要直接應用到項目當中,需根據項目具體情況進行重構后使用。
如果文章有任何錯誤和問題,歡迎留言,謝謝大家。
