前面已經作了一些準備工做,本篇將介紹查詢條件的封裝,它是規約模式的一個應用。html
規約使用一個對象來封裝謂詞,我以前已經介紹過它在驗證方面的應用,本篇是規約模式在查詢方面的應用。框架
規約的強大之處在於,可以將一堆雜亂無章的條件判斷或查詢條件封裝起來,以一個清晰的概念來表達,並使得這些謂詞具有了可複用的能力。ide
首先在Util.Domains項目的Repositories目錄中建立ICriteria接口,這個接口表示一個查詢條件,代碼以下。ui
using System; using System.Linq.Expressions; namespace Util.Domains.Repositories { /// <summary>
/// 查詢條件 /// </summary>
/// <typeparam name="TEntity">實體類型</typeparam>
public interface ICriteria<TEntity> where TEntity : class,IAggregateRoot { /// <summary>
/// 獲取謂詞 /// </summary>
Expression<Func<TEntity, bool>> GetPredicate(); } }
因爲咱們使用了EF這種ORM框架,查詢條件的結果是一個Expression<Func<TEntity, bool>>的謂詞表達式。this
在Util.Datas項目中,打開Extensions.Query.cs文件,增長如下代碼。spa
/// <summary>
/// 過濾 /// </summary>
/// <typeparam name="T">實體類型</typeparam>
/// <param name="source">數據源</param>
/// <param name="criteria">查詢條件</param>
public static IQueryable<T> Filter<T>( this IQueryable<T> source, ICriteria<T> criteria ) where T : class,IAggregateRoot { if ( criteria == null ) return source; var predicate = criteria.GetPredicate(); if ( predicate == null ) return source; return source.Where( predicate ); }
咱們在IQueryable對象上擴展了一個Filter方法,該方法接收一個查詢條件,若是查詢條件有效,就使用Where方法添加過濾條件。code
基礎工做就這麼多,下面來看幾個範例。htm
在信息系統中,常常會進行範圍查詢,好比一個日期段的查詢。這看起來是一個簡單的需求,初學者通常這樣寫,t => t.Date >= BeginDate && t.Date <= EndDate,其結果多是錯誤的,這是因爲從表現層傳入的查詢條件是可選的,若是客戶沒有進行輸入,結果就是錯的。對象
對於範圍查詢來說,還有更多的細節須要思考,好比,起始日期和結束日期都沒有輸入,或只輸入了起始日期或結束日期,也可能客戶輸入的起始日期比結束日期還大。爲了得到健壯性,咱們會對查詢條件進行各類判斷,從而致使雜亂無章的代碼。更要命的是,這些代碼沒法複用,在另外一個範圍查詢的位置,咱們必須把以前的代碼複製過去進行修改。blog
一個更好的辦法是把範圍查詢邏輯封裝到查詢條件對象中,之後須要進行範圍查詢時,便可隨手拈來。
在Util.Datas項目Queries目錄中,新建Criterias目錄,建立一個查詢條件基類CriteriaBase,代碼以下。
using System; using System.Linq.Expressions; using Util.Domains; using Util.Domains.Repositories; namespace Util.Datas.Queries.Criterias { /// <summary>
/// 查詢條件 /// </summary>
/// <typeparam name="TEntity">實體類型</typeparam>
public abstract class CriteriaBase<TEntity> : ICriteria<TEntity> where TEntity : class, IAggregateRoot { /// <summary>
/// 謂詞 /// </summary>
protected Expression<Func<TEntity, bool>> Predicate { get; set; } /// <summary>
/// 獲取謂詞 /// </summary>
public virtual Expression<Func<TEntity, bool>> GetPredicate() { return Predicate; } } }
根據數據類型不一樣,範圍查詢有不少種類,好比日期範圍查詢、日期時間範圍查詢、整數範圍查詢、浮點數範圍查詢等。咱們須要爲範圍查詢條件建立一個基類SegmentCriteria,代碼以下。
using System; using System.Linq.Expressions; using Util.Domains; using Util.Lambdas; namespace Util.Datas.Queries.Criterias { /// <summary>
/// 段過濾條件 /// </summary>
/// <typeparam name="TEntity">實體類型</typeparam>
/// <typeparam name="TProperty">屬性類型</typeparam>
/// <typeparam name="TValue">值類型</typeparam>
public abstract class SegmentCriteria<TEntity, TProperty, TValue> : CriteriaBase<TEntity>
where TEntity : class, IAggregateRoot where TValue : struct { /// <summary>
/// 初始化段過濾條件 /// </summary>
/// <param name="propertyExpression">屬性表達式</param>
/// <param name="min">最小值</param>
/// <param name="max">最大值</param>
protected SegmentCriteria( Expression<Func<TEntity, TProperty>> propertyExpression, TValue? min, TValue? max ) { Builder = new ExpressionBuilder<TEntity>(); PropertyExpression = propertyExpression; Min = min; Max = max; if ( IsMinGreaterMax( min, max ) ) { Min = max; Max = min; } } /// <summary>
/// 最小值是否大於最大值 /// </summary>
/// <param name="min">最小值</param>
/// <param name="max">最大值</param>
protected abstract bool IsMinGreaterMax( TValue? min, TValue? max ); /// <summary>
/// 屬性表達式 /// </summary>
public Expression<Func<TEntity, TProperty>> PropertyExpression { get; set; } /// <summary>
/// 表達式生成器 /// </summary>
private ExpressionBuilder<TEntity> Builder { get; set; } /// <summary>
/// 最小值 /// </summary>
public TValue? Min { get; set; } /// <summary>
/// 最大值 /// </summary>
public TValue? Max { get; set; } /// <summary>
/// 獲取謂詞 /// </summary>
public override Expression<Func<TEntity, bool>> GetPredicate() { var first = CreateLeftExpression(); var second = CreateRightExpression(); return Builder.ToLambda( first.And( second ) ); } /// <summary>
/// 建立左操做數,即 t => t.Property >= Min /// </summary>
private Expression CreateLeftExpression() { if ( Min == null ) return null; return Builder.Create( PropertyExpression, Operator.GreaterEqual, GetMinValue() ); } /// <summary>
/// 獲取最小值 /// </summary>
protected virtual TValue? GetMinValue() { return Min; } /// <summary>
/// 建立右操做數,即 t => t.Property <= Max /// </summary>
private Expression CreateRightExpression() { if ( Max == null ) return null; return Builder.Create( PropertyExpression, GetMaxOperator(), GetMaxValue() ); } /// <summary>
/// 獲取最大值相關的運算符 /// </summary>
protected virtual Operator GetMaxOperator() { return Operator.LessEqual; } /// <summary>
/// 獲取最大值 /// </summary>
protected virtual TValue? GetMaxValue() { return Max; } } }
對於日期範圍查詢,日期是否包含時間很是重要,它們在行爲上是不一樣的。若是日期不包含時間,那麼須要爲結束日期加一天,並修改運算符爲小於。
日期時間範圍查詢條件DateTimeSegmentCriteria,代碼以下。
using System; using System.Linq.Expressions; using Util.Domains; namespace Util.Datas.Queries.Criterias { /// <summary>
/// 日期時間段過濾條件 - 包含時間 /// </summary>
/// <typeparam name="TEntity">實體類型</typeparam>
/// <typeparam name="TProperty">屬性類型</typeparam>
public class DateTimeSegmentCriteria<TEntity, TProperty> : SegmentCriteria<TEntity, TProperty, DateTime> where TEntity : class,IAggregateRoot { /// <summary>
/// 初始化日期時間段過濾條件 /// </summary>
/// <param name="propertyExpression">屬性表達式</param>
/// <param name="min">最小值</param>
/// <param name="max">最大值</param>
public DateTimeSegmentCriteria( Expression<Func<TEntity, TProperty>> propertyExpression, DateTime? min, DateTime? max ) : base( propertyExpression, min, max ) { } /// <summary>
/// 最小值是否大於最大值 /// </summary>
protected override bool IsMinGreaterMax( DateTime? min, DateTime? max ) { return min > max; } } }
日期範圍查詢條件DateSegmentCriteria,代碼以下。
using System; using System.Linq.Expressions; using Util.Domains; namespace Util.Datas.Queries.Criterias { /// <summary>
/// 日期段過濾條件 - 不包含時間 /// </summary>
/// <typeparam name="TEntity">實體類型</typeparam>
/// <typeparam name="TProperty">屬性類型</typeparam>
public class DateSegmentCriteria<TEntity, TProperty> : SegmentCriteria<TEntity, TProperty, DateTime> where TEntity : class,IAggregateRoot { /// <summary>
/// 初始化日期段過濾條件 /// </summary>
/// <param name="propertyExpression">屬性表達式</param>
/// <param name="min">最小值</param>
/// <param name="max">最大值</param>
public DateSegmentCriteria( Expression<Func<TEntity, TProperty>> propertyExpression, DateTime? min, DateTime? max ) : base( propertyExpression, min, max ) { } /// <summary>
/// 最小值是否大於最大值 /// </summary>
protected override bool IsMinGreaterMax( DateTime? min, DateTime? max ) { return min > max; } /// <summary>
/// 獲取最小值 /// </summary>
protected override DateTime? GetMinValue() { return base.GetMinValue().SafeValue().Date; } /// <summary>
/// 獲取最大值 /// </summary>
protected override DateTime? GetMaxValue() { return base.GetMaxValue().SafeValue().Date.AddDays( 1 ); } /// <summary>
/// 獲取最大值相關的運算符 /// </summary>
protected override Operator GetMaxOperator() { return Operator.Less; } } }
整數範圍查詢條件IntSegmentCriteria,代碼以下。
using System; using System.Linq.Expressions; using Util.Domains; namespace Util.Datas.Queries.Criterias { /// <summary>
/// 整數段過濾條件 /// </summary>
/// <typeparam name="TEntity">實體類型</typeparam>
/// <typeparam name="TProperty">屬性類型</typeparam>
public class IntSegmentCriteria<TEntity, TProperty> : SegmentCriteria<TEntity, TProperty,int> where TEntity : class,IAggregateRoot { /// <summary>
/// 初始化整數段過濾條件 /// </summary>
/// <param name="propertyExpression">屬性表達式</param>
/// <param name="min">最小值</param>
/// <param name="max">最大值</param>
public IntSegmentCriteria( Expression<Func<TEntity, TProperty>> propertyExpression, int? min, int? max ) : base( propertyExpression,min,max){ } /// <summary>
/// 最小值是否大於最大值 /// </summary>
protected override bool IsMinGreaterMax( int? min, int? max ) { return min > max; } } }
double範圍查詢條件DoubleSegmentCriteria,代碼以下。
using System; using System.Linq.Expressions; using Util.Domains; namespace Util.Datas.Queries.Criterias { /// <summary>
/// double數值段過濾條件 /// </summary>
/// <typeparam name="TEntity">實體類型</typeparam>
/// <typeparam name="TProperty">屬性類型</typeparam>
public class DoubleSegmentCriteria<TEntity, TProperty> : SegmentCriteria<TEntity, TProperty, double> where TEntity : class,IAggregateRoot { /// <summary>
/// 初始化double數值段過濾條件 /// </summary>
/// <param name="propertyExpression">屬性表達式</param>
/// <param name="min">最小值</param>
/// <param name="max">最大值</param>
public DoubleSegmentCriteria( Expression<Func<TEntity, TProperty>> propertyExpression, double? min, double? max ) : base( propertyExpression, min, max ) { } /// <summary>
/// 最小值是否大於最大值 /// </summary>
protected override bool IsMinGreaterMax( double? min, double? max ) { return min > max; } } }
decimal範圍查詢條件DecimalSegmentCriteria,代碼以下。
using System; using System.Linq.Expressions; using Util.Domains; namespace Util.Datas.Queries.Criterias { /// <summary>
/// decimal數值段過濾條件 /// </summary>
/// <typeparam name="TEntity">實體類型</typeparam>
/// <typeparam name="TProperty">屬性類型</typeparam>
public class DecimalSegmentCriteria<TEntity, TProperty> : SegmentCriteria<TEntity, TProperty, decimal> where TEntity : class,IAggregateRoot { /// <summary>
/// 初始化decimal數值段過濾條件 /// </summary>
/// <param name="propertyExpression">屬性表達式</param>
/// <param name="min">最小值</param>
/// <param name="max">最大值</param>
public DecimalSegmentCriteria( Expression<Func<TEntity, TProperty>> propertyExpression, decimal? min, decimal? max ) : base( propertyExpression, min, max ) { } /// <summary>
/// 最小值是否大於最大值 /// </summary>
protected override bool IsMinGreaterMax( decimal? min, decimal? max ) { return min > max; } } }
咱們如今進行日期範圍查詢,就比較簡單了,代碼以下。
queryable.Filter( new DateSegmentCriteria<Test, DateTime>( t => t.Date, BeginDate,EndDate ) );
不過上面的代碼用起來還不是太順手,能夠將範圍查詢擴展到IQueryable,代碼以下。
/// <summary>
/// 過濾整數段 /// </summary>
/// <typeparam name="T">實體類型</typeparam>
/// <typeparam name="TProperty">屬性類型</typeparam>
/// <param name="source">數據源</param>
/// <param name="propertyExpression">屬性表達式,範例:t => t.Age</param>
/// <param name="min">最小值</param>
/// <param name="max">最大值</param>
public static IQueryable<T> FilterInt<T, TProperty>( this IQueryable<T> source, Expression<Func<T, TProperty>> propertyExpression, int? min, int? max ) where T : class,IAggregateRoot { return source.Filter( new IntSegmentCriteria<T, TProperty>( propertyExpression, min, max ) ); } /// <summary>
/// 過濾double數值段 /// </summary>
/// <typeparam name="T">實體類型</typeparam>
/// <typeparam name="TProperty">屬性類型</typeparam>
/// <param name="source">數據源</param>
/// <param name="propertyExpression">屬性表達式,範例:t => t.Age</param>
/// <param name="min">最小值</param>
/// <param name="max">最大值</param>
public static IQueryable<T> FilterDouble<T, TProperty>( this IQueryable<T> source, Expression<Func<T, TProperty>> propertyExpression, double? min, double? max ) where T : class,IAggregateRoot { return source.Filter( new DoubleSegmentCriteria<T, TProperty>( propertyExpression, min, max ) ); } /// <summary>
/// 過濾decimal數值段 /// </summary>
/// <typeparam name="T">實體類型</typeparam>
/// <typeparam name="TProperty">屬性類型</typeparam>
/// <param name="source">數據源</param>
/// <param name="propertyExpression">屬性表達式,範例:t => t.Age</param>
/// <param name="min">最小值</param>
/// <param name="max">最大值</param>
public static IQueryable<T> FilterDecimal<T, TProperty>( this IQueryable<T> source, Expression<Func<T, TProperty>> propertyExpression, decimal? min, decimal? max ) where T : class,IAggregateRoot { return source.Filter( new DecimalSegmentCriteria<T, TProperty>( propertyExpression, min, max ) ); } /// <summary>
/// 過濾日期段,不包含時間 /// </summary>
/// <typeparam name="T">實體類型</typeparam>
/// <typeparam name="TProperty">屬性類型</typeparam>
/// <param name="source">數據源</param>
/// <param name="propertyExpression">屬性表達式,範例:t => t.Age</param>
/// <param name="min">最小值</param>
/// <param name="max">最大值</param>
public static IQueryable<T> FilterDate<T, TProperty>( this IQueryable<T> source, Expression<Func<T, TProperty>> propertyExpression, DateTime? min, DateTime? max ) where T : class,IAggregateRoot { return source.Filter( new DateSegmentCriteria<T, TProperty>( propertyExpression, min, max ) ); } /// <summary>
/// 過濾日期時間段,包含時間 /// </summary>
/// <typeparam name="T">實體類型</typeparam>
/// <typeparam name="TProperty">屬性類型</typeparam>
/// <param name="source">數據源</param>
/// <param name="propertyExpression">屬性表達式,範例:t => t.Age</param>
/// <param name="min">最小值</param>
/// <param name="max">最大值</param>
public static IQueryable<T> FilterDateTime<T, TProperty>( this IQueryable<T> source, Expression<Func<T, TProperty>> propertyExpression, DateTime? min, DateTime? max ) where T : class,IAggregateRoot { return source.Filter( new DateTimeSegmentCriteria<T, TProperty>( propertyExpression, min, max ) ); }
日期範圍查詢的調用代碼簡化爲以下代碼。
queryable.FilterDate( t => t.Date, BeginDate, EndDate)
本文介紹瞭如何使用查詢條件對象封裝範圍查詢,固然你能夠用相似的方法將業務中的查詢條件封裝起來。
規約模式還有其它用法,更強大的用法,請參考陳晴陽老兄的這篇http://www.cnblogs.com/daxnet/p/3925426.html
.Net應用程序框架交流QQ羣: 386092459,歡迎有興趣的朋友加入討論。
謝謝你們的持續關注,個人博客地址:http://www.cnblogs.com/xiadao521/