本系列目錄:
使用EF構建企業級應用(一):主要講數據庫訪問基類IRepository及Repository 的實現
使用EF構建企業級應用(二):主要講動態排序擴展的實現
使用EF構建企業級應用(三):主要講靈活的構建查詢條件表達式Expression<Func<TEntity,bool>>.
使用EF構建企業級應用(四):主要講下在MVC環境中前端開發中如何郵箱的使用,及一個實例源碼包
在前兩篇文章中,我們已經實現了基於EF的數據庫基本操作基類的構建,以及簡單的介紹了如何方便的動態構建排序表達式,在第二篇文章結尾,我們遺漏下來了一個問題:如何方便的構建查詢參數(即類似於這樣的Expression<TEntity, bool> expression查詢表達式)
在往常的經驗中,我們知道在和數據庫交互的過程中,查詢可能是最復雜的,做過數據持久化封裝的同學們可能對這個認識尤為突出,其他原因我們就不細說了, 如何豐富的,易用的構建查詢條件這個就有點讓人迷惑.
我們來分析一個常見的查詢條件(a>4 and b<3)Or(d>5 and c in(3,4,5)),
- 設定:var exp1=a>4 and b<3; var exp2=d>5 and c in(3,4,5),則上述條件可以表示為 var exp=exp1 or exp2;
- 進一步分解exp1:var exp1_1=a>4; var exp1_2=b<3; 則exp1=exp1_1 and var exp_2
- 進一步分解exp2:var exp2_1=d>5; var exp2_2=c in (3,4,5); 則exp2=exp2_1 and exp2_2
有了上面的分解,我們發現其實構建這個查詢條件非常符合我們常見的遞歸算法.下面我們嘗試一下用遞歸的思想來實現這個.我們先大致的畫個UML草圖
為了不和系統下面的Expression 命名混淆,我們這里采用EFExpression作為條件表達式基類名稱,
- EFExpression<T> 為一個抽象基類,里面主要有一個返回類型為 Expression<Func<T,bool>> GetExpression()的一個抽象方法,其具體實現,我們放在了每一個具體的Expression中去定義.
- EmptyEFExpression<T> 表示一個空的查詢表達式
- BinaryEFExpression<T> 表示一個基於二元條件的查詢表達式,如大於,小於,等於 ……
- LikeEFExpression<T> 表示一個用於創建”類似於”條件的查詢表達式
- LogicEFExpression<T> 表示一個邏輯運算的查詢表達式,如and or
- ….可能還會有其他子類
1.首先我們試着來編寫一下抽象基類EFExpression<T>的實現
/// <summary> /// 查詢表達式基類 /// </summary> public abstract class EFExpression<T> where T : class { /// <summary> /// 獲取查詢表達式 /// </summary> /// <returns></returns> public virtual Expression<Func<T, bool>> GetExpression() { if (Expression != null) { var candidateExp = Expression.Parameter(typeof(T), "x"); var exp = Expression.Lambda<Func<T, bool>>(Expression, candidateExp); return exp; } else { return null; } } /// <summary> /// 查詢條件對應表達式 /// </summary> protected Expression _Expression { get; set; } /// <summary> /// 參數表達式 /// </summary> public ParameterExpression[] Parameters { get; set; } /// <summary> /// 獲取對應的表達式 /// </summary> internal abstract Expression Expression { get; } /// <summary> /// 獲取表達式主題 /// </summary> /// <typeparam name="T"></typeparam> /// <typeparam name="P"></typeparam> /// <param name="old"></param> /// <param name="property"></param> /// <returns></returns> protected static Expression GetMemberExpression<T, P>(EFExpression<T> old, Expression<Func<T, P>> property) where T : class { if (old.Parameters == null || old.Parameters.Length == 0) { old.Parameters = property.Parameters.ToArray(); return property.Body; } ParameterExpressionVisitor visitor = new ParameterExpressionVisitor(old.Parameters[0]); Expression memberExpr = visitor.ChangeParameter(property.Body); return memberExpr; } /// <summary> /// 獲取一個空的表達式 /// </summary> /// <returns></returns> public Expression<Func<T, bool>> GetEmptyExpression() { return (Expression<Func<T, bool>>)(f => true); } /// <summary> /// 兩個條件進行And運算 /// </summary> /// <param name="left"></param> /// <param name="right"></param> /// <returns></returns> public static EFExpression<T> operator &(EFExpression<T> left, EFExpression<T> right) { return new LogicEFExpression<T>(left, ELogicType.And, right); } /// <summary> /// 兩個條件進行Or運行 /// </summary> /// <param name="left"></param> /// <param name="right"></param> /// <returns></returns> public static EFExpression<T> operator |(EFExpression<T> left, EFExpression<T> right) { return new LogicEFExpression<T>(left, ELogicType.Or, right); } }
2. 接着我們來看一下如何實現這個BinaryEFExpression<T>
/// <summary> /// 二元運算查詢條件 /// </summary> /// <typeparam name="T">查詢條件實體類型</typeparam> /// <typeparam name="TVal">需要比較的屬性類型</typeparam> internal class BinanryEFExpression<T, TVal> : EFExpression<T> where T : class where TVal : IComparable { /// <summary> /// 定義條件的實體屬性 /// </summary> private Expression<Func<T, TVal>> property; /// <summary> /// 比較的值 /// </summary> private TVal val; /// <summary> /// 二元運算符 /// </summary> private EBinaryType binaryType; /// <summary> /// 實例化新的二元查詢表達式 /// </summary> /// <param name="property">定義條件的實體屬性</param> /// <param name="binaryType">二元運算符</param> /// <param name="val">比較的值</param> public BinanryEFExpression(Expression<Func<T, TVal>> property, EBinaryType binaryType, TVal val) { if (property == null) throw new ArgumentNullException("property"); //if (val == null && binaryType != EBinaryType.Like) // throw new ArgumentNullException("val"); this.property = property; this.val = val; this.binaryType = binaryType; } internal override Expression Expression { get { if (_Expression == null) { var propertyBody = GetMemberExpression(this, property); Type type = typeof(TVal); Expression compareVal = Expression.Constant(val); //如果是Nullable類型,則把value轉化成Nullable類型 if (type.IsNullableType()) { compareVal = Expression.Convert(compareVal, type); } Expression tempExp = null; switch (binaryType) { case EBinaryType.Equal: tempExp = Expression.Equal(propertyBody, compareVal); break; case EBinaryType.GreaterThan: tempExp = Expression.GreaterThan(propertyBody, compareVal); break; case EBinaryType.GreaterThanOrEqual: tempExp = Expression.GreaterThanOrEqual(propertyBody, compareVal); break; case EBinaryType.LessThan: tempExp = Expression.LessThan(propertyBody, compareVal); break; case EBinaryType.LessThanOrEqual: tempExp = Expression.LessThanOrEqual(propertyBody, compareVal); break; case EBinaryType.NotEqual: tempExp = Expression.NotEqual(propertyBody, compareVal); break; default: break; } _Expression = tempExp; } return _Expression; } } }
3.我們在來看一下關於表達式邏輯運算的LogicEFExpression<T> 類又是如何實現的
/// <summary>
/// 帶有邏輯運算的查詢表達式
/// </summary>
/// <typeparam name="T"></typeparam>
public class LogicEFExpression<T> : EFExpression<T> where T : class
{
private EFExpression<T> left;
private EFExpression<T> right;
private ELogicType logicType;
/// <summary>
/// 實例化新的邏輯運算查詢表達式
/// </summary>
/// <param name="left"></param>
/// <param name="logicType">邏輯運算類型</param>
/// <param name="right"></param>
public LogicEFExpression(EFExpression<T> left, ELogicType logicType, EFExpression<T> right)
{
if (left == null || right == null)
throw new ArgumentNullException("left 和 right 不能同時為空");
this.left = left;
this.right = right;
this.logicType = logicType;
}
public override Expression<Func<T, bool>> GetExpression()
{
if (left == null)
return right.GetExpression();
else if (right == null)
return left.GetExpression();
else
{
//判斷進行運算的兩個條件是否為空
if (left is EmptyEFExpression<T> && right is EmptyEFExpression<T>)
return left.GetExpression();
else if (left is EmptyEFExpression<T>)
return right.GetExpression();
else if (right is EmptyEFExpression<T>)
return left.GetExpression();
var leftExp = left.GetExpression();
var rightExp = right.GetExpression();
Expression<Func<T, bool>> exp = null;
if (leftExp == null && rightExp == null)
return new EmptyEFExpression<T>().GetExpression();
else
{
if (leftExp == null)
return rightExp;
else if (rightExp == null)
return leftExp;
else
{
switch (logicType)
{
case ELogicType.And:
exp = leftExp.And(rightExp);
break;
case ELogicType.Or:
exp = leftExp.Or(rightExp);
break;
default:
break;
}
}
}
return exp;
}
}
internal override Expression Expression
{
get
{
return null;
}
}
}
4.為了使用方便,我們在基類中再添加個靜態方法
/// <summary>
/// 構建一個二元查詢表達式
/// </summary>
/// <typeparam name="TVal">比較值的類型</typeparam>
/// <param name="property">定義條件的實體屬性</param>
/// <param name="binaryType">運算符類型</param>
/// <param name="val">比較的值</param>
/// <returns></returns>
public static EFExpression<T> CreateBinaryExpression<TVal>(Expression<Func<T, TVal>> property,
EBinaryType binaryType, TVal val) where TVal : IComparable
{
return new BinanryEFExpression<T, TVal>(property, binaryType, val);
}
5.有了上面的實現,我們來測試下我們的想法.為了讓我們更容易理解,我們假設在一個訂單管理環境中,有如下一個需求:請查詢滿足以下任意一個條件中的訂單記錄
- 訂單狀態還未送貨且金額>50W的
- 訂單狀態為送貨中,且客戶地址在深圳片區的
為了便於理解,我們在這里先定義兩個實體類
/// <summary>
/// 訂單主表
/// </summary>
public class OrderMain
{
public Guid Id { get; set; }
public string Code { get; set; }
public DateTime BillDate { get; set; }
public Guid CustomerId { get; set; }
public virtual Customer Customer { get; set; }
public decimal TotalAmount { get; set; }
public int Status { get; set; }
}
/// <summary>
/// 客戶信息
/// </summary>
public class Customer
{
public Guid Id { get; set; }
public string Name { get; set; }
public string Area { get; set; }
}
那么我們這里使用的查詢條件可能就會寫成如下形式:
var exp=EFExpression<OrderMain>.CreateBinaryExpression(o=>o.Status,EBinaryType.Equal,0); //還沒有送貨 exp&=EFExpression<OrderMain>.CreateBinaryExpression(o=>o.TotalAmount ,EBinaryType.LessThan,500000); //小於50W var exp2=EFExpression<OrderMain>.CreateBinaryExpression(o=>o.Status,EBinaryType.Equal,1); //送貨中 exp2&=EFExpression<OrderMain>.CreateBinaryExpression(o=>o.Customer.Area,EBinaryType.Equal,”0755”); //地址在深圳片區 exp|=exp2; var queryExpression=exp.GetExpression();
測試通過.
,至於其他的子類在此就不一一列舉了,當然我們可以通過擴展一些寫法使之更容易使用,比如
/// <summary>
/// 在當前條件基礎上,構建一個like條件,並且和當前條件產生And運算
/// </summary>
/// <param name="property"></param>
/// <param name="val"></param>
/// <returns></returns>
public static EFExpression<T> AndLike<T>(this EFExpression<T> old, Expression<Func<T, string>> property, string val) where T : class
{
var temp = new LikeEFExpression<T>(property, val);
if (old == null)
return temp;
else
return new LogicEFExpression<T>(old, ELogicType.And, temp);
}
/// <summary>
/// 在當前條件基礎上,構建一個等於條件,並且和當前條件產生And運算
/// </summary>
/// <typeparam name="TVal"></typeparam>
/// <param name="old"></param>
/// <param name="property"></param>
/// <param name="val"></param>
/// <returns></returns>
public static EFExpression<T> AndEqual<T, TVal>(this EFExpression<T> old, Expression<Func<T, TVal>> property, TVal val)
where TVal : IComparable
where T : class
{
var temp = new BinanryEFExpression<T, TVal>(property, EBinaryType.Equal, val);
if (old == null)
return temp;
else
return new LogicEFExpression<T>(old, ELogicType.And, temp);
}
/// <summary>
/// 在當前條件基礎上,構建一個大於條件,並且和當前條件產生And運算
/// </summary>
/// <typeparam name="TVal"></typeparam>
/// <param name="old"></param>
/// <param name="property"></param>
/// <param name="val"></param>
/// <returns></returns>
public static EFExpression<T> AndGreaterThan<T, TVal>(this EFExpression<T> old, Expression<Func<T, TVal>> property, TVal val)
where TVal : IComparable
where T : class
{
var temp = new BinanryEFExpression<T, TVal>(property, EBinaryType.GreaterThan, val);
if (old == null)
return temp;
else
return new LogicEFExpression<T>(old, ELogicType.And, temp);
}
那么我們在使用的時候就可以方便的寫成下面這種形式
var exp=EFExpression<OrderMain>.Create().AndEqual(o=>o.Status,0).AndGreaterThan(o=>o.TotalAmount ,500000)…..
在此不再贅述,有興趣的朋友們可以自己去寫出類似的東東,或問我要源碼包...
