Query Object模式设计Query Object(查询对象设计),被设计为“一个可以生成数据库查询条件”的对象。如果没有这样的查询条件设计,我们的数据层可能会充斥着大量的查询方法(当然,接收where条件的万能查询设计我们就不讨论了),比如,我们可能会要求按产品分类来查询产品,也会被要求按产品名称查询产品,还有可能要求按产品编号来查询产品等等,这样我们在Repository中会存在3个或更多的查询方法,但如果我们设计Query Object对象,Repository中只需要存在一个接收Query Object对象的查询方法即可。
下面开始实现Query Object设计。
首先我们分析一下一要sql查询命令所需及可变的元素,看下面的sql:
select * from table1 where fields1=’xx001’ and fields2=’abc’ order by fields1 desc
1,table1是表名,换到我们的程序设计中就是model对象名,它是可变的。
2,fields1与fields2是表中的字段,换到程序中就是model对象的属性,它是可变的。
3,fields1与fields2中的and,是两个查询条件的连接符,字是可变的,但我们可以知道它变化的范围,无非就是 and 和 or,所以可以设计为枚举。
4,ields1=’xx001’中的=,是字段与值的比较符,我们可以知道它变化的范围,比如=、 >、<等,所以也可以设计为枚举。
5,order by fields1 desc中的desc,查询结果的排序方式,只有两个值 desc,asc,所以还是设计为枚举。
好了,下面开始真正代码:
先设计连接符枚举
/// <summary >
/// 多个查询条件的连接符
/// </summary >
public enum eCriteriaConnect
{
And,
Or
}
再就是字段与值的比较符
/// <summary >
/// 查询连接比较符
/// </summary >
public enum eCriteriaOperator
{
Equal,
Less,
Greater
}
这里我们只枚举了三个(等于,小于,大于),更多的比较请自己添加。
再下来就是查询结果的排序方式
public enum Sort
{
Desc,
Asc
}
有了上面三个枚举,我们开始创建查询条件类QueryFilter,代码如下:
public class QueryFilter
{
private string propertyName;
private object value;
private eCriteriaOperator criteriaOperator;
private eCriteriaConnect criteriaConnect;
private QueryFilter(string propertyName, object value, eCriteriaOperator criteriaOperator)
{
this.propertyName = propertyName;
this.value = value;
this.criteriaOperator = criteriaOperator;
}
private QueryFilter(string propertyName, object value, eCriteriaOperator criteriaOperator, eCriteriaConnect criteriaConnect)
: this(propertyName, value, criteriaOperator)
{
this.criteriaConnect = criteriaConnect;
}
public string PropertyName
{
get { return propertyName; }
}
public object Value
{
get { return value; }
}
public eCriteriaOperator CriteriaOperator
{
get { return criteriaOperator; }
}
public eCriteriaConnect CriteriaConnect
{
get { return criteriaConnect; }
}
}
解释下上面代码:
propertyName:要查询的字段,即where fields1=’xx001’中的fields1
value:字段的值,即where fields1=’xx001’中的value
criteriaOperator:字段与值的比较符,即where fields1=’xx001’中的=号
criteriaConnect:两个查询条件的连接符即ields1=’xx001’ and fields2=’abc’ 中的 and。
另外我们添加了两个构造函数,因为我们知道,如果查询中只有一个查询条件,是不需要criteriaConnect的。
结果排序类:
public class OrderByClause
{
public string PropertyName { get; set; }
public eSort SortType { get; set; }
public OrderByClause(string propertyName, eSort sortType)
{
this.PropertyName = propertyName;
this.SortType = sortType;
}
}
这个类很简单
PropertyName:要排序的字段
SortType:排序的方式(升序还是降序)
查询类:
public class Query<T > where T:IModel
{
private IList<QueryFilter > filterList;
public IList<QueryFilter > FilterList
{
get
{
if (filterList == null)
{
filterList = new List<QueryFilter >();
}
return filterList;
}
}
public OrderByClause order{get;set;}
}
这个类也很简单了,它是调用整个Query Object对象的入口,用来收集用户的所有条件,然后我们通过辅助类将其生成最终的sql语句,其中where T:IModel主要用来限制T必须是一个Model对象,这个不是必须的。
辅助类,将Query对象转换成sql并执行,返回一个DataTable
public static class QueryTranslator
{
public static DataTable TranslatorSql<T >(Query<T > query)
{
StringBuilder strSql = new StringBuilder("select * from " + typeof(T).Name + " where ");
strSql.Append(GetQueryFilterForStr(query));
strSql.Append(" ");
strSql.Append(GetOrder(query.order));
SqlParameter[] parameters = InitParameter<T >(query);
return DBUtility.DbHelperSQL.Query(strSql.ToString(), parameters);
}
//将QueryFilter对象转为查询条件
private static string GetQueryFilterForStr<T >(Query<T > query)
{
StringBuilder strSql = new StringBuilder(" where ");
int filterListCount = query.FilterList.Count();
bool isFirstFilter = true;
for (int i = 0; i < filterListCount; i++)
{
if (!isFirstFilter)
strSql.Append(" " + GetCriteriaConnect(query.FilterList[i].CriteriaConnect) + " ");
strSql.Append(query.FilterList[i].PropertyName + GetCriteriaOperator(query.FilterList[i].CriteriaOperator) + "@" + query.FilterList[i].PropertyName);
isFirstFilter = false;
}
return strSql.ToString();
}
//生成SqlParameter参数集合
private static SqlParameter[] InitParameter<T >(Query<T > query)
{
IEnumerable<Pair > filterList = query.FilterList.Select(
s = > new Pair(s.PropertyName, s.Value));
int parameterCount = filterList.Count();
SqlParameter[] parameter = new SqlParameter[parameterCount];
int i = 0;
foreach (Pair t in filterList)
{
parameter[i] = new SqlParameter(t.First.ToString(), t.Second);
i++;
}
return parameter;
}
//生成排序条件
private static string GetOrder(OrderByClause order)
{
StringBuilder orderStr = new StringBuilder();
orderStr.Append(" order by");
orderStr.Append(order.PropertyName);
orderStr.Append(" ");
orderStr.Append(order.SortType.ToString());
return orderStr.ToString();
}
//生成查询条件连接符
private static string GetCriteriaConnect(eCriteriaConnect criteriaConnect)
{
switch (criteriaConnect)
{
default:
case eCriteriaConnect.And:
return "and";
case eCriteriaConnect.Or:
return "or";
}
}
//生成查询条件比较符
private static string GetCriteriaOperator(eCriteriaOperator criteriaOperator)
{
switch (criteriaOperator)
{
default:
case eCriteriaOperator.Equal:
return "=";
case eCriteriaOperator.Greater:
return " >";
}
}
}
好了,到此该设计就完成了,我们可以像下面这样调用它
Query<Model.Product > query = new Query<Model.Product >();
QueryFilter filter =new QueryFilter("ProductID","xxx_001", eCriteriaOperator.Equal);
query.FilterList.Add(filter);
filter =new QueryFilter("ProductName","xxx_002", eCriteriaOperator.Equal,eCriteriaConnect.And);
query.FilterList.Add(filter);
OrderByClause order=new OrderByClause("ProduceID",eSort.Desc)
query.order=order;
DataTable dt = QueryTranslator.TranslatorSql(query);
在上面的调用中,我们出现了"ProduceID"类的硬编码字符,这样的字符在使用的时候很容易出错,我们要能够直接引用查询对象的属性就是最好了,我们可以通个增加一个辅助类来实现这个效果。
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);
}
}
上面辅助类可以帮助我们从p= >p.ProductID这样的lambda表达式中分析出属性的名称。这样我们可以将上面的代码
QueryFilter filter =new QueryFilter("ProductID","xxx_001", eCriteriaOperator.Equal);
改写成这样子
QueryFilter filter =new QueryFilter(PropertyNameHelper.ResolvePropertyName<Product >(p= >p.ProductID),"xxx_001", eCriteriaOperator.Equal);
这样是不是有进步了呢?
但这样还是比较麻烦,我们可以进一步改造QueryFilter类,在QueryFilter类中添加以下方法:
public static QueryFilter Create<T >(Expression<Func<T, object > > expression, object value, eCriteriaOperator criteriaOperator)
{
string propertyName = PropertyNameHelper.ResolvePropertyName<T >(expression);
QueryFilter queryFilter = new QueryFilter(propertyName, value, criteriaOperator);
return queryFilter;
}
这样我们就可以将代码
QueryFilter filter =new QueryFilter(PropertyNameHelper.ResolvePropertyName<Product >(p= >p.ProductID),"xxx_001", eCriteriaOperator.Equal);
改成这样子:
QueryFilter filter =QueryFilter .Create<Product >(p= >p.ProductID,"xxx_001", eCriteriaOperator.Equal);
这样就更加的方便了。
好了,大家也可以用这种方法改造OrderByClause类,让它也可以使用lamdba表达式。
最后的类图设计是这样的
图1
图2