[转]MongoDB学习 C#驱动操作MongoDB
下载驱动
驱动的下载有两种方式:一种是在C#项目中通过NuGet进行安装,另一种是通过下面的链接:https://github.com/mongodb/mongo-csharp-driver/releases 直接下载msi进行安装或zip压缩包。不管哪种方式,其主要的目的都是获取两个dll文件:MongoDB.Bson.dll、MongoDB.Driver.dll。这是在程序中需要引用的两个类库文件。
.NET版本要求
目前最新版的C#驱动是1.9.2,是在 .NET3.5的基础上构建的,所以使用C#驱动时,.NET的版本必须是3.5及其以上。
C#驱动主要包括两个命名空间:MongoDB.Bson和MongoDB.Driver。大多数的类是非线程安全的(线程安全的类有:MongoClient、MongoServer、MongoDatabase、MongoCollection、MongoGridFS、BsonSymbolTable)。所有的静态属性和方法都是线程安全的。
线程安全
如果代码所在的进程中有多个线程在同时运行,而这些线程可能会同时运行这段代码。每次运行结果和单线程运行的结果是一样的,而且其他的变量的值也和预期的是一样的,就是线程安全的。
或者说:一个类或者程序所提供的接口对于线程来说是原子操作或者多个线程之间的切换不会导致该接口的执行结果存在二义性,也就是说我们不用考虑同步的问题。
线程安全问题都是由全局变量及静态变量引起的:若每个线程中对全局变量、静态变量只有读操作,而无写操作,一般来说,这个全局变量是线程安全的;若有多个线程同时执行写操作,一般都需要考虑线程同步,否则的话就可能影响线程安全。
创建数据库连接
创建连接时需要使用到下面几个类:MongoClient、MongoServer、MongoDatabase、MongoCollection。
MongoClient类
该类是MongoDB服务器的根对象,表示MongoDB的客户端(实例)。数据库的连接是在后台自动进行的(通过使用连接池提高性能)。默认情况下,所有的写操作在服务器承认写之前都是阻塞的。
创建一个MongoClient最简单的方式就是使用Connection String,连接字符串具有如下的模式:
mongodb://[username:password@]hostname[:port][/[database][?options]]
[]表示该项是可选的;当在MongoDB服务器上启用了认证时,需要使用到用户名和密码;当username和database同时使用时,需要保证该用户有操作该数据库的权限;当不匹配时,默认的数据库是admin;如果不知道端口号,则默认端口号为27017。
当需要连接到多台服务器时,不同服务器之间使用”,”分割,且端口号不能省略,如:
mongodb://server1:27017,server1:27018
MongoServer类
使用MongoClient的GetServer()方法可以获取到MongoServer实例,该类的GetDatabase()方法可以根据数据库名获取到MongoDatabase实例。该方法具有如下的重载:
MongoDatabase GetDatabase(MongoDatabaseSettings settings)
MongoDatabase GetDatabase(string databaseName)
MongoDatabase GetDatabase(string databaseName, MongoCredentials credentials)
MongoDatabase GetDatabase(string databaseName, MongoCredentials credentials, WriteConcern writeConcern)
MongoDatabase GetDatabase(string databaseName, WriteConcern writeConcern)
下面的代码演示了如何连接到服务器并获取数据库:
MongoClient client = new MongoClient(); // 连接到本地
MongoServer server = client.GetServer();
MongoDatabase database = server.GetDatabase("test"); // 获取test数据库
MongoDatabase类
通过给定数据库名可以获取到MongoDB上的数据库,进而通过该类的GetCollection()方法根据集合名字获取MongoCollection<TDefaultDocument>实例。
MongoCollection类
通过给定集合名并使用MongoDatabase实例的GetCollection方法获取集合对象,一切的增删改查都是在集合对象的基础上实现的。
插入文档
通过MongoCollection<TDefaultDocument>的Insert<TDefaultDocument>()方法可以插入一个文档。TDefaultDocument可以是默认的BsonDocument类型也可以是用户自定义的类型。若是用户自定义的类型,则该类型中必须有Id字段或属性,或者手动指定主键的名称。
public class Book { // int Id{get;set;} string Author {get;set;} string Title {get;set;} } MongoCollection<Book> books = database.GetCollection<Book>("books"); // 获取集合名为books的集合 Book book = new Book { Author = "Ernest Hemingway", Title = "For Whom the Bell Tolls" }; books.Insert(book);
上述的自定义类型Book中没有定义id字段或属性,插入到集合时或默认给每天记录增加_id键,该键为ObjectId类型。插入时不会报错,但当我们从数据库获取数据并将数据转化为Book类型时,就会出错,原因是Book没有id字段,获取到的_id值不知道往哪里赋值。
批量插入
通过MongoCollection<TDefaultDocument>的InsertBatch ()方法可以进行文档的批量插入。
MongoCollection<BsonDocument> books; BsonDocument[] batch = { new BsonDocument { { "author", "Kurt Vonnegut" }, { "title", "Cat's Cradle" } }, new BsonDocument { { "author", "Kurt Vonnegut" }, { "title", "Slaughterhouse-Five" } } }; books.InsertBatch(batch);
批量插入比单个插入的效率要高,因为只进行一次连接请求和处理头信息。
上面的插入文档和批量插入演示了MongoDB C#驱动插入数据所支持的两种数据类型。总得来说自定义类型比较方便,而且符合ADO.NET和EF的习惯。但是使用自定义类涉及到序列化的问题,即我们可以控制类中的哪些字段或属性以什么样的规则与集合中的键对应。关于序列化的内容,可以参考官方文档:http://docs.mongodb.org/ecosystem/tutorial/use-csharp-driver/
数据查找方法:FindOne()和FindOneAs <TDefaultDocument>()
FindOne()方法是最简单的查找方法,该方法从集合中返回第一个找到的文档(当集合中有多个文档时,并不能知道会返回哪个文档)。
MongoCollection<Book> books;
Book book = books.FindOne();
FindOneAs <TDefaultDocument>()可以将查找到的文档转换成TdefaultDocument类型:
MongoCollection<Book> books;
BsonDocument document = books.FindOneAs<BsonDocument>();
FindAs <TDefaultDocument>()
这个方法通过查询文档作为参数,告诉服务器应该返回哪个文档。
查询文档是ImongoQuery类型,一般使用其实现类QueryDocument构造查询文档:
MongoCollection collection = database.GetCollection("user");
var query = new QueryDocument("key", "value");
collection.FindAs(typeof(BsonDocument), query);
查询文档的构造也可以通过MongoDB.Driver.Builders.Query进行构造,可以构造等值查询等不同类型的文档:
MongoCollection collection = database.GetCollection("user");
var query = Query.EQ("key", "value");
collection.FindAs(typeof(BsonDocument), query);
Save<TDocument>()
该方法组合了Insert方法和Update方法。当保存的文档中的Id值已存在时,则相当于Update()方法,否则相当于Insert()方法。
MongoCollection<BsonDocument> books;
var query = Query.And(
Query.EQ("author", "Kurt Vonnegut"),
Query.EQ("title", "Cats Craddle")
);
BsonDocument book = books.FindOne(query);
if (book != null) {
book["title"] = "Cat's Cradle";
books.Save(book);
}
Save方法的Tdocument类型必须要有Id字段,否则,只能使用Insert方法。
Update()方法
该方法用于更新已经存在的文档。上面使用Save方法进行的更新相当于下面的代码:
MongoCollection<BsonDocument> books;
var query = new QueryDocument {
{ "author", "Kurt Vonnegut" },
{ "title", "Cats Craddle" }
};
var update = new UpdateDocument {
{ "$set", new BsonDocument("title", "Cat's Cradle") }
};
BsonDocument updatedBook = books.Update(query, update);
也可以使用Query和Update构造器:
MongoCollection<BsonDocument> books;
var query = Query.And(
Query.EQ("author", "Kurt Vonnegut"),
Query.EQ("title", "Cats Craddle")
);
var update = Update.Set("title", "Cat's Cradle");
BsonDocument updatedBook = books.Update(query, update);
FindAndModify()
该方法会将查找到的文档进行修改,一般用于更新单个文档。也可以通过组合查询来匹配多个文档。
var jobs = database.GetCollection("jobs");
var query = Query.And(
Query.EQ("inprogress", false),
Query.EQ("name", "Biz report")
);
var sortBy = SortBy.Descending("priority");
var update = Update.
.Set("inprogress", true)
.Set("started", DateTime.UtcNow);
var result = jobs.FindAndModify(
query,
sortBy,
update,
true // return new document
);
var chosenJob = result.ModifiedDocument;
MapReduce()
Map/Reduce是一种从集合中聚合数据的方法。集合中的每个文档都会被传递到map函数,即emit,来产生中间值。而中间值会被传递到reduce函数进行聚合操作。
var map =
"function() {" +
" for (var key in this) {" +
" emit(key, { count : 1 });" +
" }" +
"}";
var reduce =
"function(key, emits) {" +
" total = 0;" +
" for (var i in emits) {" +
" total += emits[i].count;" +
" }" +
" return { count : total };" +
"}";
var mr = collection.MapReduce(map, reduce);
foreach (var document in mr.GetResults()) {
Console.WriteLine(document.ToJson());
}
其他的属性和方法
MongoCursor<TDocument>类:Find方法并不会立即的返回结果,而是返回一个能够枚举结果的游标;查询也不会立即的被发送到服务器上,而是在试图接收第一个结果时,才会被服务器处理。
枚举游标:通过foreach来枚举游标所指向的结果:
var query = Query.EQ("author", "Ernest Hemingway");
var cursor = books.Find(query);
foreach (var book in cursor) {
}
也可通过LINQ的拓展方法来枚举游标:
var query = Query.EQ("author", "Ernest Hemingway");
var cursor = books.Find(query);
var firstBook = cursor.FirstOrDefault();
var lastBook = cursor.LastOrDefault();
上面的方式,查询会被发送到服务器2次FirstOrDefault一次、LastOrDefault一次。
注意:当游标遍历完之后要记得释放(调用Dispose()方法)。
在枚举之前修改游标:在枚举游标之前可以修改游标的属性,有两种方式可以修改,一种是直接修改,另一种是通过Fluent接口设置属性。
// 跳过游标的前100条记录并只处理10条记录
var query = Query.EQ("status", "pending");
var cursor = tasks.Find(query);
cursor.Skip = 100;
cursor.Limit = 10;
foreach (var task in cursor) {
}
上面的实现也可以用下面的方式:
var query = Query.EQ("status", "pending");
foreach (var task in tasks.Find(query).SetSkip(100).SetLimit(10)) {
// do something with task
}
游标中可修改的属性:BatchSize、Fields 、Flags 、Limit 、Options 、SerializationOptions 、Skip、SlaveOk
其他的方法:Clone、Count、Explain、Size
WriteConcern类:新增、保存、更新和删除操作是否成功,查询操作可根据返回的查询结果判定(是否为空)。
Insert()返回WriteConcernResult其中比较重要的属性有:HasLastErrorMessage、LastErrorMessage、Response
Update()也返回WriteConcernResult其中DocumentsAffected表示被更新的文档数。UpdatedExisting表示是否有更新,其他的基本一致。
Save()和Remove()方法返回的WriteConcernResult与Update()一致。
为查找设置超时时间:collection.FindAll().SetMaxTime(TimeSpan.FromSeconds(1));当超过1秒钟,则查询会被中止。
批量操作(批量新增、更新、删除)
有两种方式可以进行批量操作,一种是对操作顺序执行,并返回第一个出错时的信息;一种是对操作并非执行,并返回所有出错的信息。
顺序执行的批量操作:
BulkWriteOperation bulk = collection.InitializeOrderedBulkOperation();
bulk.Insert(new BsonDocument("_id", 1));
bulk.Insert(new BsonDocument("_id", 2));
bulk.Insert(new BsonDocument("_id", 3));
bulk.Insert(new BsonDocument("_id", 4));
bulk.Find(Query.EQ("_id", 1)).Update(Update.Set("name", "wangdh1"));
bulk.Find(Query.EQ("_id", 2)).Remove();
bulk.Find(Query.EQ("_id", 3)).ReplaceOne(new BsonDocument("_id", 3).Add("name", "wangdh3"));
BulkWriteResult bulkResult = bulk.Execute();
并行执行的批量操作:
BulkWriteOperation bulk = collection.InitializeUnorderedBulkOperation();
bulk.Find(Query.EQ("_id", 1)).RemoveOne();
bulk.Find(Query.EQ("_id", 2)).RemoveOne();
BulkWriteResult bulkResult = bulk.Execute();
批量操作的返回值:BulkWriteResult
重要属性有:DeletedCount、InsertedCount、ModifiedCount、MatchedCount、ProcessedRequests分别表示删除、插入、更新、查找到的数量以及处理过程中产生的请求。
基础类:BSON命名空间
BSON类库是C#驱动的基础,处理的细节包括:I/O、序列化和BSON文档的内存对象模型。重要的BSON对象模型有:BsonType、BsonValue、BsonElement、BsonDocument、BsonArray.
BsonType:表示Bson的类型,是枚举类型。
BsonValue:Bson的value的抽象表示
BsonElement:是一个name/value表示的键值对类型
BsonDocument:是BsonElement(键值对)集合
创建内嵌的 BsonDocument:
BsonDocument nested = new BsonDocument {
{ "name", "John Doe" },
{ "address", new BsonDocument {
{ "street", "123 Main St." },
{ "city", "Centerville" },
{ "state", "PA" },
{ "zip", 12345}
}}
};
处理BsonDocument:既然BsonDocument是键值对集合,即可通过键名获取对应的值,再通过AsXXX方法将获取到的值转换为相应的类型。
BsonDocument book;
string author = book["author"].AsString;
DateTime publicationDate = book["publicationDate"].AsDateTime;
int pages = book["pages", -1].AsInt32;
BsonArray:Bson数组