C# .NET Core 3.1中使用 MongoDB.Driver 更新数组元素和关联的一些坑

前言:

由于工作的原因,使用的数据库由原来的 关系型数据库 MySQL、SQL Server 变成了 非关系型数据库 MongoDB。可以简单的理解为存下的是 Json(实际是一个类似的东西叫 Bson)。由于仍然使用 C# 作为开发语言,自然是绕不开官方的数据库驱动 MongoDB.Driver。由于 MongoDB 的特性和驱动程序自身的实现,也可能是因为个人的习惯,感觉使用起来并不顺手,还遇到了很多坑XD,因此记录一下。

环境与数据

A. 使用 Visual Studio 2019、.NET Core 3.1 和 MongoDB.Driver 2.13.1
B. 类的定义(还是用《原神》了哈哈)
/// <summary>
/// 原神角色
/// </summary>
public class YuanshenRole
{
/// <summary>
/// 主键
/// </summary>
public ObjectId _id { get; set; }
/// <summary>
/// 名字
/// </summary>
public string Name { get; set; }
/// <summary>
/// 元素:火、水、岩、冰、风等
/// </summary>
public ObjectId ElementId { get; set; }
/// <summary>
/// 所拥有的武器
/// </summary>
public List<Weapon> Weapons { get; set; }
} /// <summary>
/// 元素
/// </summary>
public class Element
{
/// <summary>
/// 主键
/// </summary>
public ObjectId _id { get; set; }
/// <summary>
/// 名称
/// </summary>
public string ElementName { get; set; }
} /// <summary>
/// 武器
/// </summary>
public class Weapon
{
/// <summary>
/// 唯一标志
/// </summary>
public string Id { get; set; }
/// <summary>
/// 名称
/// </summary>
public string Name { get; set; }
/// <summary>
/// 星级
/// </summary>
public int LevelStart { get; set; }
}

其中每个“角色”拥有多把“武器”,同时与一种“元素”对应

C. 数据展示

可以看出,“琴”与“风”元素对应,“可莉”与“火”元素对应

主要内容

通用的结论:所谓驱动程序,就是将 C# 的写法翻译为 MongoDB 的查询语句写法(如 EntityFramework 将 C# 写法翻译为 Sql 语句)。

1. 更新数组类型字段的某个元素

不同于关系型数据库 SQL 的 UPDATE ... SET ... WHERE ...,MongoDB 中更新嵌套的类型可以使用 “outer.inner” 的形式来表示嵌套对象的对应字段。而更新嵌套的数组元素则要绕一个弯子。比如我们要把 “琴” 的 “手里剑” 改为 “新手大刀”,可以这样写:

var update = Builders<YuanshenRole>.Update
.Set(w => w.Weapons[-1].Name, "新手大刀");
collectionRole.UpdateOne(role => role.Name == "琴" && role.Weapons.Any(w => w.Name == "手里剑"), update);

“奇妙”之处在于:在查询时隐式地查询数组中的内容(Any方法),并且在更新时使用 -1 对应要更新的数组元素

对比下原生的写法:

db.YuanshenRole.updateMany({Name: "琴", "Weapons.Name":"手里剑"}, {$set:{"Weapons.$.Name":"新手大刀"}})

注意此处的 $ 符号。驱动正是将 -1 替换为了 $ 符号,参见源代码

2. 关联查询

对于关系型数据库而言,为了符合数据库的设计三大范式,不可避免的要建立多张表,关联也是家常便饭, MongoDB 中的关联思路也基本一致。

此时需要查询:角色和其对应的元素,则需要关联 角色表元素表

Linq 方式关联查询的代码为:(注意:要引入 MongoDB.Driver)

var query = from role in collectionRole.AsQueryable().Where(a => a.Weapons.Count > 0)
join element in collectionElement.AsQueryable()
on role.ElementId equals element._id
select new
{
role.Name,
element.ElementName
};
var dataList = query.ToList();
foreach(var data in dataList)
{
Console.WriteLine(string.Format("角色名:{0},元素属性:{1}", data.Name, data.ElementName));
}

查询结果:

角色名:琴,元素属性:风

角色名:可莉,元素属性:火

关联字段的类型要一致。这种方式可以正常查出结果,可如果稍微变化一下,如:

A.(报错)给关联的子表加入 where 条件

查阅了许多资料,似乎并没有说 MongoDB 关联的子表不能加入条件,或者说 MongoDB 关联的子表可以加入条件 ,最后只有一篇泛泛的描述

B.(报错)查询时查出整个“文档”

个人认为这是驱动“翻译”的问题

3. 关联查询的类原生写法

先看下 Mongo Shell 中的写法:

C# 语言中类原生的写法为:

BsonDocument query = new BsonDocument
{
{ "Weapons", new BsonDocument{ { "$size", 3 } } }
}; // 匹配条件
PipelineStageDefinition<BsonDocument, BsonDocument> match =
PipelineStageDefinitionBuilder.Match<BsonDocument>(query); // 关联条件
PipelineStageDefinition<BsonDocument, BsonDocument> lookup =
PipelineStageDefinitionBuilder.Lookup<BsonDocument, BsonDocument, BsonDocument>(
dbYuanShen.GetCollection<BsonDocument>("Element"), # 要关联的表
(FieldDefinition<BsonDocument>)"ElementId", # 主表的字段
(FieldDefinition<BsonDocument>)"_id", # 子表的对应字段
(FieldDefinition<BsonDocument>)"ElementAttr"); # 此处将关联后得到的子表对应字段设置为 “ElementAttr” // 组合并形成最终查询
var pipeline = PipelineDefinition<BsonDocument, BsonDocument>.Create(
new List<PipelineStageDefinition<BsonDocument, BsonDocument>> { match, lookup }); // 执行并得到数据
var dataList = dbYuanShen.GetCollection<BsonDocument>("YuanshenRole").Aggregate(pipeline).ToList(); var resultList = dataList.Select(a => new
{
Name = a.GetValue("Name").AsString,
ElementName = a.GetValue("ElementAttr").AsBsonArray[0] # 与上面设置的子表字段对应,不过要注意类型为数组
.AsBsonDocument.GetValue("ElementName").AsString
}).ToList();
foreach (var result in resultList)
{
Console.WriteLine(string.Format("角色名:{0},元素属性:{1}", result.Name, result.ElementName));
}

可以看出,在形成查询的 Json 时还是有一点复杂的,至少泛型的名称就够长的了QAQ。不过逻辑还算清晰,对比原生的写法也算是勉勉强强可以接受吧。需要注意的是:

(1)BsonDocument 的创建中,new BsonDocument{ { A, B } } 等价于 new BsonDocument{ new BsonElement(A, B) }

(2)如果想要在查询中使用 null, 应使用 BsonValue.Null

(3)时间的时区问题,应使用 BsonUtils.ToUniversalTime(MY_TIME) 转换一下时间。而在上面的 Linq 写法中,如果属性(如 CreateTime)添加了 BsonDateTimeOptions 特性 ,查询时驱动程序会根据类的定义自动转换,但也应注意操作系统的时区设置。

(4)如果要使用 $in 和 BsonArray,可以使用 BsonArray.Create(IEnumerable类型的值) 创建 BsonArray 对象

(5)此处的查询实际上是左外连接,从表的映射字段(即代码中的 ElementAttr)所表示的类型实际上是一个数组,如果子表中没有对应的关联,则映射字段结果为空数组。此处由于确定有关联数据,因此直接使用即可。如果要展开,可以在 pipeline 中加入 $unwind 操作。

4. 变动时多字段问题

A. 情况1,定义的类里面新加了字段 而 MongoD数据库没加

不影响。新加的字段会默认赋值为其类型的默认值

B. 情况2,MongoDB数据库中的新加了字段 而 定义的类中没加

会报错。如下:

但可通过在类上添加特性 BsonIgnoreExtraElements 来忽略数据库中多出的字段

番外篇

众所周知,MongoDB 中查询时如果要匹配数组的元素数量,只能写等于,而不能写大于或者小于。而我在 “2.关联查询”中居然写了 Count > 0,而且并没有报错,这是怎么回事呢。为了泛化这种情况,我把 Count > 0 改成 Count > 1,然后调试下:

原来是 指定了某个数组元素 配合 $exists 判断的,妙啊!

参考

MongoDB .Net Driver(C#驱动) - 内嵌数组/嵌入文档的操作(增加、删除、修改、查询(Linq 分页))(其中还有显式地调用扩展方法的讲解)

https://blog.csdn.net/qq_27441069/article/details/79586372

Mongodb在CSharp里实现Aggregate

https://www.cnblogs.com/lori/p/6864134.html

C# .NET Core 3.1中使用 MongoDB.Driver 更新嵌套数组元素和关联的一些坑的更多相关文章

  1. mongodb 更新嵌套数组的值

    概要 本文主要讲述在 mongodb 中,怎么更新嵌套数组的值. 使用$更新数组 基本语法  { "<array>.$" : value } 可以用于:update, ...

  2. ES6 中 Array.from() 不能得到的数组元素是 undefined 或是空数组

    本文地址:http://www.cnblogs.com/veinyin/p/7944072.html  正确格式 let json = { '0': 'Waaaa~', '1': 'Hello,', ...

  3. 在.Net Core中使用MongoDB的入门教程(二)

    在上一篇文章中,讲到了MongoDB在导入驱动.MongoDB的连接,数据的插入等. 在.Net Core中使用MongoDB的入门教程(一) 本篇文章将接着上篇文章进行介绍MongoDB在.Net ...

  4. 在.Net Core中使用MongoDB的入门教程(一)

    首先,我们在MongoDB的官方文档中看到,MongoDb的2.4以上的For .Net的驱动是支持.Net Core 2.0的. 所以,在我们安装好了MangoDB后,就可以开始MangoDB的.N ...

  5. Asp.Net Core中使用MongoDB的入门教程,控制台程序使用 MongoDB

    内容来源  https://blog.csdn.net/only_yu_yy/article/details/78882446 首先,创建一个.Net Core的控制台应用程序.然后使用NuGet导入 ...

  6. 在.NET Core中使用MongoDB明细教程(1):驱动基础及文档插入

    MongoDB,被归类为NoSQL数据库,是一个以类JSON格式存储数据的面向文档的数据库系统.MongoDB在底层以名为bson的二进制编码格式表示JSON文档,MongoDB bson实现是轻量级 ...

  7. 在.NET Core中使用MongoDB明细教程(2):使用Filter语句检索文档

    在上篇文章我们介绍了一些驱动程序相关的基础知识,以及如何将文档插入到集合中.在这篇文章中,我们将学习如何从数据库中检索文档. 作者:依乐祝 译文地址:https://www.cnblogs.com/y ...

  8. 如何在.Net中使用MongoDB

    最近在研究mongodb,针对.net 中使用mongodb的文章要么是早期的驱动版本,要么资料很少,所以写个随笔记录一下 本文主要记录 1.什么是MongoDB 2.MongoDB windows ...

  9. 在ASP.NET Core Web API中为RESTful服务增加对HAL的支持

    HAL(Hypertext Application Language,超文本应用语言)是一种RESTful API的数据格式风格,为RESTful API的设计提供了接口规范,同时也降低了客户端与服务 ...

随机推荐

  1. Quartz任务调度(2)CronTrigger定制个性化调度方案

    Cron表达式 1. 时间字段与基本格式 Cron表达式有6或7个空格分割的时间字段组成: 位置 时间域名 允许值 允许的特殊字符 1 秒 0-59 ,-*/ 2 分支 0-59 ,-*?/ 3 小时 ...

  2. MySQL 实例空间使用率过高的原因和解决方法

    用户在使用 MySQL 实例时,会遇到空间使用告警甚至超过实例限额被锁定的情况.在 RDS 控制台的实例基本信息中,即会出现如下信息: 本文将介绍造成空间使用率过高的常见原因及其相应的解决方法.对于M ...

  3. 紫色飞猪的研发之旅--06go自定义状态码

    在实际开发中,需要前后端需要协商状态码,状态码用于后端返前端时使用.在一个团队中,定义的状态码讲道理应该是一致的,项目开始的起始阶段状态码应该是定义了个七七八八的,随着功能的叠加而不断增加.此系列将围 ...

  4. (一)羽夏看C语言——简述

    "羽夏看C语言"介绍什么   本系列从汇编的角度,比较翔实的介绍C语言.C++和C其实是一样的东西,C++的编译器只是更强大,更能帮助我们写代码,例如模板.没有特殊说明,本系列不会 ...

  5. Redis核心原理与实践--字符串实现原理

    Redis是一个键值对数据库(key-value DB),下面是一个简单的Redis的命令: > SET msg "hello wolrd" 该命令将键"msg&q ...

  6. Java基础(一)——面向对象

    一.对象 1.成员变量和局部变量的区别 两类变量同名时,局部变量具有更高的优先级. 作用域不同:局部变量的作用域仅限于定义它的方法,作用于函数或者语句中:成员变量的作用域在整个类中. 初始值不同:Ja ...

  7. MySQL——MySQL体系结构

    1.连接层 2.SQL层: (1)将接收到的SQL语句,解析成执行计划 (2)查询优化器 ---->选择最优的执行计划,交给存储引擎 (3)查询缓存: 缓存执行计划 (4)附加功能:权限. 语法 ...

  8. Week2 题解

    T1及题解 T2 CF1207E 首先,异或这个位运算有个很好的性质:x^y^y=x 于是,可以有两种解决方法: 法一 既然让猜原来的数是多少,当它异或了一个其他值val的时候,再异或val就会变回原 ...

  9. Servlet处理带尾部斜杠/的URI

    有一个需求:让一个Servlet能够同时处理形如/XXX/YYY和/XXX/YYY/的URI,即URI尾部的斜杠有没有都要能处理到. 很容易想到,做两个URL Pattern/XXX/YYY和/XXX ...

  10. SpringSecurity-图解