最近花了点时间玩了下 MongoDB.Driver,进行封装了工具库,平常也会经常用到 MongoDB,因此写一篇文章梳理知识同时把自己的成果分享给大家。
本篇会设计到 Lambda 表达式的解析,有兴趣的同学也看看我之前写的《》。
文章最后会给出源码下载地址。
MongoDB 是一个基于分布式文件存储的非关系型数据库,相比于其他 NoSql 它支持复杂的查询。
文本是类似 JSON 的 BSON 格式,BSON 是在 JSON 的基础上进化:更快的遍历、操作更简易、更多的数据类型。因此 MongoDB 可以存储比较复杂的数据类型,同样也支持建立索引。
MongoDB 的概念有:
拥有高效的存储的特点,让 MongoDB 用在操作日志记录是非常流行的做法。
随着版本的升级提供更加强大的功能,产品逐渐成熟用在主业务也很多,例如电商行业的订单系统与包裹跟踪模块,海量的主订单与订单明细,包裹的状态变更信息。
然而因为 BSON 文档的存储方式,使平常的开发的思维模式有所变更。举个栗子,传统用关系型数据库,订单模块就会分主订单表和订单明细表,创建订单就会用事务同时添加两表的数据,查找订单也会通过两表关联查询出来。但是使用 MongoDB,主订单表与其明细,将会以一个完整的对象保存为文档。
也因为不支持事务、表关联的原因,它更加适合用作于一个完整的业务模块。
本来想写的,相应的文章在园子太多了,借用一位仁兄的博文,
MongoDB 下载地址:
管理工具:Robomongo,
创建一个控制台,到 Nuget 下载 MongoDB.Driver。写入以下代码:
View Code
- 1 using System;
- 2 using FrameWork.MongoDB.MongoDbConfig;
- 3 using MongoDB.Bson.Serialization.Attributes;
- 4 using MongoDB.Driver;
- 5 6 namespace FrameWork.MongoDb.Demo 7 {
- 8 class Program 9 {
- 10 static void Main(string[] args) 11 {
- 12
- var database = "testdatabase";
- 13
- var collection = "TestMongo";
- 14
- var db = new MongoClient("您的地址").GetDatabase(database);
- 15
- var coll = db.GetCollection(collection);
- 16 17
- var entity = new TestMongo 18 {
- 19 Name = "SkyChen",
- 20 Amount = 100,
- 21 CreateDateTime = DateTime.Now 22
- };
- 23 24 coll.InsertOneAsync(entity).ConfigureAwait(false);
- 25 26
- }
- 27
- }
- 28 29 public class TestMongo: MongoEntity 30 {
- 31 32[BsonDateTimeOptions(Kind = DateTimeKind.Local)] 33 public DateTime CreateDateTime {
- get;
- set;
- }
- 34 35 public decimal Amount {
- get;
- set;
- }
- 36 37 public string Name {
- get;
- set;
- }
- 38 39
- }
- 40
- }
第一个 demo: 添加数据就完成了。F12 可以看到 IMongoCollection 这个接口,增删改查都有,注意分 One 和 Many。基础的使用就不扯过多,在文章尾部的代码已经提供增删改查的封装。
增删查的封装相对简单,但是 MongoDB.Driver 提供的 update 的稍微比较特殊。通过 Builders<T>.Update.Set(_fieldname, value) 更新指定字段名,有多个字段名需要修改,就要通过 new UpdateDefinitionBuilder<T>().Combine(updateDefinitionList) 去完成
然而,这种方式并不适用于我们实际开发,因此需要对 Update 方法进行 实体更新封装和 Lambda 更新封装。
通过 ID 作为过滤条件更新整个实体在实际工作中是常有的。既然通过 ID 作为条件,那么只能通过 UpdateOneAsync 进行约束更新一条数据。更新的字段可以通过反射实体对象进行遍历属性。下边是实现代码:
View Code
- /// <summary>
- /// mongodb扩展方法
- /// </summary>
- internal static class MongoDbExtension
- {
- /// <summary>
- /// 获取更新信息
- /// </summary>
- /// <typeparam name="T"></typeparam>
- /// <param name="entity"></param>
- /// <returns></returns>
- internal static UpdateDefinition GetUpdateDefinition(this T entity)
- {
- var properties = typeof(T).GetProperties(BindingFlags.Instance | BindingFlags.Public);
- var updateDefinitionList = GetUpdateDefinitionList(properties, entity);
- var updateDefinitionBuilder = new UpdateDefinitionBuilder().Combine(updateDefinitionList);
- return updateDefinitionBuilder;
- }
- /// <summary>
- /// 获取更新信息
- /// </summary>
- /// <typeparam name="T"></typeparam>
- /// <param name="propertyInfos"></param>
- /// <param name="entity"></param>
- /// <returns></returns>
- internal static List> GetUpdateDefinitionList(PropertyInfo[] propertyInfos, object entity)
- {
- var updateDefinitionList = new List>();
- 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.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.Update.Set(filedName, value));
- }
- }
- return updateDefinitionList;
- }
- }
曾经用过其他 ORM 都清楚 Lambda 表达式使用是非常频繁的,MongoDB.Driver 已经支持 Lambda 表达式的过滤条件,但没支持部分字段更新,因此由我们自己来写解析。下边是现实代码:
View Code
- #region Mongo更新字段表达式解析
- /// <summary>
- /// Mongo更新字段表达式解析
- /// </summary>
- /// <typeparam name="T"></typeparam>
- public class MongoDbExpression : ExpressionVisitor
- {
- #region 成员变量
- /// <summary>
- /// 更新列表
- /// </summary>
- internal List> UpdateDefinitionList = new List>();
- private string _fieldname;
- #endregion
- #region 获取更新列表
- /// <summary>
- /// 获取更新列表
- /// </summary>
- /// <param name="expression"></param>
- /// <returns></returns>
- public static List> GetUpdateDefinition(Expression> expression)
- {
- var mongoDb = new MongoDbExpression();
- mongoDb.Resolve(expression);
- return mongoDb.UpdateDefinitionList;
- }
- #endregion
- #region 解析表达式
- /// <summary>
- /// 解析表达式
- /// </summary>
- /// <param name="expression"></param>
- private void Resolve(Expression> 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.Lambdaobject>>(Expression.Convert(memberAssignment.Expression, typeof(object)));
- var value = lambda.Compile().Invoke();
- UpdateDefinitionList.Add(Builders.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 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.Update.Inc(_fieldname, realValue);
- }
- else if (node.Type == typeof(long))
- {
- var realValue = (long)value;
- if (node.NodeType == ExpressionType.Decrement)
- realValue = -realValue;
- updateDefinition = Builders.Update.Inc(_fieldname, realValue);
- }
- else if (node.Type == typeof(double))
- {
- var realValue = (double)value;
- if (node.NodeType == ExpressionType.Decrement)
- realValue = -realValue;
- updateDefinition = Builders.Update.Inc(_fieldname, realValue);
- }
- else if (node.Type == typeof(decimal))
- {
- var realValue = (decimal)value;
- if (node.NodeType == ExpressionType.Decrement)
- realValue = -realValue;
- updateDefinition = Builders.Update.Inc(_fieldname, realValue);
- }
- else if (node.Type == typeof(float))
- {
- var realValue = (float)value;
- if (node.NodeType == ExpressionType.Decrement)
- realValue = -realValue;
- updateDefinition = Builders.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>(node);
- var list = listLambda.Compile().Invoke();
- UpdateDefinitionList.Add(Builders.Update.Set(_fieldname, list));
- return node;
- }
- /// <summary>
- /// 访问集合表达式
- /// </summary>
- /// <param name="node"></param>
- /// <returns></returns>
- protected override Expression VisitListInit(ListInitExpression node)
- {
- var listLambda = Expression.Lambda>(node);
- var list = listLambda.Compile().Invoke();
- UpdateDefinitionList.Add(Builders.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.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>(node);
- var value = lambda.Compile().Invoke();
- UpdateDefinitionList.Add(Builders.Update.Set(_fieldname, value));
- }
- else
- {
- var lambda = Expression.Lambdaobject>>(Expression.Convert(node, typeof(object)));
- var value = lambda.Compile().Invoke();
- if (node.Type.IsEnum)
- value = (int)value;
- UpdateDefinitionList.Add(Builders.Update.Set(_fieldname, value));
- }
- return node;
- }
- #endregion
- }
- #endregion
对于 Lambda 表达式的封装,我侧重讲一下。假如有一段这样的更新代码:
- new MongoDbService().Update(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 方法,因此重写二元表达式进行封装。
不知道有多少朋友直接拖到文章尾部直接下载源码的。。。。。。
如果对您有用,麻烦您推荐一下。
此外还要感谢广州赛酷酷比科技物流组的杜小非同志,率先做了我的小白鼠给我提出了可贵的 BUG,不然我还真不敢放出源码。
如果有什么问题和建议,可以在下方评论,我会及时回复。
双手奉上源码:https://github.com/SkyChenSky/Framework.MongoDB.git
来源: http://www.cnblogs.com/skychen1218/p/6595759.html