实体更新封装
通过ID作为过滤条件更新整个实体在实际工作中是常有的。既然通过ID作为条件,那么只能通过UpdateOneAsync进行约束更新一条数据。更新的字段可以通过反射实体对象进行遍历属性。
下边是实现代码:
/// <summary> /// mongodb扩展方法 /// </summary> internal static class MongoDbExtension { /// <summary> /// 获取更新信息 /// </summary> /// <typeparam name="T"></typeparam> /// <param name="entity"></param> /// <returns></returns> internal static UpdateDefinition<T> GetUpdateDefinition<T>(this T entity) { var properties = typeof(T).GetProperties(BindingFlags.Instance | BindingFlags.Public); var updateDefinitionList = GetUpdateDefinitionList<T>(properties, entity); var updateDefinitionBuilder = new UpdateDefinitionBuilder<T>().Combine(updateDefinitionList); return updateDefinitionBuilder; } /// <summary> /// 获取更新信息 /// </summary> /// <typeparam name="T"></typeparam> /// <param name="propertyInfos"></param> /// <param name="entity"></param> /// <returns></returns> internal static List<UpdateDefinition<T>> GetUpdateDefinitionList<T>(PropertyInfo[] propertyInfos, object entity) { var updateDefinitionList = new List<UpdateDefinition<T>>(); propertyInfos = propertyInfos.Where(a => a.Name != "_id").ToArray(); foreach (var propertyInfo in propertyInfos) { if (propertyInfo.PropertyType.IsArray || typeof(IList).IsAssignableFrom(propertyInfo.PropertyType)) { var value = propertyInfo.GetValue(entity) as IList; var filedName = propertyInfo.Name; updateDefinitionList.Add(Builders<T>.Update.Set(filedName, value)); } else { var value = propertyInfo.GetValue(entity); if (propertyInfo.PropertyType == typeof(decimal)) value = value.ToString(); var filedName = propertyInfo.Name; updateDefinitionList.Add(Builders<T>.Update.Set(filedName, value)); } } return updateDefinitionList; } }
Lambda表达式更新封装
曾经用过其他ORM都清楚Lambda表达式使用是非常频繁的,MongoDB.Driver已经支持Lambda表达式的过滤条件,但没支持部分字段更新,因此由我们自己来写解析。
下边是现实代码:
#region Mongo更新字段表达式解析 /// <summary> /// Mongo更新字段表达式解析 /// </summary> /// <typeparam name="T"></typeparam> public class MongoDbExpression<T> : ExpressionVisitor { #region 成员变量 /// <summary> /// 更新列表 /// </summary> internal List<UpdateDefinition<T>> UpdateDefinitionList = new List<UpdateDefinition<T>>(); private string _fieldname; #endregion #region 获取更新列表 /// <summary> /// 获取更新列表 /// </summary> /// <param name="expression"></param> /// <returns></returns> public static List<UpdateDefinition<T>> GetUpdateDefinition(Expression<Func<T, T>> expression) { var mongoDb = new MongoDbExpression<T>(); mongoDb.Resolve(expression); return mongoDb.UpdateDefinitionList; } #endregion #region 解析表达式 /// <summary> /// 解析表达式 /// </summary> /// <param name="expression"></param> private void Resolve(Expression<Func<T, T>> expression) { Visit(expression); } #endregion #region 访问对象初始化表达式 /// <summary> /// 访问对象初始化表达式 /// </summary> /// <param name="node"></param> /// <returns></returns> protected override Expression VisitMemberInit(MemberInitExpression node) { var bingdings = node.Bindings; foreach (var item in bingdings) { var memberAssignment = (MemberAssignment)item; _fieldname = item.Member.Name; if (memberAssignment.Expression.NodeType == ExpressionType.MemberInit) { var lambda = Expression.Lambda<Func<object>>(Expression.Convert(memberAssignment.Expression, typeof(object))); var value = lambda.Compile().Invoke(); UpdateDefinitionList.Add(Builders<T>.Update.Set(_fieldname, value)); } else { Visit(memberAssignment.Expression); } } return node; } #endregion #region 访问二元表达式 /// <summary> /// 访问二元表达式 /// </summary> /// <param name="node"></param> /// <returns></returns> protected override Expression VisitBinary(BinaryExpression node) { UpdateDefinition<T> updateDefinition; var value = ((ConstantExpression)node.Right).Value; if (node.Type == typeof(int)) { var realValue = (int)value; if (node.NodeType == ExpressionType.Decrement) realValue = -realValue; updateDefinition = Builders<T>.Update.Inc(_fieldname, realValue); } else if (node.Type == typeof(long)) { var realValue = (long)value; if (node.NodeType == ExpressionType.Decrement) realValue = -realValue; updateDefinition = Builders<T>.Update.Inc(_fieldname, realValue); } else if (node.Type == typeof(double)) { var realValue = (double)value; if (node.NodeType == ExpressionType.Decrement) realValue = -realValue; updateDefinition = Builders<T>.Update.Inc(_fieldname, realValue); } else if (node.Type == typeof(decimal)) { var realValue = (decimal)value; if (node.NodeType == ExpressionType.Decrement) realValue = -realValue; updateDefinition = Builders<T>.Update.Inc(_fieldname, realValue); } else if (node.Type == typeof(float)) { var realValue = (float)value; if (node.NodeType == ExpressionType.Decrement) realValue = -realValue; updateDefinition = Builders<T>.Update.Inc(_fieldname, realValue); } else { throw new Exception(_fieldname + "不支持该类型操作"); } UpdateDefinitionList.Add(updateDefinition); return node; } #endregion #region 访问数组表达式 /// <summary> /// 访问数组表达式 /// </summary> /// <param name="node"></param> /// <returns></returns> protected override Expression VisitNewArray(NewArrayExpression node) { var listLambda = Expression.Lambda<Func<IList>>(node); var list = listLambda.Compile().Invoke(); UpdateDefinitionList.Add(Builders<T>.Update.Set(_fieldname, list)); return node; } /// <summary> /// 访问集合表达式 /// </summary> /// <param name="node"></param> /// <returns></returns> protected override Expression VisitListInit(ListInitExpression node) { var listLambda = Expression.Lambda<Func<IList>>(node); var list = listLambda.Compile().Invoke(); UpdateDefinitionList.Add(Builders<T>.Update.Set(_fieldname, list)); return node; } #endregion #region 访问常量表达式 /// <summary> /// 访问常量表达式 /// </summary> /// <param name="node"></param> /// <returns></returns> protected override Expression VisitConstant(ConstantExpression node) { var value = node.Type.IsEnum ? (int)node.Value : node.Value; UpdateDefinitionList.Add(Builders<T>.Update.Set(_fieldname, value)); return node; } #endregion #region 访问成员表达式 /// <summary> /// 访问成员表达式 /// </summary> /// <param name="node"></param> /// <returns></returns> protected override Expression VisitMember(MemberExpression node) { if (node.Type.GetInterfaces().Any(a => a.Name == "IList")) { var lambda = Expression.Lambda<Func<IList>>(node); var value = lambda.Compile().Invoke(); UpdateDefinitionList.Add(Builders<T>.Update.Set(_fieldname, value)); } else { var lambda = Expression.Lambda<Func<object>>(Expression.Convert(node, typeof(object))); var value = lambda.Compile().Invoke(); if (node.Type.IsEnum) value = (int)value; UpdateDefinitionList.Add(Builders<T>.Update.Set(_fieldname, value)); } return node; } #endregion } #endregion
表达式树的解析
对于Lambda表达式的封装,我侧重讲一下。假如有一段这样的更新代码:
new MongoDbService().Update<User>(a => a._id == "d99ce40d7a0b49768b74735b91f2aa75", a => new User { AddressList = new List<string> { "number1", "number2" }, Age = 10, BirthDateTime = DateTime.Now, Name = "skychen", NumList = new List<int> { 1211,23344 }, Sex = Sex.Woman, Son = new User { Name = "xiaochenpi", Age = 1 } });
那么,我们可以调试监视看看(下图),我们可以得出两个重要信息:
1.Expression<Func<T, T>>解析出来Body的NodeType是MemberInit
2.Bindings里有需要修改的字段信息。
再调试进去看看Bindings的第一项,我们又可以了解了几个重要信息。
1.Bindings里的元素是MemberAssignment类型。
2.Member能取到Name属性,也就是字段名
3.Expression属性,使用 Expression.Lambda,进行Compile().Invoke()就能得到我们需要的值。
fileName和Value都能取到了,那么更新自然能解决了。
上图是源码的部分核心代码,奇怪的是,我并没有在VisitMemberInit里进行遍历Bindings后进行Update.Set,而是将item的Expression属性再一次访问。那是因为我需要针对不同的数据类型进行处理。
例如:常量,我可以定义一个object value进行去接收,如果遇到枚举我需要强转成整型。
集合与数组,假如草率的使用object类型,object value = Expression.Lambda<Func<object>>(node).Compile().Invoke(),那么更新到MongoDB里就会有bug,奇怪的_t,_v就会出现。以此我需要定义为IList才能解决这个问题。
此外,工作中还会遇到金额或者数量自增的情况。Amount = a.Amount+9.9M,Count =a.Count-1。 MongoDB.Driver提供了Builders<T>.Update.Inc方法,因此重写二元表达式进行封装。
附加
经过测试,官方驱动2.4.3和2.4.4版本对类型IList支持有问题,如下图,所以现在封装版本最高支持到2.4.2。