NoSQL 文件資料庫

將 RavenDB 內嵌至 ASP.NET MVC 3 應用程式中

Justin Schwartzenberger

下載程式碼範例

注意 NoSQL 移動到 Microsoft 內部變大。NET Framework 社群,隨著我們持續聽到的共用應用程式,我們知道,並使用它的實作經驗的公司。如此一來 heightened 認知是好奇心,以挖掘,並找出如何 NoSQL 資料存放區可以提供優勢或其他可能的解決方案,開發人員目前所製作的軟體。但其中沒有開頭和困難的是學習曲線呢?或許更相關的考量重點: 多少時間和精力才啟動新的資料儲存解決方案,並開始撰寫程式碼對它嗎?畢竟,有 SQL Server 安裝程序,為新的應用程式,向右科學感嗎?

Word 已經到了。NET 上還可以 NoSQL 型別資料層實作新的選項的相關之社群。RavenDB (ravendb.net) 的文件的資料庫設計的。NET/Windows 平台,您必須先啟動或非關聯性資料存放區所使用的所有項目與封裝。RavenDB 會將文件儲存為無結構描述的 JSON。RESTful 的 API 存在於資料存放區中,與直接互動,但真正的好處之內。NET 用戶端所附的 API 包裝在一起安裝。它會實作的工作單位的模式,而且會運用 LINQ 語法,若要使用的文件及查詢。如果您使用過物件關聯的對應程式 (ORM) — 例如實體架構 (EF) 或 NHibernate — 或取用 WCF 資料服務,您就能夠上手右在家與在 RavenDB 中的文件使用的 API 架構。

快速安裝和執行 RavenDB 的執行個體的學習曲線是簡短悅耳的。事實上,可能需要最規劃的片段是授權的策略 (但即使如此,也最少)。RavenDB 提供開放原始碼授權的專案,也會開啟原始檔,但是商業授權的專案都需要關閉的來源商業。可以在 ravendb 中找到授權精選及價格的詳細資料。net/授權。網站指明是適用於啟動公司或在非商業、 已關閉的來源專案中使用它尋找可用的授權。不管怎麼說,值得快速檢視選項,以了解潛在之前任何建立原型或沙箱開發長期的實作。

內嵌的 RavenDB 和 MVC

RavenDB 可以在三個不同的模式執行:

  1. 以 Windows 服務
  2. 為 IIS 應用程式
  3. 在內嵌。NET 應用程式

前兩個相當簡單的安裝過程中,但會隨附一些實作策略的額外負荷。第三個選項,內嵌的是非常容易入門並實務。事實上,沒有 NuGet 套件可供使用它。下列命令,在 Visual Studio 2010年 (或搜尋這個詞彙在管理 NuGet 封裝] 對話方塊中的"ravendb") 中的 [封裝管理員] 主控台中的呼叫會傳遞的所有參考,才能開始使用內嵌的 RavenDB 版本:

Install-Package RavenDB-Embedded

封裝的詳細資訊,請找到站台 NuGet 組件庫在 bit.ly/ns64W1

您可以將內嵌的 RavenDB 版本加入 ASP。NET MVC 3 應用程式就像加入 NuGet 透過封裝一樣簡單,並提供資料存放區檔案目錄位置。因為 ASP。NET 應用程式有一個已知的資料目錄中名為 [App_Data,架構和大部分的控管公司都提供所需的少許或不設定該目錄的讀取/寫入權限,來儲存資料檔案的好地方。RavenDB 建立它的檔案存放區時,它會建置很多,對其提供的目錄路徑中目錄和檔案。它將不會建立一個最上層的目錄,以儲存所有項目。了解的是值得加入 ASP。NET 名為透過 Visual Studio 2010年中的 [專案] 內容功能表的 [App_Data] 資料夾,並接著子目錄的目錄中建立 App_Data RavenDB 資料 (請參閱圖 1)。


[圖 1 App_Data 目錄結構

文件的資料存放區已無結構描述的本質,因此並不需要建立資料庫的執行個體,或是設定任何資料表。一旦程式碼中進行第一次的呼叫來初始化資料存放區時,就會建立維護資料狀態所需的檔案。

使用 RavenDB 用戶端 API 與資料存放區介面的處理需要實作 Raven.Client.IDocumentStore 介面來建立並初始化一個物件的執行個體。API 有兩個類別,DocumentStore 和 EmbeddedDocumentStore,實作介面方法,可以用於 RavenDB 正在執行的模式而定。應該只會有資料存放區的每一個執行個體應用程式的生命週期期間。我可以建立類別以管理我的文件存放區,讓我存取 IDocumentStore 物件,透過靜態屬性的執行個體,而且有一種靜態方法來初始化執行個體的單一連線 (請參閱圖 2)。

[圖 2 DocumentStore 類別

public class DataDocumentStore
{
  private static IDocumentStore instance;
 
  public static IDocumentStore Instance
  {
    get
    {
      if(instance == null)
        throw new InvalidOperationException(
          "IDocumentStore has not been initialized.");
      return instance;
    }
  }
 
  public static IDocumentStore Initialize()
  {
    instance = new EmbeddableDocumentStore { ConnectionStringName = "RavenDB" };
    instance.Conventions.IdentityPartsSeparator = "-";
    instance.Initialize();
    return instance;
  }
}

靜態屬性 getter 檢查 null 物件的私用靜態的支援欄位,而且如果是 null,就會擲回的 InvalidOperationException。 我例外狀況,而不是呼叫初始化方法,以使程式碼安全執行緒。 如果執行個體] 屬性允許進行的呼叫,並且應用程式依賴參考進行初始設定屬性,然後就會有機會多個使用者無法達到該應用程式在此同時,導致同時呼叫初始化方法。 內初始化方法邏輯中,我會建立 Raven.Client.Embedded.EmbeddableDocumentStore 的新執行個體,並將 ConnectionStringName 屬性設定為 RavenDB 的 NuGet 套件的安裝加入至 web.config 檔的連接字串的名稱。 在 web.config 中,我可以設定連接字串的值若要將它設定為使用內嵌的資料存放區的本機版本的 RavenDB 了解的語法。 我也會對應到我 MVC 專案的 App_Data 目錄中所建立的資料庫目錄的檔案目錄:

<connectionStrings>
  <add name="RavenDB " connectionString="DataDir = ~\App_Data\Database" />
</connectionStrings>

IDocumentStore 介面包含所有資料存放區所使用的方法。 我會傳回,並儲存為介面型別 IDocumentStore 的執行個體的 EmbeddableDocumentStore 物件,所以我就可以彈性地變更為伺服器版本 (DocumentStore) 的 EmbeddedDocumentStore 物件的執行個體化,如果我要離開內嵌的版本。 如此一來,我將會處理我的文件物件管理的邏輯程式碼的所有將會與分離的模式執行 RavenDB 的知識。

RavenDB 會依預設,以建立其他類似的格式文件 ID 索引鍵。 "Item"物件好好的機碼的格式"項目/104"。物件模型名稱轉換為小寫,和 pluralized,且為唯一追蹤識別號碼會附加在每個新的文件建立的正斜線之後。 這可能會 MVC 應用程式中,有問題,因為正斜線將會造成新的路由參數進行剖析。 RavenDB 用戶端 API 提供變更正斜線,藉由設定 IdentityPartsSeparator 值的方式。 在我的 DataDocumentStore.Initialize 方法,我將 IdentityPartsSeparator 值設為虛線我是在呼叫初始化方法之 EmbeddableDocumentStore 物件,避免發生路由問題。

MVC 應用程式的 Global.asax.cs 檔案中加入 DataDocumentStore.Initialize 靜態方法呼叫 Application_Start 方法中的,會建立 IDocumentStore 執行個體,在第一次執行的應用程式中,如下所示:

protected void Application_Start()
{
  AreaRegistration.RegisterAllAreas();
  RegisterGlobalFilters(GlobalFilters.Filters);
  RegisterRoutes(RouteTable.Routes);
 
  DataDocumentStore.Initialize();
}

從這裡我可以利用 IDocumentStore 使用靜態呼叫 DataDocumentStore.Instance 屬性來處理從我的內嵌的資料存放區,我 MVC 應用程式中的文件物件的物件。

RavenDB 物件

若要更瞭解 RavenDB 的作用中,我將建立的原型應用程式,來儲存和管理書籤。 RavenDB 被設計來處理一般的舊 CLR 物件 (POCOs),這樣就不需要加入屬性的屬性,以指導序列化。 建立類別來代表書籤是相當直接了當的。 [圖 3 顯示書籤的類別。

[圖 3 書籤類別

public class Bookmark
{
  public string Id { get; set; }
  public string Title { get; set; }
  public string Url { get; set; }
  public string Description { get; set; }
  public List<string> Tags { get; set; }
  public DateTime DateCreated { get; set; }
 
  public Bookmark()
  {
    this.Tags = new List<string>();
  }
}

RavenDB 會將物件資料序列化為 JSON 結構,會先移至儲存文件時。 已知"Id"具名屬性會用於處理的文件識別碼索引鍵。 RavenDB 將會建立該值 — 提供 Id 屬性都是空的則為 null 時進行呼叫,以建立新的文件,並將其儲存在 @ 中繼資料元素 (這用來處理資料存放區層級的文件索引鍵) 的文件。 當要求文件時,RavenDB Client API 程式碼會將文件識別碼鍵設定為 [識別碼] 屬性載入文件物件時。

文件範例書籤的 JSON 序列化台中結構如下:

{
  "Title": "The RavenDB site",
  "Url": "http://www.ravendb.
net",
  "Description": "A test bookmark",
  "Tags": ["mvc","ravendb"],
  "DateCreated": "2011-08-04T00:50:40.3207693Z"
}

書籤類別 ! 以便妥善地使用文件存放區中,但標記屬性要挑戰的 UI 層。 我想讓使用者輸入標記,以在單一的文字] 方塊中輸入欄位的逗號分隔的清單,並將所有的資料欄位對應不到我的檢視表或控制器的動作滲出任何邏輯程式碼的情況下 MVC 模型繫結器。 我可以將它處理的對應表單欄位,名為"TagsAsString"[Bookmark.Tags] 欄位中使用自訂模型的繫結器。 首先,我建立自訂模型的繫結器類別 (請參閱圖 4)。

[圖 4 BookmarkModelBinder.cs

public class BookmarkModelBinder : DefaultModelBinder
{
  protected override void OnModelUpdated(ControllerContext controllerContext,
    ModelBindingContext bindingContext)
  {
    var form = controllerContext.HttpContext.Request.Form;
    var tagsAsString = form["TagsAsString"];
    var bookmark = bindingContext.Model as Bookmark;
    bookmark.Tags = string.IsNullOrEmpty(tagsAsString)
      ?
new List<string>()
      : tagsAsString.Split(',').Select(i => i.Trim()).ToList();
  }
}

然後我會更新 Globals.asax.cs 檔案,將 BookmarkModelBinder 加入至應用程式啟動時將模型文件夾同步化:

protected void Application_Start()
{
  AreaRegistration.RegisterAllAreas();
  RegisterGlobalFilters(GlobalFilters.Filters);
  RegisterRoutes(RouteTable.Routes);
 
  ModelBinders.Binders.Add(typeof(Bookmark), new BookmarkModelBinder());
  DataDocumentStore.Initialize();
}

若要處理填入和目前模型中的 [HTML] 文字方塊中,我要新增的擴充方法,將清單 <string> 轉換 以逗點分隔的字串的物件:

public static string ToCommaSeparatedString(this List<string> list)
{
  return list == null ?
string.Empty : string.Join(", ", list);
}

工作單位

RavenDB 用戶端 API 為基礎的工作單元模式。 若要處理文件中的文件存放區,新的工作階段必須開啟 ; 工作完成,並儲存起來; 此外,工作階段必須關閉。 工作階段處理變更追蹤,並以類似於在 EF 資料內容的方式運作。 以下是建立新的文件的範例:

using (var session = documentStore.OpenSession())
{
  session.Store(bookmark);
  session.SaveChanges();
}

最好是讓生活在整個 HTTP 要求,讓它可以追蹤變更、 使用第一層級快取等工作階段。 我將建立基底的控制站將用來開啟新的工作階段的 DocumentDataStore.Instance 動作執行,並在 動作執行 儲存變更,然後再處置工作階段物件 (請參閱 圖 5)。 這可讓我執行我的行動程式碼,與一個開啟的工作階段的執行個體在執行期間所需的工作量。

[圖 5 BaseDocumentStoreController

public class BaseDocumentStoreController : Controller
{
  public IDocumentSession DocumentSession { get; set; }
 
  protected override void OnActionExecuting(ActionExecutingContext filterContext)
  {
    if (filterContext.IsChildAction)
      return;
    this.DocumentSession = DataDocumentStore.Instance.OpenSession();
    base.OnActionExecuting(filterContext);
  }
 
  protected override void OnActionExecuted(ActionExecutedContext filterContext)
  {
    if (filterContext.IsChildAction)
      return;
    if (this.DocumentSession != null && filterContext.Exception == null)
      this.DocumentSession.SaveChanges();
    this.DocumentSession.Dispose();
    base.OnActionExecuted(filterContext);
  }
}

MVC 控制器和檢視實作

BookmarksController 動作會直接使用從基底類別的 IDocumentSession 物件,以及管理所有的文件的建立、 讀取、 更新和刪除 (CRUD) 作業。 [圖 6 顯示書籤控制器的程式碼。

[圖 6 BookmarksController 類別

public class BookmarksController : BaseDocumentStoreController
{
  public ViewResult Index()
  {
    var model = this.DocumentSession.Query<Bookmark>()
      .OrderByDescending(i => i.DateCreated)
      .ToList();
    return View(model);
  }
 
  public ViewResult Details(string id)
  {
    var model = this.DocumentSession.Load<Bookmark>(id);
    return View(model);
  }
 
  public ActionResult Create()
  {
    var model = new Bookmark();
    return View(model);
  }
 
  [HttpPost]
  public ActionResult Create(Bookmark bookmark)
  {
    bookmark.DateCreated = DateTime.UtcNow;
    this.DocumentSession.Store(bookmark);
    return RedirectToAction("Index");
  }
   
  public ActionResult Edit(string id)
  {
    var model = this.DocumentSession.Load<Bookmark>(id);
    return View(model);
  }
 
  [HttpPost]
  public ActionResult Edit(Bookmark bookmark)
  {
    this.DocumentSession.Store(bookmark);
    return RedirectToAction("Index");
  }
 
  public ActionResult Delete(string id)
  {
    var model = this.DocumentSession.Load<Bookmark>(id);
    return View(model);
  }
 
  [HttpPost, ActionName("Delete")]
  public ActionResult DeleteConfirmed(string id)
  {
    this.DocumentSession.Advanced.DatabaseCommands.Delete(id, null);
    return RedirectToAction("Index");
  }
}

IDocumentSession.Query <T> 索引作用中的方法會傳回結果物件實作 IEnumerable 介面,因此我可以使用 OrderByDescending LINQ 運算式來排序項目,並呼叫 ToList 方法來擷取資料到我傳回的物件。 詳細資料動作中的 IDocumentSession.Load 方法取得文件識別碼的機碼值,並 de-serializes 相符的文件,以書籤型別的物件。

Create 方法的狀況下動詞命令屬性與書籤項目上設定 CreateDate 屬性,並會呼叫 IDocumentSession.Store 方法,關閉工作階段物件至文件存放區中加入新的文件記錄。 以狀況下指令動詞的 Update 方法可以呼叫 IDocumentSession.Store 方法,,因為書籤物件必須已經設定的 Id 值。 RavenDB 可辨識的 Id 與現有的更新程式包含的文件相符的索引鍵而是建立一個新。 DeleteConfirmed 動作會呼叫 Delete 方法的 IDocumentSession.Advanced.DatabaseCommands 物件,它提供方法,若要刪除的機碼的文件,而不必先載入物件。 我不需要呼叫 IDocumentSession.SaveChanges 方法從內的任何動作,因為我已經在進行該呼叫的基底控制器動作執行

所有檢視都是相當直接了當。 它們可以是強型別書籤中的類別建立、 編輯和刪除的標記,以及一份索引標記中的書籤。 每個檢視可以直接參考的模型屬性,來顯示和輸入的欄位。 我需要來變更物件的屬性參考上的同一個位置是有標籤的 [輸入] 欄位。 下列程式碼建立與編輯檢視中,我將使用 ToCommaSeparatedString 的擴充方法:

@Html.TextBox("TagsAsString", Model.Tags.ToCommaSeparatedString())

這可讓使用者輸入和編輯書籤的單一文字方塊中以逗點分隔的格式相關聯的標籤。

搜尋物件

有了一個就地我 CRUD 作業,我可以將我指出將加入一個的最後一個位元的功能: 依標記篩選書籤清單的能力。 除了實作 IEnumerable 介面時,從 IDocumentSession.Query 方法傳回的物件也實作 IOrderedQueryable 和 IQueryable 介面中。NET Framework。 這可讓我使用 LINQ 來篩選及排序我的查詢。 例如,以下是過去五天內所建立的書籤的查詢:

var bookmarks = session.Query<Bookmark>()
  .Where( i=> i.DateCreated >= DateTime.UtcNow.AddDays(-5))
  .OrderByDescending(i => i.DateCreated)
  .ToList();

下面是一個用來逐頁查看完整的書籤清單:

var bookmarks = session.Query<Bookmark>()
  .OrderByDescending(i => i.DateCreated)
  .Skip(pageCount * (pageNumber – 1))
  .Take(pageCount)
  .ToList();

RavenDB 會建置動態執行之前處置的"一段時間"會保存這些查詢為基礎的索引。 當類似的查詢以相同的參數結構重新執行的時將會使用暫存的動態索引。 如果索引使用不足,無法在給定的時間期間,索引將設成永久性。 這些會保存應用程式生命週期之外。

我可以加入下列動作方法至我的 BookmarksController 類別,以處理取得的書籤所標記:

public ViewResult Tag(string tag)
{
  var model = new BookmarksByTagViewModel { Tag = tag };
  model.Bookmarks = this.DocumentSession.Query<Bookmark>()
    .Where(i => i.Tags.Any(t => t == tag))
    .OrderByDescending(i => i.DateCreated)
    .ToList();
  return View(model);
}

我知道這個動作會定期地叫用我的應用程式的使用者。 如果的確是這樣,此動態查詢會取得轉換成永久索引的 RavenDB 與我所需任何額外的處理。

還可以傳送給我們喚醒

使用的 RavenDB,崛起。NET 社群出現最後能夠擁有 NoSQL 文件存放區類型方案 catered 它,讓 Microsoft 為中心的車廠和開發人員滑落到或非關聯性很多其他的架構和語言巡覽過去幾年來的世界。 我們應該在 nevermore 聽到 Microsoft 堆疊或非關聯性的愛缺乏的想得到。 RavenDB 很方便。NET 的開發人員開始播放及原型設計與使用初始狀態的用戶端 API,可以模擬資料管理技術,開發人員都已採用包安裝或非關聯性資料存放區。 雖然 perennial 的引數之間關係,或非關聯性一定不會死出,雖然便利,試用一下"new"東西應該可以幫助導致更加瞭解的方式和位置或非關聯性的解決方案可符合應用程式架構。

Justin Schwartzenberger ,在駐防壕溝 cto 在 DealerHosts,能夠在 Web 開發的應用程式早就荒廢了,周遊句法的 jungles 的 PHP,傳統的 ASP,Visual Basic vb.NET,ASP.NET Web Form 中。 為 ASP 早期採納者。NET MVC 2007 中,他決定重整至所有的項目 MVC 他 Web 堆疊重點。 他會提供文件、 在使用者群組、 維持在部落格 iwantmymvc.com 可以加上在 Twitter 和 twitter.com/schwarty

因為有到下列的技術專家來檢閱這份文件: Ayende Rahien