上一篇文章《MongoDB系列(一):簡介及安裝》已經介紹了MongoDB以及其在window環境下的安裝,這篇文章主要講講如何用C#來與MongoDB進行通信。再次強調一下,我使用的MongoDB版本是2.6,由於2.6是我最熟悉的版本,並且我使用的GUI工具Robomongo目前還不支持3.0版本。html
官方驅動能夠從Nuget上獲取,可是這裏咱們不使用最新的驅動,而是使用1.9.2這個版本,我的認爲該版本對MongoDB2.6的支持最好,並且目前的下載量也是最多。驅動地址:https://www.nuget.org/packages/mongocsharpdriver/1.9.2。所以,須要在程序包管理器中獲取Nuget。c++
打開「程序包管理器中」git
輸入指令Install-Package mongocsharpdriver -Version 1.9.2,下載添加驅動github
mongodb://[username:password@]host1[:port1][,host2[:port2],…[,hostN[:portN]]][/[database][?options]]mongodb
內容數據庫 |
描述設計模式 |
mongodb://框架 |
是鏈接字串必須的前綴字串工具 |
username:password@單元測試 |
可選項,鏈接到數據庫後會嘗試驗證登錄 |
host1 |
必須的指定至少一個host |
:portX |
可選項,默認鏈接到27017 |
/database |
若是指定username:password@,鏈接並驗證登錄指定數據庫。若不指定,默認打開admin數據庫。 |
?options |
是鏈接選項。若是不使用/database,則前面須要加上/。全部鏈接選項都是鍵值對name=value,鍵值對之間經過&或;(分號)隔開 |
方法 |
描述 |
InsertBatch |
批量插入 |
Insert |
單條插入 |
FindOneById |
按Id查詢 |
Save |
保存,若是庫中有記錄則更新,不然作插入,按Id匹配 |
Remove |
刪除指定文檔 |
AsQueryable |
返回IQueryable<T>對象 |
Update |
更新一個或多個文檔 |
RemoveAll |
刪除全部記錄 |
… |
其它 |
抽象實體類Entity
public abstract class EntityWithTypedId<TId> { public TId Id { get; set; } } public abstract class Entity : EntityWithTypedId<ObjectId> { }
MongoDB要求每一個集合都須要有一個Id,即便你定義的類中沒有Id字段,存數據的時候也會生成一個Id,並且Id的類型默認是使用ObjectId,固然也可使用其餘簡單類型做爲Id,如int。
核心代碼封裝DbContext
public class DbContext { private readonly MongoDatabase _db; public DbContext() { var client = new MongoClient("mongodb://localhost:27017"); var server = client.GetServer(); _db = server.GetDatabase("Temp"); } public MongoCollection<T> Collection<T>() where T : Entity { var collectionName = InferCollectionNameFrom<T>(); return _db.GetCollection<T>(collectionName); } private static string InferCollectionNameFrom<T>() { var type = typeof(T); return type.Name; } }
1. 經過鏈接字符串與數據庫創建鏈接。
2. 獲取須要操做的Database,這裏是Temp。
3. 類名與Collection名一致,做爲映射的約束。若是庫中沒有這個Collection,則建立該Collection,若是有,則操做該Collection。
定義一個股票類Stock,包含股票代碼,股票名稱,股票價格等簡單類型字段以及股票粉絲複雜字段:
public class Stock : Entity { public string Symbol { get; set; } public string Name { get; set; } public double Price { get; set; } public List<Follower> Followers { get; set; } } public class Follower { public string Name { get; set; } public int Age { get; set; } }
代碼調用
static void Main() { SetConvention(); var db = new DbContext(); var collection = db.Collection<Stock>(); var stocks = new List<Stock> { new Stock { Symbol = "000001", Name = "股票1", Price = 100, Followers = new List<Follower> { new Follower{ Name = "張三", Age = 20 }, new Follower{ Name = "李四", Age = 22 }, new Follower{ Name = "王五", Age = 23 } } }, new Stock { Symbol = "000002", Name = "股票2", Price = 200, Followers = new List<Follower> { new Follower{ Name = "張三", Age = 20 }, new Follower{ Name = "李四", Age = 22 } } }, new Stock { Symbol = "000003", Name = "股票3", Price = 300, Followers = new List<Follower> { new Follower{ Name = "張三", Age = 20 } } }, new Stock { Id = ObjectId.GenerateNewId(), //這裏能夠本身設定Id,也能夠不設,不設的話操做後會自動分配Id Symbol = "000004", Name = "股票4", Price = 400 } }; Console.WriteLine("批量插入"); var results = collection.InsertBatch(stocks); Console.WriteLine(results.Count()); //這裏返回的是1,挺奇怪的。 Console.WriteLine(); var stock = new Stock { Id = ObjectId.GenerateNewId(), //這裏能夠本身設定Id,也能夠不設,不設的話操做後會自動分配Id Symbol = "000005", Name = "股票5", Price = 500 }; Console.WriteLine("單條插入"); var result = collection.Insert(stock); Console.WriteLine("插入是否成功:{0}", result.Ok); Console.WriteLine(); Console.WriteLine("經過Id檢索"); var findedStock = collection.FindOneById(BsonValue.Create(stock.Id)); Console.WriteLine("Symbol:{0}, Name:{1}, Price:{2}", findedStock.Symbol, findedStock.Name, findedStock.Price); Console.WriteLine(); Console.WriteLine("保存操做,庫裏有數據"); stock.Symbol = "000006"; result = collection.Save(stock); Console.WriteLine("保存是否成功:{0}", result.Ok); Console.WriteLine(); Console.WriteLine("刪除"); result = collection.Remove(Query<Stock>.EQ(n => n.Id, stock.Id)); Console.WriteLine("刪除是否成功:{0}", result.Ok); Console.WriteLine(); Console.WriteLine("保存操做,庫裏沒數據"); result = collection.Save(stock); Console.WriteLine("保存是否成功:{0}", result.Ok); Console.WriteLine(); Console.WriteLine("簡單查詢"); var list = collection.AsQueryable().Where(n => n.Price >= 300).ToList(); Console.WriteLine("查詢結果條數:{0}", list.Count); Console.WriteLine(); Console.WriteLine("複雜類型查詢"); list = collection.AsQueryable().Where(n => n.Followers.Any(f => f.Name == "王五")).ToList(); Console.WriteLine("查詢結果條數:{0}", list.Count); Console.WriteLine(); Console.WriteLine("批量更新"); var query = Query<Stock>.Where(n => n.Price >= 300); var update = Update<Stock>.Set(n => n.Name, "股票300") .Set(n => n.Price, 299); result = collection.Update(query, update, UpdateFlags.Multi); Console.WriteLine("批量更新是否成功:{0}", result.Ok); Console.WriteLine(); Console.WriteLine("批量刪除"); result = collection.Remove(Query<Stock>.Where(n => n.Price >= 299)); Console.WriteLine("批量刪除更新是否成功:{0}", result.Ok); Console.WriteLine(); Console.WriteLine("刪除全部記錄"); result = collection.RemoveAll(); Console.WriteLine("刪除全部記錄是否成功:{0}", result.Ok); Console.WriteLine(); Console.ReadKey(); }
全局公約設置
private static void SetConvention() { var pack = new ConventionPack {new IgnoreExtraElementsConvention(true), new IgnoreIfNullConvention(true)}; ConventionRegistry.Register("IgnoreExtraElements&IgnoreIfNull", pack, type => true); }
1. IgnoreExtraElementsConvention:忽略庫中有可是類中沒有定義的字段。這個通常用於敏感字段處理,例如密碼字段,它會存在用戶Collection中,可是這個字段只是登陸校驗的時候會用到(這時能夠用js來查詢),其餘用戶查詢(linq查詢)基本都不須要用到密碼字段。
2. IgnoreIfNullConvention:若是字段null,則不存這個字段,簡單來講就是省空間,假設一個類中有A,B兩個字段,其中A字段爲空,若是指定該設置,存爲:{B:'B'},不然,存爲{A:null, B:'B'}。
返回值說明
爲何MongoDB提供的API基本都有返回值?那若是API中出現的異常怎麼處理,被MongoDB吃掉了?
這裏我查看了MongoDB的驅動源碼,它的結果是經過執行getLastError的方式來獲取的,這是c++的方式,C#沒法捕捉到這些異常,所以,返回值標識着操做是否成功。同時,API中也會拋出C#的異常或者自定義的異常。這就說明了,操做要知足兩個條件纔算成功:1、無異常,2、返回值標識成功。
來到這裏,應該有不少人會問,爲何還要用Repository?特別是接觸過Entity Framework,由於Entity Framework代表已包含了Unit of Work和Repository,或者是看過《博客園的大牛們,被大家害慘了,Entity Framework歷來都不須要去寫Repository設計模式》這類文章的童鞋。
首先,我須要代表立場,對於不使用Repository的觀點,我是百分之八九十贊同的。那爲何還要用呢?不爲何,我就是任性(開個玩笑)!我認爲作技術的不能太偏執,仍是要根據具體的場景和需求,技術和框架沒有絕對好的,只有相對好的。技術是發展的,但技術不可能面面俱到。
那麼爲何要用Repository呢?由於我要寫單元測試,我須要經過Mock的方式拋開數據庫訪問的依賴,要Mock的話,要經過接口或虛方法(virtual)。如今的EF 6確實包含了Repository的思想,可是直接用dbContext的話,仍是沒法Mock(若是可Mock,請告之),所以須要用Repository來包裝一下。就好像當你須要測試一個internal的類的時候,你須要定義一個public的類包一下這個內部類。
所以,若是你須要寫單元測試的話,那麼你應該須要Repository,不然,以爲怎麼爽就怎麼用吧!
通用接口IRepository
public interface IRepositoryWithTypedId<T, in TId> where T : EntityWithTypedId<TId> { IEnumerable<bool> InsertBatch(IEnumerable<T> entities); bool Insert(T entity); T Get(TId id); bool Save(T entity); bool Delete(TId id); IQueryable<T> AsQueryable(); bool RemoveAll(); } public interface IRepository<T> : IRepositoryWithTypedId<T, ObjectId> where T : Entity { }
通用實現MongoRepository
public class MongoRepositoryWithTypedId<T, TId> : IRepositoryWithTypedId<T, TId> where T : EntityWithTypedId<TId> { private readonly MongoCollection<T> _collection; public MongoRepositoryWithTypedId() { var client = new MongoClient("mongodb://localhost:27017"); var server = client.GetServer(); var db = server.GetDatabase("Temp"); var collectionName = InferCollectionNameFrom(); _collection = db.GetCollection<T>(collectionName); } private string InferCollectionNameFrom() { var type = typeof(T); return type.Name; } protected internal MongoCollection<T> Collection { get { return _collection; } } public IEnumerable<bool> InsertBatch(IEnumerable<T> entities) { var result = Collection.InsertBatch(entities); return result.Select(n => n.Ok); } public bool Insert(T entity) { var result = Collection.Insert(entity); return result.Ok; } public T Get(TId id) { return Collection.FindOneById(BsonValue.Create(id)); } public bool Save(T entity) { var result = Collection.Save(entity); return result.Ok; } public bool Delete(TId id) { var result = Collection.Remove(Query<T>.EQ(t => t.Id, id)); return result.Ok; } public IQueryable<T> AsQueryable() { return Collection.AsQueryable(); } public bool RemoveAll() { var result = Collection.RemoveAll(); return result.Ok; } } public class MongoRepository<T> : MongoRepositoryWithTypedId<T, ObjectId>, IRepository<T> where T : Entity { }
股票接口IStockRepository
public interface IStockRepository : IRepository<Stock> { bool UpdateBatch(double minPrice, string name, double price); bool DeleteBatch(double minPrice); }
注:若是通用方法足夠用的話,可不須要自定義接口,直接使用IRepository<T>。 如IRepository<Stock> repository = new MongoRepository<Stock>();
股票接口實現StockRepository
public class StockRepository : MongoRepository<Stock>, IStockRepository { public bool UpdateBatch(double minPrice, string name, double price) { var query = Query<Stock>.Where(n => n.Price >= minPrice); var update = Update<Stock>.Set(n => n.Name, name) .Set(n => n.Price, price); var result = Collection.Update(query, update, UpdateFlags.Multi); return result.Ok; } public bool DeleteBatch(double minPrice) { var result = Collection.Remove(Query<Stock>.Where(n => n.Price >= minPrice)); return result.Ok; } }
代碼調用
static void Main() { SetConvention(); var repository = new StockRepository(); var stocks = new List<Stock> { new Stock { Symbol = "000001", Name = "股票1", Price = 100, Followers = new List<Follower> { new Follower{ Name = "張三", Age = 20 }, new Follower{ Name = "李四", Age = 22 }, new Follower{ Name = "王五", Age = 23 } } }, new Stock { Symbol = "000002", Name = "股票2", Price = 200, Followers = new List<Follower> { new Follower{ Name = "張三", Age = 20 }, new Follower{ Name = "李四", Age = 22 } } }, new Stock { Symbol = "000003", Name = "股票3", Price = 300, Followers = new List<Follower> { new Follower{ Name = "張三", Age = 20 } } }, new Stock { Id = ObjectId.GenerateNewId(), //這裏能夠本身設定Id,也能夠不設,不設的話操做後會自動分配Id Symbol = "000004", Name = "股票4", Price = 400 } }; Console.WriteLine("批量插入"); var results = repository.InsertBatch(stocks); Console.WriteLine(results.Count()); Console.WriteLine(); var stock = new Stock { Id = ObjectId.GenerateNewId(), //這裏能夠本身設定Id,也能夠不設,不設的話操做後會自動分配Id Symbol = "000005", Name = "股票5", Price = 500 }; Console.WriteLine("單條插入"); var result = repository.Insert(stock); Console.WriteLine("插入是否成功:{0}", result); Console.WriteLine(); Console.WriteLine("經過Id檢索"); var findedStock = repository.Get(stock.Id); Console.WriteLine("Symbol:{0}, Name:{1}, Price:{2}", findedStock.Symbol, findedStock.Name, findedStock.Price); Console.WriteLine(); Console.WriteLine("保存操做,庫裏有數據"); stock.Symbol = "000006"; result = repository.Save(stock); Console.WriteLine("保存是否成功:{0}", result); Console.WriteLine(); Console.WriteLine("刪除"); result = repository.Delete(stock.Id); Console.WriteLine("刪除是否成功:{0}", result); Console.WriteLine(); Console.WriteLine("保存操做,庫裏沒數據"); result = repository.Save(stock); Console.WriteLine("保存是否成功:{0}", result); Console.WriteLine(); Console.WriteLine("簡單查詢"); var list = repository.AsQueryable().Where(n => n.Price >= 300).ToList(); Console.WriteLine("查詢結果條數:{0}", list.Count); Console.WriteLine(); Console.WriteLine("複雜類型查詢"); list = repository.AsQueryable().Where(n => n.Followers.Any(f => f.Name == "王五")).ToList(); Console.WriteLine("查詢結果條數:{0}", list.Count); Console.WriteLine(); Console.WriteLine("批量更新"); result = repository.UpdateBatch(300, "股票300", 299); Console.WriteLine("批量更新是否成功:{0}", result); Console.WriteLine(); Console.WriteLine("批量刪除"); result = repository.DeleteBatch(299); Console.WriteLine("批量刪除更新是否成功:{0}", result); Console.WriteLine(); Console.WriteLine("刪除全部記錄"); result = repository.RemoveAll(); Console.WriteLine("刪除全部記錄是否成功:{0}", result); Console.WriteLine(); Console.ReadKey(); }
注:我這裏沒有提供Unit of Work的實現,由於我認爲MongoDB對鏈接的處理比關係型好,固然用Unit of Work的話應該會更好。