网址:http://www.cnblogs.com/skychen1218/p/6595759.html

前言

  最近花了点时间玩了下MongoDB.Driver,进行封装了工具库,平常也会经常用到MongoDB,因此写一篇文章梳理知识同时把自己的成果分享给大家。

  本篇会设计到Lambda表达式的解析,有兴趣的同学也看看我之前写的《表达式树的解析》。

  文章最后会给出源码下载地址。

MongoDB简介

  MongoDB是一个基于分布式文件存储的非关系型数据库,相比于其他NoSql它支持复杂的查询。

  文本是类似JSON的BSON格式,BSON是在JSON的基础上进化:更快的遍历、操作更简易、更多的数据类型。因此MongoDB可以存储比较复杂的数据类型,同样也支持建立索引。

  MongoDB的概念有:

  • DataBase(库)
  • Collections(集合),类似于关系型数据库的表
  • Document(文档),类似于关系型数据库的一条数据

  

MongoDB优缺点

  • 优点

  1. 高效性,内置GridFS,从而达到海量数据存储,并且满足大数据集的快速范围查询。
  2. 高扩展性,分片使MongoDB的有更高的吞吐量,复制使MongoDB更高的可用性。
  3. BSON文档,易于理解、查看,
  4. 免费
  • 缺点

  1. 不支持事务
  2. 不支持表关联
  3. 不耗CPU却耗内存
  4. 没有成熟的管理工具

MongoDB使用场景

  拥有高效的存储的特点,让MongoDB用在操作日志记录是非常流行的做法。

  随着版本的升级提供更加强大的功能,产品逐渐成熟用在主业务也很多,例如电商行业的订单系统与包裹跟踪模块,海量的主订单与订单明细,包裹的状态变更信息。

  然而因为BSON文档的存储方式,使平常的开发的思维模式有所变更。举个栗子,传统用关系型数据库,订单模块就会分主订单表和订单明细表,创建订单就会用事务同时添加两表的数据,查找订单也会通过两表关联查询出来。但是使用MongoDB,主订单表与其明细,将会以一个完整的对象保存为文档。

  也因为不支持事务、表关联的原因,它更加适合用作于一个完整的业务模块。

  部分朋友会带着一个问题,非关系型数据库和关系型数据库哪个更好。我认为,谁都无法代替谁,一般情况下,非关系型数据库更多的作为关系型数据库扩展,用好了效果甚佳,滥用了只会寸步难行。

  

MongoDB安装

  本来想写的,相应的文章在园子太多了,借用一位仁兄的博文,传送门

  MongoDB下载地址:https://www.mongodb.com/download-center#community

  管理工具:Robomongo,传送门

MongoDB.Driver的使用

  

  创建一个控制台,到Nuget下载MongoDB.Driver。写入以下代码:

using System;
using FrameWork.MongoDB.MongoDbConfig;
using MongoDB.Bson.Serialization.Attributes;
using MongoDB.Driver;

namespace FrameWork.MongoDb.Demo
{
class Program
{
static void Main(string[] args)
{
var database = "testdatabase";
var collection = "TestMongo";
var db = new MongoClient("您的地址").GetDatabase(database);
var coll = db.GetCollection<TestMongo>(collection);

var entity = new TestMongo
{
Name = "SkyChen",
Amount = 100,
CreateDateTime = DateTime.Now
};

coll.InsertOneAsync(entity).ConfigureAwait(false);

}
}

public class TestMongo : MongoEntity
{

[BsonDateTimeOptions(Kind = DateTimeKind.Local)]
public DateTime CreateDateTime { get; set; }

public decimal Amount { get; set; }

public string Name { get; set; }

}
}

  第一个demo:添加数据就完成了。F12可以看到IMongoCollection这个接口,增删改查都有,注意分One和Many。基础的使用就不扯过多,在文章尾部的代码已经提供增删改查的封装。

  增删查的封装相对简单,但是MongoDB.Driver提供的update的稍微比较特殊。通过Builders<T>.Update.Set(_fieldname, value)更新指定字段名,有多个字段名需要修改,就要通过new UpdateDefinitionBuilder<T>().Combine(updateDefinitionList)去完成

  然而,这种方式并不适用于我们实际开发,因此需要对Update方法进行 实体更新封装Lambda更新封装

实体更新封装

  通过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。

  

结束

  不知道有多少朋友直接拖到文章尾部直接下载源码的。。。。。。

  如果对您有用,麻烦您推荐一下。

  此外还要感谢非非大哥哥,率先做了我的小白鼠给我提出了可贵的BUG,不然我还真不敢放出源码。

  如果有什么问题和建议,可以在下方评论,我会及时回复。

  双手奉上源码:https://github.com/SkyChenSky/Framework.MongoDB.git

转载于:https://www.cnblogs.com/zxtceq/p/7692189.html

.net平台的MongoDB使用相关推荐

  1. 三.Linux平台安装MongoDB

    Linux平台安装MongoDB 1.配置方法 安装(使用yum源安装) 在/etc/yum.repos.d 创建一个mongodb-org.repo源文件 vi /etc/yum.repos.d/m ...

  2. centos mysql mongodb_Linux 平台安装 MongoDB | 菜鸟教程

    Linux平台安装MongoDB MongoDB 提供了 linux 各个发行版本 64 位的安装包,你可以在官网下载安装包. 安装前我们需要安装各个 Linux 平台依赖包. Red Hat/Cen ...

  3. Mac OSX 平台安装 MongoDB

    Mac OSX 平台安装 MongoDB MongoDB 提供了 OSX 平台上 64 位的安装包,你可以在官网下载安装包. 下载地址:https://www.mongodb.com/download ...

  4. Windows 平台安装 MongoDB数据库(检测是否安装成功、启动和关闭MongoDB数据库)

    参考: 菜鸟教程: Windows 平台安装 MongoDB | 菜鸟教程 windows平台mongoDB安装配置 - ymwangel - 博客园 1.安装: 去官网下载MongoDB Commu ...

  5. Linux平台安装MongoDB(亲测)

    MongoDB 提供了 linux 各个发行版本 64 位的安装包,你可以在官网下载安装包. 安装前我们需要安装各个 Linux 平台依赖包. Red Hat/CentOS: sudo yum ins ...

  6. 在linux平台搭建mongodb环境

    2019独角兽企业重金招聘Python工程师标准>>> 1. 下载安装包,解压 根据linux版本,下载mongodb版本,我是用的ubuntu16.04.地址:https://ww ...

  7. Linux平台安装MongoDB

    MongoDB 提供了 linux 各发行版本 64 位的安装包,你可以在官网下载安装包. 下载地址:https://www.mongodb.com/download-center#community ...

  8. Windows 平台安装 MongoDB教程

    1.MongoDB下载 链接: https://pan.baidu.com/s/1dRZw748Lui_r9kE5aYwZFA 密码: 9yj6 此文件只适用于64位系统安装,32位系统请自己上官网下 ...

  9. Windows 平台安装 MongoDB

    MongoDB 下载 MongoDB 提供了可用于 32 位和 64 位系统的预编译二进制包,你可以从MongoDB官网下载安装,MongoDB 预编译二进制包下载地址:https://www.mon ...

最新文章

  1. Linux不停往外发包
  2. rsatool使用步骤图解_图解360系统重装大师如何使用
  3. catch的执行与try的匹配
  4. js 的 math 函数
  5. php并发取源码,PHP读取大文件源码示例-Swoole多进程读取大文件
  6. Linux 系统检测工具
  7. Xen虚拟机两大迁移方法详解
  8. 华为ac控制器web配置手册_欧姆龙AC伺服系统1S系列产品型号说明及功能介绍
  9. 海洋CMS仿RiPro主题风格自适应模板
  10. 深度学习行人重识别综述与展望
  11. MySQL多实例配置
  12. 开发基础框架:mybatis-3.2.8 +hibernate4.0+spring3.0+struts2.3
  13. Java Java Java
  14. oracle数据泵能增量吗,Oracle12c中数据泵新特性之功能增强(expdp, impdp)
  15. 直线平面平行的判定和性质
  16. 多元统计分析——多元线性回归
  17. gcc怎么编写c语言程序,gcc如何编译C语言程序
  18. java验证码短信发送_java发送短信验证码
  19. 中南民大计算机学院复试细则,中南民族大学各学院2019年考研复试细则
  20. python导入gif_Scratch3.0直接导入gif动画

热门文章

  1. AutoMapper的介绍与使用(二)
  2. 分析不同类型页面渲染过程
  3. poj 2507Crossed ladders 计算几何
  4. iOS Application Security
  5. 【转载】Windwos CE 跨进程内存注入
  6. vs2005 创建解决方案不显示,解决方案管理器中看不到解决方案节点的解决办法...
  7. Leetcode PHP题解--D47 868. Binary Gap
  8. 3.19PMP试题每日一题
  9. Silverlight:Downloader的使用(event篇)
  10. selenium+ python自动化--断言assertpy