目录
一、前言 1. 运行环境二、前期准备工作 1. 创建 MongoDBContext MongoDb操作上下文类2.创建测试类3.创建测试代码三、内嵌数组增加元素操作 1.Update.Set()方法 替换内嵌数组(不推荐使用)2.Update.Push()方法 直接将元素压入内嵌数组(推荐)3. Update.PushEach()方法 将多个元素压入内嵌数组(推荐)四、内嵌数组删除元素操作 1. Update.Set()方法 替换内嵌数组(不推荐使用)2. Update.Pull()方法 Pull删除一个元素(推荐)3. Update.PullFilter()方法 删除过滤器删除元素(推荐)五、内嵌数组修改元素操作 1. Update.Set() Set先查询后修改(不推荐)2. Update.Set() 配合过滤器修改(推荐)六、内嵌数组查找元素操作(Linq) 1. Linq查询一条记录(推荐)2. Linq查询分页 (推荐)七、总结笔者水平有限,如有错误还请批评指正!
.net版本
.Net Framwork 4.6.2 x64MongoDb数据库版本
MongoDb 3.6.2 x64驱动版本
MongoDb Driver 2.5笔者采用的MongoDB驱动是官方2.5版本驱动,大家可以通过下载GitHub源码自行编译,或者通过NuGet包管理工具安装。 GitHub地址:戳一戳查看、Link NuGet地址:戳一戳查看、Link
本例源码已上传至Gitee
本例源码地址:戳一戳、Link
假设当前是一个物联网数据采集项目,其中有很多个传感器节点(SensorNode),每个节点都会产生记录(Records),一条记录(Record)由数据值(Data)和记录时间(RecordDateTime)组成。要求对其Recods内嵌数组进行增删改查操作。
那么由上需求可以抽象出如下对象关系,在实际应用中,不建议将记录直接放在SensorNode下,因为Mongodb数据库对单个文档有16MB的大小限制。
/// <summary> /// 传感器节点 /// </summary> public class SensorNode { /// <summary> /// ID /// </summary> public ObjectId Id { get; set; } /// <summary> /// 记录数 /// </summary> public List<Record> Records { get; set; } } /// <summary> /// 数据记录 /// </summary> public class Record { /// <summary> /// 数据值 /// </summary> public double Data { get; set; } /// <summary> /// 记录时间 /// </summary> public DateTime RecorDateTime { get; set; } }测试类所对应的Bson结构,如下所示.
{ "_id" : ObjectId("5aa877e4aa25752ab4d4cae3"), "Records" : [ { "Data" : 1962163552.0, "RecorDateTime" : ISODate("2018-03-16T10:01:14.931Z") }, { "Data" : 1111405346.0, "RecorDateTime" : ISODate("2018-03-16T08:53:14.931Z") }, ...... ] }此方法主要使用操作符[$set],它的思路是先将内嵌文档的父元素查找出来,然后对子文档数组进行增删改操作,最后将子文档数组重新替换。此方式性能较低,除非涉及到大批量更改,否则不推荐使用。
#region 方法一. 查出sensor再更新Records, $set操作 - 随机插入100个元素(不推荐) // 查询需修改的sensorNode对象 var sensor = await _sensorNodes.Find(s => s.Id == _sensorNodeId).FirstOrDefaultAsync(); // 往Records里面增加随机元素 sensor.Records.AddRange(GetRandomRecord(100)); // 构建update Bson, 将原有Records替换为新的Records var update = Builders<SensorNode>.Update.Set(d => d.Records, sensor.Records); // 使用Update方法 await _sensorNodes.UpdateOneAsync(s => s.Id == _sensorNodeId, update); #endregion此方法主要使用操作符[$push]来增加元素到子文档数组,性能好,推荐使用。
// 构建update Bson ,添加一条记录使用Push方法 var updateOne = Builders<SensorNode>.Update.Push(d => d.Records, GetRandomRecord(1).First()); // 更新 await _sensorNodes.UpdateOneAsync(s => s.Id == _sensorNodeId, updateMany);此方法使用的也是[$push]操作符,只是在生成BsonDocument时允许接收数组。
#region 方法二. $push操作 - 随机插入100个元素(推荐) // 构建update Bson 添加多条记录使用PushEach方法 var updateMany = Builders<SensorNode>.Update.PushEach(d => d.Records, GetRandomRecord(100)); // 更新 await _sensorNodes.UpdateOneAsync(s => s.Id == _sensorNodeId, updateMany); #endregion此方式思路和增加元素的思路一致,性能较低,不推荐使用。
#region 方法一 . $set方式删除 - 删除前5个元素(不推荐) // 查询需修改的sensorNode对象 var sensor = await _sensorNodes.Find(s => s.Id == _sensorNodeId).FirstOrDefaultAsync(); // 删除Record for (int i = 0; i < 5; i++) { sensor.Records.RemoveAt(i); } // 构建update Bson, 将原有Records替换为新的Records var update = Builders<SensorNode>.Update.Set(d => d.Records, sensor.Records); // 使用Update方法 await _sensorNodes.UpdateOneAsync(s => s.Id == _sensorNodeId, update); #endregion此方式性能较低,不推荐。
#region 方法一. $set方式修改(将某个元素记录时间改为最小值) - 先查询后修改(不推荐) // 查询需修改的sensorNode对象 var sensor = await _sensorNodes.Find(s => s.Id == _sensorNodeId).FirstOrDefaultAsync(); // 修改Record值 sensor.Records[0].RecorDateTime = DateTime.MinValue; // 构建update Bson, 将原有Records替换为新的Records var update = Builders<SensorNode>.Update.Set(d => d.Records, sensor.Records); // 使用Update方法 await _sensorNodes.UpdateOneAsync(s => s.Id == _sensorNodeId, update); #endregion在数据库内部完成过滤和修改,性能高,推荐使用。
#region 方法二. $set方式修改(将Data = 1835821478.0的元素记录时间改为最大值) - 直接过滤修改(推荐) // 构造filter var filter = Builders<SensorNode>.Filter.Where(s => s.Id == _sensorNodeId) & Builders<SensorNode>.Filter.Where(d => d.Records.Any(r => r.Data == 1835821478.0)); // 执行更新 var update = Builders<SensorNode>.Update.Set(d => d.Records[-1].RecorDateTime, DateTime.MaxValue); await _sensorNodes.UpdateOneAsync(filter, update); #endregion需要先using MongoDB.Driver.Linq,否则Linq无法使用。
主要是SelectMany()方法的使用,此方法用于选择一个数组。
#region SelectMany查询内嵌数组(查询Data = 1340695206.0的第一条记录) - (推荐) // 转换为Queryable var result = await _sensorNodes.AsQueryable() // 查找对应的sensorNode .Where(s => s.Id == _sensorNodeId) // 选择Records内嵌数组 .SelectMany(s => s.Records) // 查找Data == 1340695206.0的元素 .Where(r => r.Data == 1340695206.0) // 取第一个 .FirstOrDefaultAsync(); #endregion主要区别使用SelectMany其它与EF Linq方式差不多
#region SelectMany、Skip、Take内嵌数组排序分页 - (推荐) // 页码 var index = 4; // 页面大小 var size = 10; // 转换为Queryable var page = await _sensorNodes.AsQueryable() // 查找对应的sensorNode .Where(s => s.Id == _sensorNodeId) // 选择Records内嵌数组 .SelectMany(s => s.Records) // 根据记录时间排序 .OrderBy(r => r.RecorDateTime) // 跳过 index - 1页数据 .Skip((index - 1) * size) // 选取一页数据 .Take(size) // 转换为集合 .ToListAsync(); #endregion 对内嵌数组的查询操作主要是通过聚合来实现,以上代码转换为Native方式后是以下命令。 aggregate( [ {"$match" : { "_id" : ObjectId("5aa877e4aa25752ab4d4cae3") } }, { "$unwind" : "$Records" }, { "$project" : { "Records" : "$Records", "_id" : 0 } }, { "$sort" : { "Records.RecorDateTime" : 1 } }, { "$skip" : 30 }, { "$limit" : 10 } ]);如果同时引用了System.Linq 和 MongoDB.Driver.Linq 调用Linq方法会产生方法二义性,解决方案链接如下。 Linq方法二义性解决方案: http://blog.csdn.net/qq_27441069/article/details/79586216
本文简单的介绍了MongoDB C#驱动对内嵌文档的增删改查操作,笔者水平有限,如有错误请批评指正!
转载于:https://www.cnblogs.com/InCerry/p/9390193.html
