前言:在DDD系列文章里面,我們在后台倉儲里面封裝了傳遞Lamada表達式的通用方法,類似這樣:
public virtual IQueryable<TEntity> Find(Expression<Func<TEntity, bool>> express) { Func<TEntity, bool> lamada = express.Compile(); return UnitOfWork.context.Set<TEntity>().Where(lamada).AsQueryable<TEntity>(); }
通過前端傳過來的Lamada表達式,直接放到Where條件里面查詢。那么問題來了,我們前端如何傳入Lamada呢?當然,有人說了,這個不用傳啊,前端直接.Find(x=>x.Name=="abc")這樣寫就好了啊。確實,如果前端條件只有一個條件,你確實可以這樣簡單處理,但是實際開發的過程中,我們很多時候是需要傳遞多個參數,並且.Find(x=>x.Name=="abc")這種寫法也不利於方法的封裝。於是,我們神奇的動態Lamada誕生了。
一、再談Lamada表達式
1、匿名委托
之前在介紹委托的時候我們介紹過一種特殊的匿名委托,它型如:
class Program { private delegate void SayHello(string name); static void Main(string[] args) { Say("張三", delegate(string name) { Console.WriteLine("你好," + name); }); Say("Zhangsan", delegate(string name) { Console.WriteLine("Hello," + name); }); } static void Say(string name,SayHello dTest) { dTest(name); } }
也就是說,不用定義一種具體的委托方法去對應SayHello(string name);,而直接delegate(string name){}這樣定義一種匿名的委托去執行,這樣能減少部分定義具體方法的代碼。
2、Lamada表達式進化史
了解了匿名委托的概念,我們來看看我們經常使用的Linq里面的擴展方法Where、Select等。先來看看一般用法:
var lstTest = new List<string>();
//.......業務邏輯 var lstRes = lstTest.Where(x => x.Contains("_"));
我們來將Where里面的x => x.Contains("_")分解。
初級進化(最原始的匿名委托形式):
Func<string, bool> oFunc = delegate(string x) { return x.Contains("_"); }; lstRes = lstTest.Where(oFunc);
高級進化(型如Lamada,但還有匿名委托的影子):
Func<string, bool> oFunc = (string x) => { return x.Contains("_"); }; lstRes = lstTest.Where(oFunc);
究極進化(完完全全的Lamada)
Func<string, bool> oFunc = x => x.Contains("_"); lstRes = lstTest.Where(oFunc);
有沒有很強大,是不是一樣一樣的。其實這樣一看lamada就是匿名委托的縮略形式。x => x.Contains("_")表達式左邊的表示Func里面的string類型變量,x.Contains("_")表示bool類型的返回值。有了這份進化史,程序員再也不用擔心面試官問我Lamada怎么回事了。
二、動態Lamada
與其叫動態Lamada,更加嚴謹一點應該叫動態Expression,因為拼接Lamada表達式用的基本都是Expression的類和方法。博主習慣,暫且就叫它動態Lamada吧。廢話不多說,直接吃點栗子吧。
public class DTO_ORDER { public string TO_ORDER_ID { get; set; } public string ORDER_NO { get; set; } public string ORDER_NAME { get; set; } public int ORDER_STATUS {get;set;} }
static void Main() { //1.定義lamada的參數,型如我們常寫的“x=>” ParameterExpression m_Parameter = Expression.Parameter(typeof(DTO_ORDER), "x"); //2.定義要使用lamada的屬性成員(比如我們這里要對DTO_ORDER對象的ORDER_NO屬性做篩選) MemberExpression member = Expression.PropertyOrField(m_Parameter, "ORDER_NO"); //3.定義篩選的操作(是大於、等於、小於、like等) Expression expRes = Expression.Equal(member, Expression.Constant("aaaa", member.Type)); //4.將表達式轉換為Lamada的表達式 Expression<Func<DTO_ORDER, bool>> exprelamada = Expression.Lambda<Func<DTO_ORDER, bool>>(expRes, m_Parameter); var lstRes = new List<DTO_ORDER>(); for (var i = 0; i < 10; i++) { var oModel = new DTO_ORDER(); oModel.ORDER_NO = i % 2 == 0 ? "aaaa" : "bbbb"; lstRes.Add(oModel); } //5.將Expression表達式轉換為Func委托,用於Where里面的參數 var lamada = exprelamada.Compile(); lstRes = lstRes.Where(lamada).ToList(); }
以上就構造了一個查詢List<DTO_ORDER>對象里面ORDER_NO 屬性等於aaaa的lamada表達式。我們看看運行效果截圖:
是不是已經得到了我們想要的表達式!有沒有很簡單。。。
三、動態Lamada的使用
看到這里有人就郁悶了,為了得到x=>x.ORDER_NO=="aaaa"這種表達式,你繞了這么大一圈,有什么屌用?直接lstRes=lstRes.Where(x=>x.ORDER_NO=="aaaa");就能夠搞定的事,你非要把簡單問題復雜化。其實不然,有一定編程經驗的朋友肯定知道,一般我們前端傳過來的查詢參數肯定不會只有一個,當需要查詢多個參數時需要我們構造一個統一的Lamada傳遞到后台;當然你也可以說,我將多個參數全部傳遞到后台,然后再后台使用IQueryable接口去過濾。當然,這確實可行,但是別忘了我們封裝Find(Expression exp...)的意義,不就是為了簡化方法么,從這點來說,構造動態Lamada非常必要。
1、通用Lamada表達式類
博主封裝了一個簡單操作(大於、等於、小於、like等)的動態Lamada類。
public class LamadaExtention<Dto> where Dto:new () { private List<Expression> m_lstExpression = null; private ParameterExpression m_Parameter = null; public LamadaExtention() { m_lstExpression = new List<Expression>(); m_Parameter = Expression.Parameter(typeof(Dto), "x"); }
//構造表達式,存放到m_lstExpression集合里面 public void GetExpression(string strPropertyName, object strValue, ExpressionType expressType) { Expression expRes = null; MemberExpression member = Expression.PropertyOrField(m_Parameter, strPropertyName); if (expressType == ExpressionType.Contains) { expRes = Expression.Call(member, typeof(string).GetMethod("Contains"), Expression.Constant(strValue)); } else if (expressType == ExpressionType.Equal) { expRes = Expression.Equal(member, Expression.Constant(strValue, member.Type)); } else if (expressType == ExpressionType.LessThan) { expRes = Expression.LessThan(member, Expression.Constant(strValue, member.Type)); } else if (expressType == ExpressionType.LessThanOrEqual) { expRes = Expression.LessThanOrEqual(member, Expression.Constant(strValue, member.Type)); } else if (expressType == ExpressionType.GreaterThan) { expRes = Expression.GreaterThan(member, Expression.Constant(strValue, member.Type)); } else if (expressType == ExpressionType.GreaterThanOrEqual) { expRes = Expression.GreaterThanOrEqual(member, Expression.Constant(strValue, member.Type)); } //return expRes; m_lstExpression.Add(expRes); } //針對Or條件的表達式 public void GetExpression(string strPropertyName, List<object> lstValue) { Expression expRes = null; MemberExpression member = Expression.PropertyOrField(m_Parameter, strPropertyName); foreach (var oValue in lstValue) { if (expRes == null) { expRes = Expression.Equal(member, Expression.Constant(oValue, member.Type)); } else { expRes = Expression.Or(expRes, Expression.Equal(member, Expression.Constant(oValue, member.Type))); } } m_lstExpression.Add(expRes); }
//得到Lamada表達式的Expression對象 public Expression<Func<Dto, bool>> GetLambda() { Expression whereExpr = null; foreach (var expr in this.m_lstExpression) { if (whereExpr == null) whereExpr = expr; else whereExpr = Expression.And(whereExpr, expr); } if (whereExpr == null) return null; return Expression.Lambda<Func<Dto, Boolean>>(whereExpr, m_Parameter); } }
//用於區分操作的枚舉 public enum ExpressionType { Contains,//like Equal,//等於 LessThan,//小於 LessThanOrEqual,//小於等於 GreaterThan,//大於 GreaterThanOrEqual//大於等於 }
2、使用場景
博主項目中有某一個頁面,查詢條件非常多,需要傳遞到后台很多參數。先來看看頁面:
來看后台web api代碼
public object Get(int limit, int offset, string strBodyno, string strVin, string strOrderno, string strEngincode, string strOrderstatus, string strTranscode, string strVms, string strCarcode, string strImportStartdate, string strImportEnddate, string strSendStartdate, string strSendEnddate) { //1.定義對象,傳入泛型 var oLamadaExtention = new LamadaExtention<DTO_TO_ORDER>(); //2.依次構造Lamada表達式 if (!string.IsNullOrEmpty(strBodyno)) { oLamadaExtention.GetExpression("BODY_NO", strBodyno, ExpressionType.Contains); } if (!string.IsNullOrEmpty(strVin)) { oLamadaExtention.GetExpression("VIN", strVin, ExpressionType.Contains); } if (!string.IsNullOrEmpty(strOrderno)) { oLamadaExtention.GetExpression("ORDER_NO", strOrderno, ExpressionType.Contains); } if (!string.IsNullOrEmpty(strEngincode)) { oLamadaExtention.GetExpression("ENGIN_CODE", strEngincode, ExpressionType.Contains); } if (!string.IsNullOrEmpty(strOrderstatus)) { if (strOrderstatus.Contains(",")) { var lstValue = strOrderstatus.Split(",".ToCharArray(), StringSplitOptions.RemoveEmptyEntries).ToList(); var lstObj = new List<object>(); lstValue.ForEach(x => { lstObj.Add(Convert.ToInt16(x)); }); oLamadaExtention.GetExpression("ORDER_STATUS", lstObj); } else { oLamadaExtention.GetExpression("ORDER_STATUS", Convert.ToInt16(strOrderstatus), ExpressionType.Equal); } } if (!string.IsNullOrEmpty(strTranscode)) { oLamadaExtention.GetExpression("TRANS_CODE", strTranscode, ExpressionType.Contains); } if (!string.IsNullOrEmpty(strVms)) { oLamadaExtention.GetExpression("VMS_NO", strVms, ExpressionType.Contains); } if (!string.IsNullOrEmpty(strCarcode)) { oLamadaExtention.GetExpression("TM_MODEL_MATERIAL_ID", strCarcode, ExpressionType.Contains); } if (!string.IsNullOrEmpty(strImportStartdate)) { oLamadaExtention.GetExpression("CREATE_DATE", Convert.ToDateTime(strImportStartdate), ExpressionType.GreaterThanOrEqual); } if (!string.IsNullOrEmpty(strImportEnddate)) { oLamadaExtention.GetExpression("CREATE_DATE", Convert.ToDateTime(strImportEnddate), ExpressionType.LessThanOrEqual); } if (!string.IsNullOrEmpty(strSendStartdate)) { oLamadaExtention.GetExpression("OFFLINE_DATE_ACT", Convert.ToDateTime(strSendStartdate), ExpressionType.GreaterThanOrEqual); } if (!string.IsNullOrEmpty(strSendEnddate)) { oLamadaExtention.GetExpression("OFFLINE_DATE_ACT", Convert.ToDateTime(strSendEnddate), ExpressionType.LessThanOrEqual); } //3.得到需要的Lamada表達式Expression var lamada = oLamadaExtention.GetLambda(); var lstRes = orderManager.Find(lamada).ToList(); //4.得到Bootstrap Table需要的對象 var oRes = new PageRowData(); return oRes; ; }
倉儲基類里面的find方法:
public virtual IQueryable<TEntity> Find(Expression<Func<TEntity, bool>> express) { Func<TEntity, bool> lamada = express.Compile(); return UnitOfWork.context.Set<TEntity>().Where(lamada).AsQueryable<TEntity>(); }
四、小結
至此,所謂的動態Lamada就完了。如果你之前用過,請一笑而過;當然如果你沒用過,學習點新東西也是好的。請不要嘲笑博主亂下定義,叫動態Lamada挺好的呢。當然你可以叫動態Expression,動態Linq都行,不管叫什么,正確使用才是王道。