MongoDB强类型Collection用法示例
.NET中使用MongoDB非常简单,一般来说可以直接使用BsonDocument,也可以使用定义好的数据类型对文档进行CRUD操作。本文通过实例对比一下两种方式的优劣,通常,通过强类型Collection对文档进行操作更为便捷。
BsonDocument CRUD
MongoDB
C# Driver官方文档
是直接用BsonDocument进行操作的,猜想官方文档可能是想突出体现MongoDB是schema-less的,可以不定义schema而读写文档。但noSQL不代表schema-less,而是not only SQL
。在同一个collection中还是推荐使用统一的data
model,因为统一的schema可以更方便读写,而且也方便使用索引。
下文将使用的示例文档定义:
public class DemoEntry
{
public string Uuid { get; set; }
public string Value { get; set; }
public override string ToString()
{
return Uuid + ": " + Value;
}
}
获取一个弱类型collection:
IMongoCollection<BsonDocument> _bsonCollection = database.GetCollection<BsonDocument>(CollectionName);
使用弱类型collection进行CRUD:
var entry = new DemoEntry
{
Uuid = uuid,
Value = "1"
};
Console.WriteLine("-- BsonDocument CRUD ---");
var filter = Builders<BsonDocument>.Filter.Eq("Uuid", uuid);
// Create
Console.WriteLine("[Create] " + entry);
await _bsonCollection.ReplaceOneAsync(filter, entry.ToBsonDocument(), new ReplaceOptions { IsUpsert = true });
// Read
var doc = await _bsonCollection.Find(filter).FirstOrDefaultAsync();
var retrievedEntry = BsonSerializer.Deserialize<DemoEntry>(doc);
Console.WriteLine("[Read] " + retrievedEntry);
// Update
await _bsonCollection.UpdateOneAsync(filter, Builders<BsonDocument>.Update.Set("Value", "2"));
doc = await _bsonCollection.Find(filter).FirstOrDefaultAsync();
retrievedEntry = BsonSerializer.Deserialize<DemoEntry>(doc);
Console.WriteLine("[Update] " + retrievedEntry);
// Delete
var t = await _bsonCollection.DeleteOneAsync(filter);
Console.WriteLine("[Delete] deleted count: " + t.DeletedCount);
注意在弱类型collection的读写中,我们需要在写操作之前手动将class转换成bsondocument
(entry.ToBsonDocument()
),同时在读操作之后手动将bsondocument解析成class
(BsonSerializer.Deserialize<DemoEntry>(doc)
)。此外,我们在构建filter时需要手动输入field名称的字符串,很容易出错,而且是运行时错误。
强类型CRUD示例
获取一个强类型collection:
IMongoCollection<DemoEntry> _stronglyTypedCollection = database.GetCollection<DemoEntry>(CollectionName);
CRUD示例:
public async Task StronglyTypedCrudAsync(string uuid)
{
var entry = new DemoEntry
{
Uuid = uuid,
Value = "1"
};
Console.WriteLine("-- Strongly typed CRUD ---");
// Create
Console.WriteLine("[Create] " + entry);
await _stronglyTypedCollection.ReplaceOneAsync(x => x.Uuid == uuid, entry, new ReplaceOptions {IsUpsert = true});
// Read
var retrievedEntry = await _stronglyTypedCollection.Find(x => x.Uuid == uuid).FirstOrDefaultAsync();
Console.WriteLine("[Read] " + retrievedEntry);
// Update
await _stronglyTypedCollection.UpdateOneAsync(x => x.Uuid == uuid, Builders<DemoEntry>.Update.Set(x => x.Value, "2"));
retrievedEntry = await _stronglyTypedCollection.Find(x => x.Uuid == uuid).FirstOrDefaultAsync();
Console.WriteLine("[Update] " + retrievedEntry);
// Delete
var t = await _stronglyTypedCollection.DeleteOneAsync(x => x.Uuid == uuid);
Console.WriteLine("[Delete] deleted count: " + t.DeletedCount);
}
是否要使用强类型collection?
推荐使用强类型collection。 优点非常明显:
- 不易出错:使用lambda表达式指定filter,在编译时就确定了field name
- 代码简捷:不需要手动在class和bsondocument之间转换。
- LINQ语法支持:这一点在使用aggregation的时候极为便捷,一行代码就能处理十行的一个aggregation pipeline。
IgnoreExtraElements Convention
大部分MongoDB新用户想必都会遇到如下错误:
Element '_id' does not match any field or property of class
In MongoDB, each document stored in a collection requires a unique _id field that acts as a primary key. If an inserted document omits the _id field, the MongoDB driver automatically generates an ObjectId for the _id field.
但在代码中我们往往不想显式定义这么个字段,因为它对开发者来说没有太多意义。可以通过两种方法解决这个问题。
[BsonIgnoreExtraElements] Annotation
在类定义中加上[BsonIgnoreExtraElements]
annotation:
[BsonIgnoreExtraElements]
public class DemoEntry
{
public string Uuid { get; set; }
public string Value { get; set; }
public override string ToString()
{
return Uuid + ": " + Value;
}
}
此方法的缺点在于要引入MongoDB驱动包的依赖。如果这个类定义在一个接口库中,其他用户在使用此接口库时也会依赖MongoDB
nuget包。而在系统设计中,底层存储逻辑对上层用户应该是透明的。
设置IgnoreExtraElementsConvention
另一种方案是在获取collection之前设置IgnoreExtraElementsConvention
:
var ignoreExtraElementsConvention = new ConventionPack { new IgnoreExtraElementsConvention(true) };
ConventionRegistry.Register("IgnoreExtraElements", ignoreExtraElementsConvention, type => true);
IgnoreExtraElementsConvention
的意思是在反序列化文档时,忽略映射类中未定义的字段。
对强类型collection的用法,设置IgnoreExtraElementsConvention
和获取collection的顺序非常重要:
_beingCollection = database.GetCollection<BsonDocument>(AvatarFrameworkMongoConfig.BeingCollectionName);
var ignoreExtraElementsConvention = new ConventionPack { new IgnoreExtraElementsConvention(true) };
ConventionRegistry.Register("IgnoreExtraElements", ignoreExtraElementsConvention, type => true);
上面就是错误的顺序,下面错误依然会发生:
Element '_id' does not match any field or property of class
正确设置方法:
var ignoreExtraElementsConvention = new ConventionPack { new IgnoreExtraElementsConvention(true) };
ConventionRegistry.Register("IgnoreExtraElements", ignoreExtraElementsConvention, type => true);
_beingCollection = database.GetCollection<BsonDocument>(AvatarFrameworkMongoConfig.BeingCollectionName);
而对于弱类型collection,这个设置顺序似乎是无所谓的。猜想应该是在创建强类型collection时会使用当前的ConventionRegistry
设置BsonClassMap
。
Full Code
using System;
using System.Threading.Tasks;
using MongoDB.Bson;
using MongoDB.Bson.Serialization;
using MongoDB.Bson.Serialization.Conventions;
using MongoDB.Driver;
namespace MongoDemo
{
class Program
{
static void Main(string[] args)
{
var demo = new StronglyTypedDemo();
demo.BsonCrudAsync("abc").Wait();
Console.WriteLine("-----------------------------------");
demo.StronglyTypedCrudAsync("abc").Wait();
Console.ReadLine();
}
}
public class DemoEntry
{
public string Uuid { get; set; }
public string Value { get; set; }
public override string ToString()
{
return Uuid + ": " + Value;
}
}
public class StronglyTypedDemo
{
private const string DatabaseName = "";
private const string CollectionName = "Test";
private const string ConnectionString = "";
private readonly MongoClient MongoClient;
private readonly IMongoCollection<BsonDocument> _bsonCollection;
private readonly IMongoCollection<DemoEntry> _stronglyTypedCollection;
public StronglyTypedDemo()
{
var clientSettings = MongoClientSettings.FromConnectionString(ConnectionString);
clientSettings.ConnectTimeout = TimeSpan.FromSeconds(5);
clientSettings.ServerSelectionTimeout = TimeSpan.FromSeconds(5);
clientSettings.AllowInsecureTls = true;
MongoClient = new MongoClient(clientSettings);
var database = MongoClient.GetDatabase(DatabaseName);
var ignoreExtraElementsConvention = new ConventionPack { new IgnoreExtraElementsConvention(true) };
ConventionRegistry.Register("IgnoreExtraElements", ignoreExtraElementsConvention, type => true);
_bsonCollection = database.GetCollection<BsonDocument>(CollectionName);
_stronglyTypedCollection = database.GetCollection<DemoEntry>(CollectionName);
}
public async Task StronglyTypedCrudAsync(string uuid)
{
var entry = new DemoEntry
{
Uuid = uuid,
Value = "1"
};
Console.WriteLine("-- Strongly typed CRUD ---");
// Create
Console.WriteLine("[Create] " + entry);
await _stronglyTypedCollection.ReplaceOneAsync(x => x.Uuid == uuid, entry, new ReplaceOptions {IsUpsert = true});
// Read
var retrievedEntry = await _stronglyTypedCollection.Find(x => x.Uuid == uuid).FirstOrDefaultAsync();
Console.WriteLine("[Read] " + retrievedEntry);
// Update
await _stronglyTypedCollection.UpdateOneAsync(x => x.Uuid == uuid, Builders<DemoEntry>.Update.Set(x => x.Value, "2"));
retrievedEntry = await _stronglyTypedCollection.Find(x => x.Uuid == uuid).FirstOrDefaultAsync();
Console.WriteLine("[Update] " + retrievedEntry);
// Delete
var t = await _stronglyTypedCollection.DeleteOneAsync(x => x.Uuid == uuid);
Console.WriteLine("[Delete] deleted count: " + t.DeletedCount);
}
public async Task BsonCrudAsync(string uuid)
{
var entry = new DemoEntry
{
Uuid = uuid,
Value = "1"
};
Console.WriteLine("-- BsonDocument CRUD ---");
var filter = Builders<BsonDocument>.Filter.Eq("Uuid", uuid);
// Create
Console.WriteLine("[Create] " + entry);
await _bsonCollection.ReplaceOneAsync(filter, entry.ToBsonDocument(), new ReplaceOptions { IsUpsert = true });
// Read
var doc = await _bsonCollection.Find(filter).FirstOrDefaultAsync();
var retrievedEntry = BsonSerializer.Deserialize<DemoEntry>(doc);
Console.WriteLine("[Read] " + retrievedEntry);
// Update
await _bsonCollection.UpdateOneAsync(filter, Builders<BsonDocument>.Update.Set("Value", "2"));
doc = await _bsonCollection.Find(filter).FirstOrDefaultAsync();
retrievedEntry = BsonSerializer.Deserialize<DemoEntry>(doc);
Console.WriteLine("[Update] " + retrievedEntry);
// Delete
var t = await _bsonCollection.DeleteOneAsync(filter);
Console.WriteLine("[Delete] deleted count: " + t.DeletedCount);
}
}
}
运行结果:
-- BsonDocument CRUD ---
[Create] abc: 1
[Read] abc: 1
[Update] abc: 2
[Delete] deleted count: 1
-----------------------------------
-- Strongly typed CRUD ---
[Create] abc: 1
[Read] abc: 1
[Update] abc: 2
[Delete] deleted count: 1