.net平台的MongoDB使用,.net平台mongodb
前言
最近花了点时间玩了下MongoDB.Driver,进行封装了工具库,平常也会经常用到MongoDB,因此写一篇文章梳理知识同时把自己的成果分享给大家。
本篇会设计到Lambda表达式的解析,有兴趣的同学也看看我之前写的《表达式树的解析》。
文章最后会给出源码下载地址。
MongoDB简介
MongoDB是一个基于分布式文件存储的非关系型数据库,相比于其他NoSql它支持复杂的查询。
文本是类似JSON的BSON格式,BSON是在JSON的基础上进化:更快的遍历、操作更简易、更多的数据类型。因此MongoDB可以存储比较复杂的数据类型,同样也支持建立索引。
MongoDB的概念有:
- DataBase(库)
- Collections(集合),类似于关系型数据库的表
- Document(文档),类似于关系型数据库的一条数据

MongoDB优缺点
MongoDB使用场景
拥有高效的存储的特点,让MongoDB用在操作日志记录是非常流行的做法。
随着版本的升级提供更加强大的功能,产品逐渐成熟用在主业务也很多,例如电商行业的订单系统与包裹跟踪模块,海量的主订单与订单明细,包裹的状态变更信息。
然而因为BSON文档的存储方式,使平常的开发的思维模式有所变更。举个栗子,传统用关系型数据库,订单模块就会分主订单表和订单明细表,创建订单就会用事务同时添加两表的数据,查找订单也会通过两表关联查询出来。但是使用MongoDB,主订单表与其明细,将会以一个完整的对象保存为文档。
也因为不支持事务、表关联的原因,它更加适合用作于一个完整的业务模块。
部分朋友会带着一个问题,非关系型数据库和关系型数据库哪个更好。我认为,谁都无法代替谁,一般情况下,非关系型数据库更多的作为关系型数据库扩展,用好了效果甚佳,滥用了只会寸步难行。

MongoDB安装
本来想写的,相应的文章在园子太多了,借用一位仁兄的博文,传送门
MongoDB下载地址:https://www.mongodb.com/download-center#community
管理工具:Robomongo,传送门
MongoDB.Driver的使用

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

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<TestMongo>
(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 }
View Code
第一个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;
}
}
View Code
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
View Code
表达式树的解析
对于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
http://www.htsjk.com/MongoDB/10357.html
www.htsjk.Com
true
http://www.htsjk.com/MongoDB/10357.html
NewsArticle
.net平台的MongoDB使用,.net平台mongodb 前言 最近花了点时间玩了下MongoDB.Driver,进行封装了工具库,平常也会经常用到MongoDB,因此写一篇文章梳理知识同时把自己的成果分享给大家。 本篇...
本站文章为和通数据库网友分享或者投稿,欢迎任何形式的转载,但请务必注明出处.
同时文章内容如有侵犯了您的权益,请联系QQ:970679559,我们会在尽快处理。