.Net Core 使用MongoDb、及关系介绍
一、前言
简单介绍Asp.Net Core应用连接MongoDB数据库,并进行简单的CRUD和一对多关系的建立。
需要用到的东西:.NET Core 3.0+、MongoDB、Robot3T、MongoDB.Driver。
二、创建简单CRUD
使用MongoDB创建简单CRUD的WebApi,直接看微软的官方教程。很详细!
https://docs.microsoft.com/zh-cn/aspnet/core/tutorials/first-mongo-app?view=aspnetcore-3.1&tabs=visual-studio
看完不太懂,也能看MongoDB.Driver的文档,也有教程 (**警告)。
https://mongodb.github.io/mongo-csharp-driver/2.11/getting_started/quick_tour/
三、上面的注意点
1. MongoClient
MongoClient实际代表数据库的一个连接池,就算使用多线程,也只需一个实例。(可以注册为单例服务,如一个单例类的属性)
2. MongoDB的查询结果调用ToList<T>小心字段对不上出错。可以尝试在Model类上加[BsonIgnoreExtraElements]特性。
四、介绍MongoDB集合间的关系与引用
1. 嵌入式关系
可以把A的文档嵌入到B的文档中。优点是比较容易的获取和维护数据。缺点是数据量不断变大,会影响读写性能,而且会出现数据冗余。
一次查询即可带出
db.users.findOne({"name":"Tom Benzamin"},{"address":1})
参考资料:https://www.runoob.com/mongodb/mongodb-relationships.html
2. 引用式关系
引用式关系是设计数据库时经常用到的方法,这种方法把A文档和B文档分开,通过引用文档的 id 字段来建立关系。
优点是像关系数据库的外键,省空间。缺点是查询要查两遍,或者要使用$lookup来关联集合,查的慢。
- 手动引用(Manual References)
A文档中有个字段直接存B文档的id。示例:
{
"_id":ObjectId("52ffc33cd85242f436000001"),
"contact": "987654321",
"dob": "01-01-1991",
"name": "Tom Benzamin",
"address_ids": [
ObjectId("52ffc4a5d85242602e000000"),
ObjectId("52ffc4a5d85242602e000001")
]
}
//查询语句:
var result = db.users.find({"name":"Tom Benzamin"},{"address_ids":1})
var addresses = db.address.find({"_id":{"$in":result[0]["address_ids"]}})
- DBRefs
{ $ref : <value>, $id : <value>, $db : <value> }
$ref:集合名称;$id:引用的id;$db:数据库名称,可选参数。
可以看到DBRef的结构比Manual References的复杂,占用的空间大,但是功能也强大。如果要跨数据库连接,都得需要使用DBRef。
MongoDB提供了函数来解析DBRef,不用像Manual References需要自己手动写两次查询。
DBRef就是在两个Collection之间定义的一个关联关系,比如,把CollectionB "_id"列的值存在CollectionA的一个列中,然后通过CollectionA这个列中所存的值在CollectionB中找到相应的记录。
示例:
{
"_id":ObjectId("53402597d852426020000002"),
"address": {
"$ref": "address_home",
"$id": ObjectId("534009e4d852427820000002"),
"$db": "runoob"},
"name": "Tom Benzamin"
}
//查询语句:
var user = db.users.findOne({"name":"Tom Benzamin"});
var dbRef = user.address;
db[dbRef.$ref].findOne({"_id":(dbRef.$id)});
//或者另一种DBRef用法
{
"_id":ObjectId("53402597d852426020000002"),
"address":new DBRef('address_home',ObjectId("534009e4d852427820000002"))
"name": "Tom Benzamin"
}
//查询语句,用DBRef的fetch方法
var user = db.users.findOne({"name":"Tom Benzamin"});
user.address.fetch();
参考资料:https://www.runoob.com/mongodb/mongodb-database-references.html
https://www.cnblogs.com/duanxz/p/10838910.html
- 使用Aggregate的$lookup来查询手动引用
使用$lookup来连接集合,查询对应的引用,也不错,类似SQL连表。
示例:
db.product.aggregate([
{
$lookup:
{
from: "orders", //需要关联的集合【orders】
localField: "_id", //【orders】的matching key
foreignField: "pid", //【product】集合需要关联的键
as: "inventory_docs" //连接到的外键数据的集合
}
}
])
了解更多:https://www.cnblogs.com/xuliuzai/p/10055535.html
五、.Net Core访问MongoDB的例子代码
1. 相关模型类
例子有两个模型,Product(产品),Manufacturer(制造商)
[BsonIgnoreExtraElements] //忽略其余字段
public class Manufacturer
{
///// <summary>
///// 主键,mongodb默认
///// </summary>
//public string _id { get; set; }
[BsonId]
[BsonRepresentation(BsonType.ObjectId)]
[JsonProperty("_id")]
public string Id { get; set; }
/// <summary>
/// 名称
/// </summary>
[BsonElement("name")] //测试例子,name为mongodb的集合的字段
[JsonProperty("name")]
public string FullName { get; set; }
}
[BsonIgnoreExtraElements] //忽略其余字段
public class Product
{
[BsonId]
[BsonRepresentation(BsonType.ObjectId)]
[JsonProperty("_id")]
public string Id { get; set; }
[BsonElement("name")]
public string Name { get; set; }
/// <summary>
/// 图片地址
/// </summary>
[BsonElement("image")]
public string Image { get; set; }
/// <summary>
/// 价格
/// </summary>
[BsonElement("price")]
public double? Price { get; set; }
/// <summary>
/// 描述
/// </summary>
[BsonElement("description")]
public string Description { get; set; }
/// <summary>
/// 制造商id
/// </summary>
[BsonElement("manufacturer")]
[BsonRepresentation(BsonType.ObjectId)]
public string ManufacturerId { get; set; }
/// <summary>
/// 制造商模型
/// </summary>
[BsonIgnoreIfNull]
public Manufacturer Manufacturer { get; set; }
}
2. CRUD的简单例子
列出一个制造商Service的CRUD代码
public class ManufacturerService
{
//生产商对应集合
private readonly IMongoCollection<Manufacturer> _manufacturerCol;
public ManufacturerService(IMongoCollection<Manufacturer> collection)
{
//外部传入用到的集合
_manufacturerCol = collection ?? throw new ArgumentNullException(nameof(collection));
}
/// <summary>
/// 查询所有
/// </summary>
public List<Manufacturer> Get() => _manufacturerCol.Find(m => true).ToList();
/// <summary>
/// 查询单个
/// </summary>
public Manufacturer Get(string id) => _manufacturerCol.Find(m => m.Id == id).FirstOrDefault();
public Manufacturer Create(Manufacturer manufacturer)
{
_manufacturerCol.InsertOne(manufacturer);
return manufacturer;
}
public void Update(string id, Manufacturer manufacturer)
{
_manufacturerCol.ReplaceOne(m => m.Id == id, manufacturer);
}
public void Remove(Manufacturer manufacturer)
{
_manufacturerCol.DeleteOne(m => m.Id == manufacturer.Id);
}
public void Remove(string id)
{
_manufacturerCol.DeleteOne(m => m.Id == id);
}
}
3. 使用DBRefs的文档,如何关联
MongoDB.Driver这个库有个MongoDBRef的类,表示DBRefs这种结构。以前是有API直接使用MongoDBRef,查出对应文档。据我所知,这种操作好像已经过时,不太清楚。详情看官方文档。
MongoDBRef r1 = new MongoDBRef("testdb", "orders",new ObjectId("5e194c1b2dfe031505deaf67"));
4. 使用手动引用的文档,如何关联
- 手动查两遍(第一次查询A,再通过引用字段,查出B)
自己封装了一个第二遍查关联集合的方法。
//调用自定义封装方法
await MongoDbCusHelper.IncludeList(list, _manufacturerCol, "ManufacturerId", "Manufacturer", "Id");
//定义
public static async Task<IEnumerable<TSource>> IncludeList<TSource, TDest>(
IEnumerable<TSource> sourcesList, IMongoCollection<TDest> destCol, string srcFieldName, string newDocFieldName, string destFieldName = "_id")
{
if (sourcesList == null)
{
throw new ArgumentNullException(nameof(sourcesList));
}
if (destCol is null)
{
throw new ArgumentNullException(nameof(destCol));
}
if (string.IsNullOrWhiteSpace(newDocFieldName))
newDocFieldName = destCol.CollectionNamespace.CollectionName;
//判断TSource, TDest是否存在对应字段
PropertyInfo srcPi = typeof(TSource).GetProperty(srcFieldName);
if (srcPi == null)
{
throw new ArgumentException($"{nameof(TSource)}没有{srcFieldName}这个属性");
}
PropertyInfo destPi = typeof(TDest).GetProperty(destFieldName);
if (destPi == null)
{
throw new ArgumentException($"{nameof(TDest)}没有{destFieldName}这个属性");
}
PropertyInfo srcDestPi = typeof(TSource).GetProperty(newDocFieldName);
if (srcDestPi == null)
{
throw new ArgumentException($"{nameof(TSource)}没有{newDocFieldName}这个属性");
}
//查关联数据
var srcFieldNames = sourcesList.Select(src => srcPi.GetValue(src)).Distinct();
var filter = Builders<TDest>.Filter.In(destFieldName, srcFieldNames);
var destList = await destCol.Find(filter).ToListAsync();
//只包含一个文档
foreach (var srcItem in sourcesList)
{
var tmpDestItem = destList.FirstOrDefault
(m => destPi?.GetValue(m)?.ToString() == srcPi?.GetValue(srcItem)?.ToString());
srcDestPi.SetValue(srcItem, tmpDestItem);
}
return sourcesList;
}
- 通过lookup来关联数据,只需查一遍
//通过Aggregate方法查
public Product GetFluentLookup(string id)
{
//使用Fluent Aggregate Query
var tmpresults = _productCol.Aggregate()
.Match(p => p.Id == id)
.Lookup("manufacturers", "manufacturer"
, "_id", "Manufacturer")
//使用Unwind将数组变为文档
.Unwind("Manufacturer").FirstOrDefault();
Product products = null;
products = BsonSerializer.Deserialize<Product>(tmpresults);
return products;
}
//通过Linq形式的Aggregate查
public Product GetByLinq(string id)
{
var results = from p in _productCol.AsQueryable()
where p.Id == id
join m in _manufacturerCol.AsQueryable()
on p.ManufacturerId equals m.Id into joined
//再from in 关联结果,就会报错,不知为何
//from sub_m in joined.DefaultIfEmpty()
//select new { Product = p, sub_m };
select new { p, joined };
List<Product> products = new List<Product>();
foreach (var p in results)
{
Product tmpP = p.p;
tmpP.Manufacturer = p.joined.FirstOrDefault();
products.Add(tmpP);
}
return products.FirstOrDefault();
}
了解更多,可查阅https://mongodb.github.io/mongo-csharp-driver/2.11/reference/driver/crud/linq/#lookup
六、小结
大概写了下,记录.Net Core如何使用MongoDB数据库,主要是一对多、一对一关联相比SQL麻烦。好像MongoDB不擅长复杂的关系,复杂的多表连接,不过其查询性能比关系数据库好很多。这里只是少部分内容,还有很多要学。

浙公网安备 33010602011771号