之前都是看別人寫博客,自己沒有寫博客的習慣.在工作的過程中,總是會碰到許多的技術問題.有很多時候想記錄下來,後面一直有許多的問題等著解決.總想著等系統完成了,再回頭總結下.往往結果就把這事拋到腦後了. 總覺得不能一直這樣哈.今天簡單記一下吧.有表達不清楚的地方,多多包涵. 最近在研究.net orm ...
之前都是看別人寫博客,自己沒有寫博客的習慣.在工作的過程中,總是會碰到許多的技術問題.有很多時候想記錄下來,後面一直有許多的問題等著解決.總想著等系統完成了,再回頭總結下.往往結果就把這事拋到腦後了.
總覺得不能一直這樣哈.今天簡單記一下吧.有表達不清楚的地方,多多包涵.
最近在研究.net orm框架.想開發一套更好用的Orm框架.別嫌輪子多.碰到一個Expression合併的問題.
一.需求情況描述
需要更新部分數據的時候,可能前端傳回的只有部分欄位的數據.而更新的時候,需要設置更新人,更新日期等.
舉個慄子來說:
現在有一個預約信息表
前端需要修改數據內容如下,我們暫且叫表達A
var exp = ExpressionHelper.CreateExpression<AppointmentDto>(a => new {
a.Id,
a.PatientName,
a.PatientNamePy,
a.IdentityCardNumber,
a.Birthday,
a.PatientAge,
a.PatientSex,
a.PatientPhone,
a.Address
});
而寫入資料庫的時候需要添加更新人,更新時間.LastUpdateUserId和UpdateTime.
於是我們便又多了一個lambda表達式,我們叫它表達式B
var exp = ExpressionHelper.CreateExpression<AppointmentDto>(a => new {
a.Id,
a.PatientName,
a.PatientNamePy,
a.IdentityCardNumber,
a.Birthday,
a.PatientAge,
a.PatientSex,
a.PatientPhone,
a.Address,
a.LastUpdateUserId,
a.UpdateTime
});
這裡說下ExpressionHelper.CreateExpression<T>方法,只是一個為了縮減代碼長度而寫的方法.輸入的lambda表達式原樣返回了.
外面不用寫好長的類型了.Expression這個類型平時不用.寫外面看著眼暈. Expression<Func<AppointmentDto, object>> exp1 = a => new {a.Id,a.PatientName};
/// <summary>
/// 轉換Expr
/// 在外面調用時可以使用var以減少代碼長度
/// </summary>
/// <param name="expr"></param>
/// <returns></returns>
public static Expression<Func<T, object>> CreateExpression<T>(Expression<Func<T, object>> expr)
{
return expr;
}
所以此處,使用var可以看起來更整潔.但並不推薦在正常情況下使用var.
個人覺得使用var讓代碼可維護性降低.讀起來真的是頭疼.之前在維護一個比較大的系統的時候,公司的主要項目,缺少項目文檔,代碼裡面也基本上沒啥註釋.而且又清一色的var,每個方法返回的是啥類型?你得去方法那邊看去.看著真是惱火,又不得不去一點一點的改.都改成相應的類型後,看著就清爽多了.看一眼,流程就基本上能明白大概.所以,var在C#這種強類型語言里,能不用就別用了.
上面就當是發牢騷了.我們回到正題.
我們看到表達式B比表達式A只多了兩個欄位.大多數代碼都是重覆的.而且,兩個lambda表達式嚴重的加長了代碼行數.幾個這樣的表達式下來,這個類就到了幾百行了.
對於喜歡簡潔,簡單的我來說,類一大了我就頭疼.那咋整?要是有辦法將這兩個表達式簡化處理一下就好了.將表達式A加上一個短的表達式,來實現表達式B呢.
比如實現 var exprB = exprA.Add(a => new { a.PatientPhone });
So,開始捯飭...
二.解決方法
因為這個合併表達式的方法是在個人系統內部使用滿足我定製的Orm的類名稱需求
所以定義了一個新的Expression表達式類型NewObjectExpression來處理
1 /// <summary> 2 /// New Object Expression 3 /// 合併NewExpression使用. 4 /// </summary> 5 public class NewObjectExpression : Expression, IArgumentProvider 6 { 7 private IList<Expression> arguments; 8 9 /// <summary> 10 /// 構造方法 11 /// </summary> 12 /// <param name="constructor"></param> 13 /// <param name="arguments"></param> 14 /// <param name="members"></param> 15 internal NewObjectExpression(ConstructorInfo constructor, IList<Expression> arguments, List<MemberInfo> members) 16 { 17 this.Constructor = constructor; 18 this.arguments = arguments; 19 this.Members = members; 20 21 if (members != null) 22 { 23 List<string> nameList = members.Select(member => member.Name).ToList(); 24 for (int i = 0; i < nameList.Count; i++) 25 { 26 if (!string.IsNullOrEmpty(ExpressionString)) 27 { 28 ExpressionString += "," + nameList[i]; 29 } 30 else 31 { 32 ExpressionString = nameList[i]; 33 } 34 } 35 } 36 } 37 38 /// <summary> 39 /// Gets the static type of the expression that this <see cref="Expression" /> represents. (Inherited from <see cref="Expression"/>.) 40 /// </summary> 41 /// <returns>The <see cref="Type"/> that represents the static type of the expression.</returns> 42 public override Type Type 43 { 44 get { return Constructor.DeclaringType; } 45 } 46 47 /// <summary> 48 /// Returns the node type of this <see cref="Expression" />. (Inherited from <see cref="Expression" />.) 49 /// </summary> 50 /// <returns>The <see cref="ExpressionType"/> that represents this expression.</returns> 51 public sealed override ExpressionType NodeType 52 { 53 get { return ExpressionType.New; } 54 } 55 56 /// <summary> 57 /// Gets the called constructor. 58 /// </summary> 59 public ConstructorInfo Constructor { get; } 60 61 /// <summary> 62 /// Gets the arguments to the constructor. 63 /// </summary> 64 public ReadOnlyCollection<Expression> Arguments 65 { 66 get { return (ReadOnlyCollection<Expression>)arguments; } 67 } 68 69 Expression IArgumentProvider.GetArgument(int index) 70 { 71 return arguments[index]; 72 } 73 74 int IArgumentProvider.ArgumentCount 75 { 76 get 77 { 78 return arguments.Count; 79 } 80 } 81 82 /// <summary> 83 /// ExpressionString 84 /// </summary> 85 public string ExpressionString { get; private set; } = ""; 86 87 public ConstructorInfo Constructor1 => Constructor; 88 89 public List<MemberInfo> Members { get; set; } 90 91 /// <summary> 92 /// 更新members 93 /// </summary> 94 /// <param name="arguments"></param> 95 /// <param name="members"></param> 96 /// <returns></returns> 97 public NewObjectExpression Update(IList<Expression> arguments, List<MemberInfo> members) 98 { 99 if (arguments != null) 100 { 101 this.arguments = arguments; 102 } 103 if (Members != null) 104 { 105 this.Members = members; 106 ExpressionString = ""; 107 List<string> nameList = members.Select(member => member.Name).ToList(); 108 for (int i = 0; i < nameList.Count; i++) 109 { 110 if (!string.IsNullOrEmpty(ExpressionString)) 111 { 112 ExpressionString += "," + nameList[i]; 113 } 114 else 115 { 116 ExpressionString = nameList[i]; 117 } 118 } 119 } 120 return this; 121 } 122 }View Code
待處理的屬性都放到了Members裡面.後面解析使用的也是Members.其它方法Copy自NewExpression的源碼,可以刪了不用.
下麵我們來擴展Expression<Func<T, object>>,讓Expression<Func<T, object>>擁有Add和Remove屬性的方法.
直接上代碼,看前兩個方法.後面兩個方法是擴展Expression<Func<T, bool>>表達式的And和Or.等有回頭有空再介紹.
1 /// <summary> 2 /// Expression 擴展 3 /// </summary> 4 public static class ExpressionExpand 5 { 6 /// <summary> 7 /// Expression And 8 /// NewExpression 合併 9 /// </summary> 10 /// <param name="expr"></param> 11 /// <returns></returns> 12 public static Expression<Func<T, object>> Add<T>(this Expression<Func<T, object>> expr, Expression<Func<T, object>> expandExpr) 13 { 14 Expression<Func<T, object>> result = null; 15 ParameterExpression parameter = Expression.Parameter(typeof(T), "p"); 16 List<MemberInfo> memberInfoList = new List<MemberInfo>(); 17 #region 處理原expr 18 if (expr.Body is NewExpression) 19 { // t=>new{t.Id,t.Name} 20 NewExpression newExp = expr.Body as NewExpression; 21 if (newExp.Members != null) 22 { 23 memberInfoList = newExp.Members.ToList(); 24 } 25 } 26 else if (expr.Body is NewObjectExpression) 27 { 28 NewObjectExpression newExp = expr.Body as NewObjectExpression; 29 if (newExp.Members != null) 30 { 31 memberInfoList = newExp.Members.ToList(); 32 } 33 } 34 else if (expr.Body is UnaryExpression) 35 { //t=>t.Id 36 UnaryExpression unaryExpression = expr.Body as UnaryExpression; 37 MemberExpression memberExp = unaryExpression.Operand as MemberExpression; 38 memberInfoList.Add(memberExp.Member); 39 } 40 #endregion 41 42 #region 處理擴展expr 43 if (expandExpr.Body is NewExpression) 44 { // t=>new{t.Id,t.Name} 45 NewExpression newExp = expandExpr.Body as NewExpression; 46 for (int i = 0; i < newExp.Members.Count; i++) 47 { 48 MemberExpression memberExp = Expression.Property(parameter, newExp.Members[i].Name); 49 if (!memberInfoList.Any(member => member.Name == newExp.Members[i].Name)) 50 { 51 memberInfoList.Add(newExp.Members[i]); 52 } 53 } 54 } 55 else if (expr.Body is NewObjectExpression) 56 { 57 NewObjectExpression newExp = expr.Body as NewObjectExpression; 58 if (newExp.Members != null && newExp.Members.Count > 0) 59 { 60 for (int i = 0; i < newExp.Members.Count; i++) 61 { 62 MemberExpression memberExp = Expression.Property(parameter, newExp.Members[i].Name); 63 if (!memberInfoList.Any(member => member.Name == newExp.Members[i].Name)) 64 { 65 memberInfoList.Add(newExp.Members[i]); 66 } 67 } 68 } 69 } 70 else if (expandExpr.Body is UnaryExpression) 71 { //t=>t.Id 72 UnaryExpression unaryExpression = expandExpr.Body as UnaryExpression; 73 MemberExpression memberExp = unaryExpression.Operand as MemberExpression; 74 if (!memberInfoList.Any(exp => exp.Name == memberExp.Member.Name)) 75 { 76 memberInfoList.Add(memberExp.Member); 77 } 78 } 79 #endregion 80 NewObjectExpression newObjExpression = new NewObjectExpression(typeof(object).GetConstructors()[0], null, memberInfoList); 81 result = Expression.Lambda<Func<T, object>>(newObjExpression, parameter); 82 return result; 83 } 84 85 /// <summary> 86 /// Expression Remove 87 /// NewExpression 合併 88 /// </summary> 89 /// <param name="expr"></param> 90 /// <returns></returns> 91 public static Expression<Func<T, object>> Remove<T>(this Expression<Func<T, object>> expr, Expression<Func<T, object>> expandExpr) 92 { 93 Expression<Func<T, object>> result = null; 94 ParameterExpression parameter = Expression.Parameter(typeof(T), "p"); 95 List<MemberInfo> memberInfoList = new List<MemberInfo>(); 96 List<MemberInfo> removeMemberInfoList = new List<MemberInfo>(); 97 #region 處理原expr 98 if (expr.Body is NewExpression) 99 { // t=>new{t.Id,t.Name} 100 NewExpression newExp = expr.Body as NewExpression; 101 if (newExp.Members != null) 102 { 103 memberInfoList = newExp.Members.ToList(); 104 } 105 } 106 else if (expr.Body is NewObjectExpression) 107 { 108 NewObjectExpression newExp = expr.Body as NewObjectExpression; 109 if (newExp.Members != null) 110 { 111 memberInfoList = newExp.Members.ToList(); 112 } 113 } 114 else if (expr.Body is UnaryExpression) 115 { //t=>t.Id 116 UnaryExpression unaryExpression = expr.Body as UnaryExpression; 117 MemberExpression memberExp = unaryExpression.Operand as MemberExpression; 118 memberInfoList.Add(memberExp.Member); 119 } 120 #endregion 121 122 #region 處理擴展expr 123 if (expandExpr.Body is NewExpression) 124 { // t=>new{t.Id,t.Name} 125 NewExpression newExp = expandExpr.Body as NewExpression; 126 for (int i = 0; i < newExp.Members.Count; i++) 127 { 128 MemberExpression memberExp = Expression.Property(parameter, newExp.Members[i].Name); 129 if (!removeMemberInfoList.Any(member => member.Name == newExp.Members[i].Name)) 130 { 131 removeMemberInfoList.Add(newExp.Members[i]); 132 } 133 } 134 } 135 else if (expr.Body is NewObjectExpression) 136 { 137 NewObjectExpression newExp = expr.Body as NewObjectExpression; 138 if (newExp.Members != null && newExp.Members.Count > 0) 139 { 140 for (int i = 0; i < newExp.Members.Count; i++) 141 { 142 MemberExpression memberExp = Expression.Property(parameter, newExp.Members[i].Name); 143 if (!removeMemberInfoList.Any(member => member.Name == newExp.Members[i].Name)) 144 { 145 removeMemberInfoList.Add(newExp.Members[i]); 146 } 147 } 148 } 149 } 150 else if (expandExpr.Body is UnaryExpression) 151 { //t=>t.Id 152 UnaryExpression unaryExpression = expandExpr.Body as UnaryExpression; 153 MemberExpression memberExp = unaryExpression.Operand as MemberExpression; 154 if (!memberInfoList.Any(exp => exp.Name == memberExp.Member.Name)) 155 { 156 removeMemberInfoList.Add(memberExp.Member); 157 } 158 } 159 #endregion 160 161 for (int i = memberInfoList.Count - 1; i >= 0; i--) 162 { 163 if (removeMemberInfoList.Any(member => member.Name == memberInfoList[i].Name)) 164 { 165 memberInfoList.Remove(memberInfoList[i]); 166 } 167 } 168 if (memberInfoList.Count <= 0) 169 { 170 throw new System.Exception("Expression Remove Error.All Properties are removed."); 171 } 172 NewObjectExpression newObjExpression = new NewObjectExpression(typeof(object).GetConstructors()[0], null, memberInfoList); 173 result = Expression.Lambda<Func<T, object>>(newObjExpression, parameter); 174 return result; 175 } 176 177 /// <summary> 178 /// Expression And 179 /// </summary> 180 /// <param name="expr"></param> 181 /// <returns></returns> 182 public static Expression<Func<T, bool>> And<T>(this Expression<Func<T, bool>> expr, Expression<Func<T, bool>> expandExpr) 183 { 184 Expression<Func<T, bool>> result = Expression.Lambda<Func<T, bool>>(Expression.And(expandExpr.Body, expr.Body), expr.Parameters); 185 return result; 186 } 187 188 /// <summary> 189 /// Expression And 190 /// </summary> 191 /// <param name="expr"></param> 192 /// <returns></returns> 193 public static Expression<Func<T, bool>> Or<T>(this Expression<Func<T, bool>> expr, Expression<Func<T, bool>> expandExpr) 194 { 195 Expression<Func<T, bool>> result = Expression.Lambda<Func<T, bool>>(Expression.Or(expandExpr.Body, expr.Body), expr.Parameters); 196 return result; 197 } 198 }View Code
Add方法可處理 NewExpression 類似 t=>new{t.Id,t.Name} , UnaryExpression 類似t=>t.Id,以及我們自定義的NewObjectExpression類型
所以我們在更新數據的時候就可以這麼寫了:
Dbc.Db.Update(dto, exp.Add(a => a.LastUpdateUserId));
Dbc.Db.Update(dto, exp.Add(a => new { a.LastUpdateUserId, a.UpdateTime }));
在Orm框架內部,解析NewObjectExpression時,解析方法如下
1 /// <summary> 2 /// 通過Lambed Expression獲取屬性名稱 3 /// </summary> 4 /// <param name="expr">查詢表達式</param> 5 /// <returns></returns> 6 public static List<string> GetPiList<T>(Expression<Func<T, object>> expr) 7 { 8 List<string> result = new List<string>(); 9 if (expr.Body is NewExpression) 10 { // t=>new{t.Id,t.Name} 11 NewExpression nexp = expr.Body as NewExpression; 12 if (nexp.Members != null) 13 { 14 result = nexp.Members.Select(member => member.Name).ToList(); 15 } 16 } 17 else if (expr.Body is NewObjectExpression) 18 { // t=>new{t.Id,t.Name} 19 NewObjectExpression nexp = expr.Body as NewObjectExpression; 20 if (nexp.Members != null) 21 { 22 result = nexp.Members.Select(member => member.Name).ToList(); 23 } 24 } 25 else if (expr.Body is UnaryExpression) 26 { //t=>t.Id 27 UnaryExpression uexp = expr.Body as UnaryExpression; 28 MemberExpression mexp = uexp.Operand as MemberExpression; 29 result.Add(mexp.Member.Name); 30 } 31 else 32 { 33 throw new System.Exception("不支持的Select lambda寫法"); 34 } 35 return result; 36 }View Code
至此,就完成了Expression<Func<T, object>>Add和Remove屬性的擴展,Orm可以讓代碼更簡潔.
三.後記
其實在使用新的類NewObjectExpression來解決之前,嘗試過其它的許多方式,因為使用.net的類型可以在其它的框架程式中借鑒引用.不必局限在個人框架內部.
NewExpression內部有一些校驗,本身Expression<Func<T, object>>是一個匿名類.試過處理NewExpression,以及新建類繼承自NewExpression等方式.都沒成功.
要是大家有更好的方法歡迎留言告知.希望本文能對大家有所幫助.