NoSQL 文档数据库

将 RavenDB 嵌入 ASP.NET MVC 3 应用程序中

索 Schwartzenberger

下载代码示例

NoSQL 移动要注意在 Microsoft 内部不断增加。NET 框架社区,随着我们不断听到的共享它的其实施经验,我们知道和使用的应用程序中的公司。 这保证意识方面深入讨论并确定如何 NoSQL 数据存储区无法提供好处或其他潜在解决方案的软件开发人员目前正在编制的好奇。 但在执行开始时间和难的是学习过程? 也许,一个甚至更相关的问题: 多少时间和精力都需要启动一个新的数据存储解决方案,并开始编写针对它的代码吗? 毕竟,您有 SQL Server 的安装过程,到科学,右侧为新应用程序吗?

Word 已到达。Raven NoSQL 类型的数据层实现为一个新选项有关的翅膀上的 NET 社区。 RavenDB (ravendb.net) 为设计文档数据库。NET/Windows 平台,若要开始使用非关系型数据存储区所需的一切与打包在一起。 RavenDB 将文档存储为架构免 JSON。 Rest 风格的 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 文件夹,然后 RavenDB 数据的 App_Data 目录中创建子目录 (请参阅图 1)。

App_Data Directory Structure
图 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,它将引发 InvalidOperationException。 我可以引发异常在这里,而不是调用初始化方法,以保持代码的线程安全。 如果实例属性允许进行的调用和应用程序依赖引用该属性进行初始化,然后会有一个多个用户可以击中该应用程序在同一时间,从而导致同时调用初始化方法的机会。 内初始化方法逻辑,我创建 Raven.Client.Embedded.EmbeddableDocumentStore 的新实例,并将 ConnectionStringName 属性设置为添加到 web.config 文件中的 RavenDB NuGet 软件包的安装连接字符串的名称。 在 web.config 文件中,我的连接字符串的值设置为一种语法,以将其配置为使用嵌入的数据存储区的本地版本 RavenDB 可理解的。 我还映射到 MVC 项目 App_Data 目录中创建的数据库目录的文件目录:

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

IDocumentStore 接口包含的所有数据存储区所使用的方法。 我返回,并将 EmbeddableDocumentStore 对象存储为 IDocumentStore 接口类型的实例,因此我有 EmbeddedDocumentStore 对象的实例化更改为的服务器版本 (DocumentStore),如果我想要离开的嵌入版本的灵活性。 这种方式,我将处理我的文档对象管理的逻辑代码的所有将彼此在其中运行 RavenDB 的模式的知识。

默认情况下,RavenDB 将在类似于其余部分的格式创建文档 ID 参数。 "项目"对象将获取密钥格式"项目/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"命名属性将用于处理文档 ID 密钥。 RavenDB 将创建该值 — 提供的 Id 属性为空或空进行调用以创建新文档时 — — 并将存储在文档 (这用来处理在数据存储级别的文档密钥) @ 元数据元素。 请求的文档,RavenDB 客户端 API 代码在加载文档对象时将设置为 Id 属性的文档 ID 密钥。

在下面的结构表示书签的示例文档的 JSON 序列化:

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

书签类高速与文档存储区,但标记属性将会造成用户界面层中的一个难题。 我想让用户输入标记的单个文字框中输入字段中的逗号分隔的列表,并且具有 MVC 模型联编程序的所有数据字段映射不含任何渗透到我的视图或控制器操作的逻辑代码。 我能够应对这用于映射到 Bookmark.Tags 字段中名为"TagsAsString"窗体域中使用自定义模型活页夹。 首先,创建自定义模型活页夹类 (请参阅图 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();
  }
}

然后更新将 BookmarkModelBinder 添加到应用程序启动时这些模型活页夹的 Globals.asax.cs 文件:

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 方法接受一个文档 ID 密钥值,并反序列化到书签类型的对象匹配的文档。

创建方法与 HttpPost verb 属性上的书签项设置 CreateDate 属性和调用 IDocumentSession.Store 方法要文档存储区中添加新文档记录的会话对象关闭。 与 HttpPost 谓词 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 框架。 这允许我使用 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 与我零件上所需的任何额外工作。

发送到唤醒我们 Raven

面对不断涌现的 RavenDB,。NET 社区似乎最后有餐饮向它,将 NoSQL 文档存储类型解决方案允许以 Microsoft 为中心的商店和开发人员可以通过这么多其他框架和语言已导航过去几年中非关系型世界滑翔。 我们应 nevermore 听到缺乏 Microsoft 堆栈的非关系型最爱的 cries。 RavenDB 方便。NET 开发人员能够开始播放和原型设计与通过使用干净的客户端 API 模仿开发人员已经采用的数据管理技术捆绑安装非关系型数据存储区。 尽管多年生植物参数之间关系和非关系型肯定不会出,易用性试验凹一些"新"应帮助更好地了解非关系型解决方案如何以及在何处可以适应应用程序体系结构会导致。

索 Schwartzenberger ,已 entrenched 首席技术官 DealerHosts,在 Web 应用程序开发过程中的较长,遍历句法 jungles 的 PHP,传统的 ASP,Visual Basic,vb.NET 和 ASP。NET Web 窗体。 作为 ASP 先期。NET MVC 在 2007 年,他决定重构他 Web 堆栈焦点移到 MVC 的所有事宜。 他所占的文章,讲述在用户组,保持在博客 iwantmymvc.com 的后面可以在 Twitter 上和 twitter.com/schwarty

这要归功于以下技术专家审阅这篇文章: Ayende Rahien