MongoDB系列(二):C#應用


前言

  上一篇文章《MongoDB系列(一):簡介及安裝》已經介紹了MongoDB以及其在window環境下的安裝,這篇文章主要講講如何用C#來與MongoDB進行通訊。再次強調一下,我使用的MongoDB版本是2.6,因為2.6是我最熟悉的版本,而且我使用的GUI工具Robomongo目前還不支持3.0版本。

 

添加官方驅動

  官方驅動可以從Nuget上獲取,但是這里我們不使用最新的驅動,而是使用1.9.2這個版本,個人認為該版本對MongoDB2.6的支持最好,而且目前的下載量也是最多。驅動地址:https://www.nuget.org/packages/mongocsharpdriver/1.9.2。因此,需要在程序包管理器中獲取Nuget。

打開“程序包管理器中”

  

輸入指令Install-Package mongocsharpdriver -Version 1.9.2,下載添加驅動

 

連接字符串

mongodb://[username:password@]host1[:port1][,host2[:port2],…[,hostN[:portN]]][/[database][?options]]

內容

描述

mongodb://

是連接字串必須的前綴字串

username:password@

可選項,連接到數據庫后會嘗試驗證登陸

host1

必須的指定至少一個host

:portX

可選項,默認連接到27017

/database

如果指定username:password@,連接並驗證登陸指定數據庫。若不指定,默認打開admin數據庫。

?options

是連接選項。如果不使用/database,則前面需要加上/。所有連接選項都是鍵值對name=value,鍵值對之間通過&或;(分號)隔開

 

C#驅動提供的常用API

方法

描述

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#的異常或者自定義的異常。這就說明了,操作要滿足兩個條件才算成功:一、無異常,二、返回值標識成功。

 

Repository方式

  來到這里,應該有很多人會問,為什么還要用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的話應該會更好。

 

源碼下載

  下載地址:https://github.com/ErikXu/MongoDBUsage


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM