安装使用
小插曲
:项目属性-生成-高级,发现vs2013只支持到C#5.0
C#6.0在vs2015引入,或在vs2013基础上从https://github.com/dotnet/roslyn下载roslync包
MongoDB Client
RoboMongo --> Robo 3T
RoboMongo镜像地址:http://dl.mongodb.org/dl/win32/x86_64
Robo 3T下载地址:https://robomongo.org/download
可分别参考:使用教程1, 使用教程2
.Net MongoDB
驱动下载:https://github.com/mongodb/mongo-csharp-driver/releases,或直接在 nuget.net 中下载包
MongoDB .Net文档:.NET MongoDB Driver 2.2 API注释
版本兼容一览表:MongoDB C#/.NET Driver
Robo 3T
常用命令汇总
db.getCollection('collectName').find({'UID':/^2020-03-25$/}) //正则匹配
基础概念
查询
若类对象T中无_id字段,FindOneAsElement '_id' doesnot match any field of class T
- 类对象T指定属性
[BsonIgnoreExtraElements]
- 类定义增加字段
public ObjectId _id { get; set; }
- 类对象T使用
BsonDocument
为所有类实现BsonIgnoreExtraElements,具体:Implement for all classes BsonIgnoreExtraElements
var conventionPack = new ConventionPack { new IgnoreExtraElementsConvention(true) };
ConventionRegistry.Register("IgnoreExtraElements", conventionPack, type => true);
查询指定字段
FilterDefinitionBuilder<BsonDocument> builderFilter = Builders<BsonDocument>.Filter;
ProjectionDefinitionBuilder<BsonDocument> builderProjection = Builders<BsonDocument>.Projection;
ProjectionDefinition<BsonDocument> projection = builderProjection.Include("_fieldName1").Exclude("_fieldName2");
var result = coll.Find<BsonDocument>(builderFilter.Empty).Project(projection).ToList();
//Linq查询
var result = from y in coll.AsQueryable()
select new { _fieldName1 = y["_fieldName1"], _fieldName2 = y["_fieldName2"] };
var result = from y in coll.AsQueryable() group y
by y["_fieldName1"] into s
select new { _fieldName1 = s.Key, Count = s.Count() };
将BsonDocument相关对象转换为具体类T对象:扩展方法
public static IList<T> ToEntityList<T>(this IEnumerable<BsonDocument> bsons) {
IList<T> list = new List<T>();
foreach (var item in bsons) {
list.Add(BsonSerializer.Deserialize<T>(item));
}
return list;
}
新增
- insert
- save
insert可以一次性插入一个列表,不用遍历、效率高, save需要遍历列表、一个个插入。
关于insert和save的区别:https://blog.csdn.net/xiaojin21cen/article/details/40480609
注意,文中强调的是自有的主键_id。若插入数据中有自定义为Unique索引的字段,insert和save均会抛异常键重复。
更新
- update
- replace
删除
此处提供一个简单的MongoDB工具类
private static MongoServer server = MongoServer.Create("connectionString");
private static MongoDatabase database {
get {
if (server == null) return null;
server.Connect(TimeSpan.FromSeconds(5));
if (!server.DatabaseExists(databaseName)) {
var databaseSettings = server.CreateDatabaseSettings(databaseName);
return server.GetDatabase(databaseSettings);
} else {
return server.GetDatabase(databaseName);
}
}
}
public static MongoCollection<BsonDocument> GetMongoCollection(string collectionName){
if (server == null) return null;
using (server.RequestStart(database)) {
if (!database.CollectionExists(collectionName)) {
database.CreateCollection(collectionName);
}
return database.GetCollection<BsonDocument>(collectionName);
}
}
此处提供一个功能更丰富的工具类:参考1, 参考2, 参考3, 参考4, 参考5
// 建议T名称与collection名称保持一致
public class MongoUtil<T> where T : class {
private string _conn = string.Empty;
private string _dbName = string.Empty;
private string _collectName = String.Empty;
private MongoClient client;
private IMongoDatabase db;
private IMongoCollection<T> collection;
public MongoUtil(string connectionString, string dbName, string collectName) {
this._conn = connectionString;
this._dbName = dbName;
this._collectName = collectName;
this.client = new MongoClient(connectionString);
this.db = client.GetDatabase(dbName);
this.collection = db.GetCollection<T>(this._collectName);
}
public MongoUtil(string connectionString, string dbName) :
this(connectionString, dbName, typeof(T).Name) //获取类名
{ }
public void InsertOne(String collName, T data) {
this.collection.InsertOne(data);//InsertOneAsync
}
public void InsertBath(String collName, IEnumerable<T> datas) {
this.collection.InsertMany(datas);//InsertManyAsync
}
public void DeleteOneByKey(string key, object v) {
FilterDefinition<T> filter = Builders<T>.Filter.Eq(key, v);
this.collection.DeleteOne(filter, CancellationToken.None);//DeleteOneAsync
}
public void DeleteManyByKey(string key, object value) {
FilterDefinition<T> filter = Builders<T>.Filter.Eq(key, value);
collection.DeleteMany(filter);//DeleteManyAsync
}
public void UpdateByKey(string key, object value, T data) {
FilterDefinition<T> filter = Builders<T>.Filter.Eq(key, value);
UpdateOptions opts = new UpdateOptions() { IsUpsert = false };
ReplaceOneResult rlt = this.collection.ReplaceOne(filter, data, opts, CancellationToken.None);
}
public void Update(Expression<Func<T, Boolean>> filter, UpdateDefinition<T> update, Boolean upsert = false) {
var result = this.collection.UpdateMany(filter, update, new UpdateOptions { IsUpsert = upsert });
}
public void UpdateByKey(string k, object v, string key2, object value2) {
FilterDefinition<T> filter = Builders<T>.Filter.Eq(k, v);
var updateDefinition = Builders<T>.Update.Set(key2, value2);
var model = new UpdateOneModel<T>(filter, updateDefinition) { IsUpsert = false };
List<WriteModel<T>> requests = new List<WriteModel<T>>();
requests.Add(model);
BulkWriteResult<T> bulkWriteResult = this.collection.BulkWrite(requests);
}
public IFindFluent<T, T> FindByKey(string k, object v) {
FilterDefinition<T> filter = Builders<T>.Filter.Eq(k, v);
IFindFluent<T, T> findFluent = this.collection.Find(filter);
return findFluent;
}
public IFindFluent<T, T> FindRangeKeys(string k, string _start, string _end) {
var filterDefinition = Builders<T>.Filter.Gte(k, _start) & Builders<T>.Filter.Lt(k, _end);
var findFluent = collection.Find(filterDefinition);
return findFluent;
}
public IFindFluent<T, T> FindRangeKeys(string key, string[] uids) {
var filterDefinition = Builders<T>.Filter.In(key, uids);
return collection.Find(filterDefinition);
}
public IFindFluent<T, T> FindByUids(string key, string[] uids, int limit, int skip) {
var filterDefinition = Builders<T>.Filter.In(key, uids);
return collection.Find(filterDefinition).Limit(limit).Skip(skip).Sort(Builders<T>.Sort.Descending(key));
}
public IFindFluent<T, T> FindByRegex(string key, List<string> orValue) {
StringBuilder sb = new StringBuilder().Append("{'$or':[ ");
for (int i = 0; i < orValue.Count; i++) {
//i表示包括,最后一位加逗号不会报错
sb.Append("{'" + key + "':{$regex: '" + orValue[i] + "', $options:'i'}},");
}
sb.Append("]}").Append(".limit(" + 10 + ").skip(" + 0 + ")");
BsonDocument bsonDocumnet = BsonSerializer.Deserialize<BsonDocument>(sb.ToString());
return collection.Find(bsonDocumnet);
}
}
注意,MongoServer
已废弃,推荐Mongo客户端MongoClient
提供几个异步方式示例:参考
public async Task<T> FindByBsonAsync(BsonDocument bson){
return await this.collection.Find(new BsonDocument()).FirstOrDefaultAsync();
}
// 若返回的数据预期很大,建议采用以下异步的迭代的方法处理。
await collection.Find(new BsonDocument()).ForEachAsync(d => Console.WriteLine(d));
// 若用同步方法,可以采用ToEnumerable适配器方法
var cursor = collection.Find(new BsonDocument()).ToCursor();
foreach (var document in cursor.ToEnumerable()) { ... }
提供一个分页查询示例
public List<T> FindListByPage(FilterDefinition<T> filter, int pageIndex, int pageSize, out long count,
string[] field = null, SortDefinition<T> sort = null) {
try {
count = this.collection.CountDocuments(filter);
if (field == null || field.Length == 0) { //不指定查询字段
if (sort == null)
return this.collection.Find(filter).Skip((pageIndex - 1) * pageSize).Limit(pageSize).ToList();
return this.collection.Find(filter).Sort(sort).Skip((pageIndex - 1) * pageSize).Limit(pageSize).ToList();
} else { //指定查询字段
var _fieldLst = new List<ProjectionDefinition<T>>();
for (int i = 0; i < field.Length; i++) {
_fieldLst.Add(Builders<T>.Projection.Include(field[i].ToString()));
}
var projection = Builders<T>.Projection.Combine(_fieldLst);
_fieldLst?.Clear();
if (sort == null)
return this.collection.Find(filter).Project<T>(projection).Skip((pageIndex - 1) * pageSize).Limit(pageSize).ToList();
return this.collection.Find(filter).Project<T>(projection).Sort(sort).Skip((pageIndex - 1) * pageSize).Limit(pageSize).ToList();
}
} catch (Exception ex) { throw ex; }
}
问题解决
问题1:保存DateTime类型到Mongo后,日期比存入的日期要小
原因:Mongo会将时间保存成UCT时间,即格林威治时间,比北京时间要晚8小时
解决:在时间属性上加标签[BsonDateTimeOptions(Kind = DateTimeKind.Local)]
或注册
BsonSerializer.RegisterSerializer(typeof(DateTime),
new DateTimeSerializer(DateTimeSerializationOptions.LocalInstance));
具体参见:http://www.voidcn.com/article/p-tanzovjm-bsx.html
问题2:在.Net中写enum类型对象到MongoDB,会存储为Int32类型
解决:方法1,方法2
但是,并不影响程序调用和解析。
问题3:调用Save()方法,提示报错:
No IdGenerator found. 在 MongoDB.Driver.MongoCollection.Save(Type nominalType, Object document, MongoInsertOptions options)
在 MongoDB.Driver.MongoCollection.Save(Type nominalType, Object document)
在 MongoDB.Driver.MongoCollection.Save[TNominalType](TNominalType document)
在 CMB.ScheduleTask.MongoHelper.SaveOne[T](MongoServerSettings connString, String dbName, String collectionName, T entity)
解决:entity中必须要有_id字段。另,MongoDB驱动新版(v2.7.0等)均已去掉Save()方法
问题4:MongoDB(v3.4)连接初始化报错(MongoDB Driver v2.8.1)
Multiple custom attributes of the same type found.
at System.Attribute.GetCustomAttribute(Assembly element, Type attributeType, Boolean inherit)
at System.Runtime.InteropServices.RuntimeInformation.get_FrameworkDescription() [4.3.0]
at System.Lazy`1.CreateValue()
--- End of stack trace from previous location where exception was thrown ---
at System.Lazy`1.get_Value()
at MongoDB.Driver.Core.Connections.ClientDocumentHelper.CreateClientDocument(String applicationName)
at MongoDB.Driver.Core.Connections.BinaryConnectionFactory..ctor(ConnectionSettings settings, IStreamFactory streamFactory, IEventSubscriber eventSubscriber)
at MongoDB.Driver.Core.Configuration.ClusterBuilder.CreateConnectionPoolFactory()
at MongoDB.Driver.Core.Configuration.ClusterBuilder.CreateServerFactory()
at MongoDB.Driver.Core.Configuration.ClusterBuilder.CreateClusterFactory()
at MongoDB.Driver.ClusterRegistry.CreateCluster(ClusterKey clusterKey)
at MongoDB.Driver.ClusterRegistry.GetOrCreateCluster(ClusterKey clusterKey)
at MongoDB.Driver.MongoClient..ctor(MongoClientSettings settings)
at Tool.MongoDBHelper`1..ctor(String collectionName)
解决:先降版本至2.7.0。可参考:由System.Runtime.InteropServices.RuntimeInformation.dll引发的MongoDB连接问题
问题5:提示Size 27105946 is large than MaxDocumentSize 16777216
因为bson文档大小最大限制为16MB,确保单个文档不会使用过多RAM,或在迁移期间不会占用过多的带宽。
参见:MongoDB限制,其他限制