本文章是由機器翻譯。

程式設計師雜談

使用 MongoDB 投入 NoSQL 的懷抱 (第 2 部)

Ted Neward

下載範例程式碼

在我 前一篇文章 ,MongoDB ’s 基本概念花了前面中心:取得它安裝,執行] 及 [插入] 和 [尋找資料。但是,我涵蓋基本概念 — 使用的資料物件是簡單的名稱/值組。進行合理,因為 MongoDB ’s 「 sweet 特別 」 包括非結構化和相對簡單的資料結構。但是,這個資料庫一定可以儲存多個只是簡單的名稱/值組。

這個本文中,我們使用稍微不同的方法加以調查 MongoDB (或任何技術)。程序呼叫已探索的測試將幫助我們在伺服器中尋找可能的錯誤,並在的方式以及反白物件導向的開發人員將會執行到使用 MongoDB 時常見的問題。

在我們上次的劇集 …

首先我們請確定我們 ’re 都在相同的頁面上,而且我們也涵蓋一些稍有新地面。let’s 看看 MongoDB 稍微結構化的方式比我們未在前一篇文章 ( msdn.microsoft.com/magazine/ee310029 )。只要建立簡單的應用程式,並在其上劈砍,非 let’s 以內的結束兩個鳥石頭而建立探索測試 — 程式碼片段的樣子單元測試,但的瀏覽功能而嘗試確認它。

研究新的技術時,撰寫探索測試做幾個不同的用途。一個,有助於找出在研究技術是否原本就可測試 (假設,如果它 ’s 難探索測試、 要很難單元測試 — 極大的紅色標幟)。兩個,它們做為排序的迴歸分析時在研究技術的新版本是來自出,因為他們提供一個 heads-up 如果舊的功能不再正常運作。然後三,因為測試應該原本就是相當小,且細微,探索測試進行學習更容易的技術,藉由建立前面幾個案例所建立的新 「 what-if 」 案例。

但不像單位廠商的測試探索測試 aren’t 持續開發與的應用程式一起因此一旦您考慮學到的技術,設定測試一邊。不過 don’t 捨棄它們,— 它們也可以協助程式庫或架構中分隔應用程式程式碼中的錯誤。測試會執行,藉由提供輕量型、 應用程式中性的環境,而不需額外負荷的實驗的應用程式。

有了,記住 let’s 建立 MongoDB 的-探索,Visual C# 測試專案。您可以新增 MongoDB.Driver.dll 至清單中的組件參考和建置並確定一切就 OK 了。(建置應該拿起一個的 TestMethod 產生專案範本的一部份。它會傳遞預設,所以一切都應該是個好,這表示,如果專案建置失敗、 東西螺杆式環境中。檢查的假設是一定很好的事)。

為 tempting 跳到應立即撰寫程式碼時,它就是如,不過,問題 surfaces 相當快速:MongoDB 需要外部的伺服器處理序 (mongod.exe),用戶端程式碼,可以對它連線,並執行任何有用的動作之前執行。雖然它 ’s 誘人只需說出 「 正常運作正常,let’s 啟動它,回到撰寫程式碼 」,有 ’s corollary 的問題。它 ’s 幾乎確定的辦法,有些時候 15 週稍後當回頭看這個程式碼 (您,我或一小組) 將會試著執行這些的測試某些不佳的開發人員看到全部失敗,並放棄嘗試找出什麼她認為來查看是否執行伺服器之前,請先將的兩個或三天。

課程:請試著以某種方式擷取測試的所有相依性。將仍在單元測試期間,一次發生問題。此時我們需要從全新的伺服器啟動、 進行一些修訂,然後復原它們全部。’s 最簡單的方式達成只需停止及啟動伺服器,所以解決現在儲存之後的時間。

這個概念的測試之前,請先執行的項目 (或後,或兩者) isn’t 新,一個,和 Microsoft 測試和實驗室管理員專案可有每個測試和每個測試套件的初始設定式和清除方法。這些是由自訂屬性 ClassInitialize 和 ClassCleanup 每個測試套件簿記及 TestInitialize TestCleanup 的每個測試簿記 adorned。(請參閱 「 使用單元測試 」 以取得詳細資訊)。因此,每個測試套件的初始設定式會啟動 mongod.exe] 程序,並將每個測試套件清除會關機程序, 的 圖 1 所示。

圖 1 部分程式碼測試的初始設定式和清理

namespace MongoDB_Explore
{
  [TestClass]
  public class UnitTest1
  {
    private static Process serverProcess;

   [ClassInitialize]
   public static void MyClassInitialize(TestContext testContext)
   {
     DirectoryInfo projectRoot = 
       new DirectoryInfo(testContext.TestDir).Parent.Parent;
     var mongodbbindir = 
       projectRoot.Parent.GetDirectories("mongodb-bin")[0];
     var mongod = 
       mongodbbindir.GetFiles("mongod.exe")[0];

     var psi = new ProcessStartInfo
     {
       FileName = mongod.FullName,
       Arguments = "--config mongo.config",
       WorkingDirectory = mongodbbindir.FullName
     };

     serverProcess = Process.Start(psi);
   }
   [ClassCleanup]
   public static void MyClassCleanup()
   {
     serverProcess.CloseMainWindow();
     serverProcess.WaitForExit(5 * 1000);
     if (!serverProcess.HasExited)
       serverProcess.Kill();
  }
...

第一次這樣會執行,對話方塊將快顯通知正在啟動處理程序的使用者。 按一下 [確定],會使 [離開] 對話方塊... 在下一次測試之前執行。 一旦該對話方塊取得太令人討厭的尋找指出選項方塊、 「 不要再顯示此對話方塊 」,並檢查它讓訊息消失的好。 如果正在執行防火牆軟體,Windows 防火牆,例如對話方塊可能將這裡的外觀也,因為伺服器想要開啟接收用戶端連線的連接埠。 套用相同的處理方式,一切都應該以無訊息模式執行。 第一行的清除程式碼,來確認伺服器執行視中放置中斷點。

一旦伺服器執行測試可以開始引發 — 除非另一個問題的介面:想要使用自己的全新資料庫,但它 ’s 的每個測試很有幫助,有些已存在的資料,以進行測試的特定事件的資料庫 (查詢,例如) 更為容易。 如果每個測試都可以有自己的全新組的既有的資料,則它會是好。 那將是 TestInitializer 和 TestCleanup adorned 方法的角色。

但我們取得的 let’s 看看會嘗試確保伺服器可以找到,連線,進行和物件插入,找到並移除,此快速 TestMethod 之前將研究測試最新 100X 與我們在前一篇文章涵蓋的內容 (請參閱 的 圖 2)。

圖 2 ,請確定該伺服器可以找 TestMethod 及連線,所做

[TestMethod]
public void ConnectInsertAndRemove()
{
  Mongo db = new Mongo();
  db.Connect();

  Document ted = new Document();
  ted["firstname"] = "Ted";
  ted["lastname"] = "Neward";
  ted["age"] = 39;
  ted["birthday"] = new DateTime(1971, 2, 7);
  db["exploretests"]["readwrites"].Insert(ted);
  Assert.IsNotNull(ted["_id"]);

  Document result =
    db["exploretests"]["readwrites"].FindOne(
    new Document().Append("lastname", "Neward"));
  Assert.AreEqual(ted["firstname"], result["firstname"]);
  Assert.AreEqual(ted["lastname"], result["lastname"]);
  Assert.AreEqual(ted["age"], result["age"]);
  Assert.AreEqual(ted["birthday"], result["birthday"]);

  db.Disconnect();
}

如果此程式碼執行時,它 trips 判斷提示,測試就會失敗。 在就特別引發最後的判斷提示,周圍 「 生日 」。 因此就顯然 MongoDB 資料庫,而不需一次傳送一個日期時間 doesn’t 往返作業很正確。 資料型別會相關聯的午夜時間的日期,但傳回的相關聯的時間為 8 上午點中斷 AreEqual 判斷提示,在測試結束的日期。

這會反白顯示的探索測試可用性 — 沒有它 (做為最的情況下,就例如與前一篇文章從程式碼),這個小 MongoDB 特性可能已經人直到週數或月到專案。 這是否 MongoDB 伺服器的錯誤是值判斷並不是要現在探索。 重點是探索測試放在幫助您找出這個 「 有趣 」 行為的顯微鏡技術。 可讓開發人員尋找作為技術產生它們自己的決策到是否這是中斷變更。 forewarned forearmed。

修正程式碼,因此通過測試,順帶需要回來自資料庫,以轉換成當地時間的日期時間。 我帶這出在線上的論壇,並根據 MongoDB.Driver] 作者的 [Sam Corder,從回應 「 所有進行的日期轉換為 UTC 與 UTC 光顧但左邊 」因此您必須不論是將日期時間轉換成 UTC 時間之前儲存透過 DateTime.ToUniversalTime,或是轉換任何日期時間從資料庫擷取到本地時區,透過 DateTime.ToLocalTime,藉由使用下列程式碼範例:

Assert.AreEqual(ted["birthday"], 
  ((DateTime)result["birthday"]).ToLocalTime());

這本身中醒目提示的社群盡最大努力最大的優點之一 — 通常牽涉到的主體都只有一封電子郵件離開。

增加的複雜性

尋找要使用 MongoDB 需要瞭解的 contrary 來初始的外觀的開發人員 isn’t 物件資料庫 — 也就是它 can’t 處理複雜的物件圖形沒有說明。 有幾個處理方法提供該的說明,但為止進行開發人員 ’s 肩上的有保留的慣例。

就例如考慮 圖 3 ,簡單的設計來反映數描述已知的系列文件的儲存體的物件集合。 到目前為止,好。 在就其實時它 ’s 在它,測試真的應該查詢資料庫中的插入,這些物件只以確定它們 ’re 擷取 圖 4 中所示。 和 … 通過測試。 實用的。

圖 3 的 一個簡單物件集合

[TestMethod]
public void StoreAndCountFamily()
{
  Mongo db = new Mongo();
  db.Connect();

  var peter = new Document();
  peter["firstname"] = "Peter";
  peter["lastname"] = "Griffin";

  var lois = new Document();
  lois["firstname"] = "Lois";
  lois["lastname"] = "Griffin";

  var cast = new[] {peter, lois};
  db["exploretests"]["familyguy"].Insert(cast);
  Assert.IsNotNull(peter["_id"]);
  Assert.IsNotNull(lois["_id"]);

  db.Disconnect();
}

圖 4 查詢物件的資料庫

[TestMethod]
public void StoreAndCountFamily()
{
  Mongo db = new Mongo();
  db.Connect();

  var peter = new Document();
  peter["firstname"] = "Peter";
  peter["lastname"] = "Griffin";

  var lois = new Document();
  lois["firstname"] = "Lois";
  lois["lastname"] = "Griffin";

  var cast = new[] {peter, lois};
  db["exploretests"]["familyguy"].Insert(cast);
  Assert.IsNotNull(peter["_id"]);
  Assert.IsNotNull(lois["_id"]);

  ICursor griffins =
    db["exploretests"]["familyguy"].Find(
      new Document().Append("lastname", "Griffin"));
  int count = 0;
  foreach (var d in griffins.Documents) count++;
  Assert.AreEqual(2, count);

  db.Disconnect();
}

實際,可能不會完全,則為 True (之後的家,並輸入程式碼可能會發現當它所宣稱的物件預期的計數 isn’t 比對 2 測試 doesn’t 會之後就所有傳遞的讀取器。 這是因為這一個在引動過程,如資料庫都預期要保留狀態,因為測試程式碼 isn’t 明確地移除這些物件,它們都是透過測試。

這會反白顯示 [文件方向] 資料庫的另一項的功能:重複的項目完全預期,並允許。 ’s 為什麼每一個記錄、 一次插入、 是以 implicit_id 屬性標記並指定唯一的識別碼來儲存在它,其作用中成為文件 ’s 主索引鍵。

因此,如果測試要傳遞,資料庫需要每個測試回合之前被清除。 雖然它 ’s 很簡單,只需刪除其中 MongoDB 儲存它們在目錄中的檔案,重新,具有自動測試套件的一部分進行這最好大幅。 每個測試可以請手動取得經過一段時間會有點繁瑣的完成之後。 或測試程式碼可以利用 TestInitialize] 和 [TestCleanup] 功能的 Microsoft 測試和實驗室管理員擷取通用的程式碼 (以及為什麼不包含資料庫連接和中斷連接邏輯) 的 [圖 5] 所示。

圖 5 受到 TestInitialize] 和 [TestCleanup 優點

private Mongo db;

[TestInitialize]
public void DatabaseConnect()
{
  db = new Mongo();
  db.Connect();
}
        
[TestCleanup]
public void CleanDatabase()
{
  db["exploretests"].MetaData.DropDatabase();

  db.Disconnect();
  db = null;
}

雖然 CleanDatabase 方法的最後一行是不必要的因為下一個測試會覆寫欄位參考與新 Mongo 物件,有時 ’s 最好也清楚地參考不再是好的。 警告 emptor. 重要的是測試 dirtied 資料庫被卸除,清空 MongoDB 用來儲存資料的檔案,並讓所有項目,全新和 sparkly 為下一個測試的清除。

但是,事情突顯如家族的模型不完整 (兩個參考的人都有的一些且給定的應該有 spouses,為彼此參考下列所示:

peter["spouse"] = lois;
  lois["spouse"] = peter;

執行此測試,不過中, 產生一個 StackOverflowException — MongoDB 驅動程式序列化程式原本 doesn’t 瞭解概念的循環參考,並且 naively 遵循 臨 infinitum 周圍的參考。 糟糕。 不好。

修正這需要您選取兩個選項之一。 具有一個,配偶欄位可以填入其他文件 ’s _id 欄位 (一旦插入該文件) 和已 的 圖 6 所示更新。

圖 6 克服循環參考問題

[TestMethod]
public void StoreAndCountFamily()
{
  var peter = new Document();
  peter["firstname"] = "Peter";
  peter["lastname"] = "Griffin";

  var lois = new Document();
  lois["firstname"] = "Lois";
  lois["lastname"] = "Griffin";

  var cast = new[] {peter, lois};
  var fg = db["exploretests"]["familyguy"];
  fg.Insert(cast);
  Assert.IsNotNull(peter["_id"]);
  Assert.IsNotNull(lois["_id"]);

  peter["spouse"] = lois["_id"];
  fg.Update(peter);
  lois["spouse"] = peter["_id"];
  fg.Update(lois);

  Assert.AreEqual(peter["spouse"], lois["_id"]);
  TestContext.WriteLine("peter: {0}", peter.ToString());
  TestContext.WriteLine("lois: {0}", lois.ToString());
  Assert.AreEqual(
    fg.FindOne(new Document().Append("_id",
    peter["spouse"])).ToString(),
    lois.ToString());

  ICursor griffins =
    fg.Find(new Document().Append("lastname", "Griffin"));
  int count = 0;
  foreach (var d in griffins.Documents) count++;
  Assert.AreEqual(2, count);
}

不過有 ’s 最的方式的缺點:它需要在文件插入資料庫,和其 _id 值 (也就是 Oid] 執行個體中 MongoDB.Driver parlance) 複製到適當的每一個物件的 [配偶] 欄位。 然後會再次更新每份文件。 雖然有傳統的 RDBMS 更新以比較快速 MongoDB 資料庫的往返,這個方法仍然是有點浪費的。

第二種方法是預先產生 Oid 值,每份文件、 填入配偶] 欄位並再將整個批次傳送至的資料庫,如 的 [圖 7] 所示。

圖 7 的較佳方式,以解決循環參照問題

[TestMethod]
public void StoreAndCountFamilyWithOid()
{
  var peter = new Document();
  peter["firstname"] = "Peter";
  peter["lastname"] = "Griffin";
  peter["_id"] = Oid.NewOid();

  var lois = new Document();
  lois["firstname"] = "Lois";
  lois["lastname"] = "Griffin";
  lois["_id"] = Oid.NewOid();

  peter["spouse"] = lois["_id"];
  lois["spouse"] = peter["_id"];

  var cast = new[] { peter, lois };
  var fg = db["exploretests"]["familyguy"];
  fg.Insert(cast);

  Assert.AreEqual(peter["spouse"], lois["_id"]);
  Assert.AreEqual(
    fg.FindOne(new Document().Append("_id",
    peter["spouse"])).ToString(),
    lois.ToString());

  Assert.AreEqual(2, 
    fg.Count(new Document().Append("lastname", "Griffin")));
}

這種方法需要只插入方法,因為現在 Oid 值已知的時間之前。順帶注意,ToString 呼叫,在判斷提示測試是故意 — 這個的方式,文件會轉換成字串之前進行比較。

什麼 ’s 真正重要注意到 的 圖 7 中程式碼,不過,,de-referencing 透過 [Oid 參考文件能相當困難且冗長乏味,因為文件導向的樣式會假設文件更多或更少是獨立或階層式實體,物件 Graph。(請注意.NET 驅動程式提供提供稍微更豐富的方法的參考/解除參照另一個文件的 DBRef,但它仍然不打算將此設成物件 Graph 方便系統)。因此時它肯定 ’s 可能 ,採取豐富的物件模型,並將它儲存到 MongoDB 資料庫,, 它 ’s 不 建議 。球桿緊密地儲存叢集群組的指導的比喻為使用 Word 或 Excel 文件的資料。然後如果項目可以被視為大的文件或試算表,它可能 ’s MongoDB 或某些其他文件導向資料庫的正確大小。

若要瀏覽更多

我們完成的 MongoDB,我們調查,但是我們結束之前先有多個事項瀏覽,包括執行述詞的查詢、 彙總、 支援 LINQ 和一些實際執行的系統管理附註。我們處理下一個月份。(此文件移至是非常忙碌的片段]!)在此同時瀏覽 MongoDB] 系統,而且一定要卸除我的電子郵件與未來資料行的建議。

Ted Neward  是以 Neward 的主體與夥伴專為獨立公司精於企業.NET Framework 和 Java 平台的系統。他有寫入 100 個以上的文件一個 C# MVP,INETA 喇叭和作者或 coauthor 十幾的書籍的包括在眼前 「 專業 F # 2.0 」 (Wrox)。他諮詢,並定期 mentors。ted@tedneward.com 在到達他和讀取他的部落格,在 blogs.tedneward.com

感謝至下列的技術專家來檢閱這份文件: Sam Corder