在RavenDB集合中存储/查询多个类型的最佳方法是什么?

Mik*_*ike 8 logging entity-framework relational-database nosql ravendb

我正在设计一个将日志条目存储在RavenDB中的日志记录系统,对于这个特定的系统,我想根据记录的事件类型存储(以后查询)具有不同数据结构的文档.请考虑我可能要记录的以下事件:

  1. 用户登录 - 存储UserID
  2. 用户删除文件 - 存储UserID和要删除的文件名

我有几种不同的方式可以去这里......

选项A.创建两个完全不同的类型

class LoginEvent
{
  public int UserId { get; set; }
}

class FileDeleteEvent
{
  public int UserId { get; set; }
  public string Filename { get; set; }
}
Run Code Online (Sandbox Code Playgroud)

这种方法在RavenDB中产生两个不同的集合,并且它们易于查询.但是,检索所有日志条目的并集需要多次查询和多次往返服务器 - 一次用于LoginEvents,另一次用于FileDeleteEvents.只有两种事件类型没有太大区别,但随着事件类型数量的增加,问题会变得更加严重.

选项B.创建一个基类并从中派生

abstract class Event
{
}

class LoginEvent : Event
{
  public int UserId { get; set; }
}

class FileDeleteEvent : Event
{
  public int UserId { get; set; }
  public string Filename { get; set; }
}
Run Code Online (Sandbox Code Playgroud)

我试过这种方法,但是RavenDB似乎按照它们的实际类型来存储和查询文档,而不是它们的类型 - 当我这样做时我没有Query<Event>().ToArray()得到任何结果.为了获取文档,我将不得不查询它们各自的类型,这有效地使其等同于上面的选项A.

选项C.创建不同的属性类

enum EventType { Login, FileDelete }

class Event
{
  public EventType EventType { get; set; }
  public object Info { get; set; }
}

class LoginInfo
{
  public int UserId { get; set; }
}

class FileDeleteInfo
{
  public int UserId { get; set; }
  public string Filename { get; set; }
}
Run Code Online (Sandbox Code Playgroud)

使用此方法,我们始终存储Event类型的条目,但我们使用相应的Info类填充其Info属性,该类提供特定于事件类型的详细信息.起初这个选项似乎是最好的,因为它将所有日志条目存储在单个Event集合中,并且可以轻松查询完整集合.但是,假设我只想要Filename为"test.txt"的FileDelete事件.这变得有点棘手.

例如,以下引发了一个关于"Filename"字段未被索引的有点模糊的错误:

var events = session.Query<Event>()
  .Where(a => a.EventType == EventType.FileDelete)
  .Where(a => ((FileDeleteInfo)a.Info).Filename == "test.txt")
  .ToArray();
Run Code Online (Sandbox Code Playgroud)

以下,除了不是我想要的,返回零结果:

var events = session.Query<Event>()
  .Select(a => a.Info)
  .OfType<FileDeleteInfo>()
  .Where(a => a.Filename == "test.txt")
  .ToArray();
Run Code Online (Sandbox Code Playgroud)

实际上,下面的投影,根据文档支持的操作,甚至没有返回预期的类型,只是一堆奇怪的中间结果,没有意义:

var events = session.Query<Event>()
  .Select(a => a.Info)
  .ToArray();
Run Code Online (Sandbox Code Playgroud)

因此,尽管从数据存储的角度来看这个选项可能很好,但从可查询性的角度来看却失败了.(假设我正在构建正确的查询 - 可能还有另一种我不考虑的方式).

选项D.创建一个包含所有可能属性的巨型事件类

enum EventType { Login, FileDelete }

class Event
{
  public EventType EventType { get; set; }
  public int UserId { get; set; }
  public string Filename { get; set; }
  .
  .
  .
}
Run Code Online (Sandbox Code Playgroud)

尽管从存储角度来看有点浪费,但从可查询性的角度来看,这种方法是微不足道的.当您开始添加要记录的更多类型的事件时,会出现问题 - 然后属性的数量开始增加.

选项E.忘记RavenDB并使用Entity Framework + Sql

我可以相当简单地做到这一点,并使用EF的每个表继承模式有效地查询.这种方法的缺点是Sql对于这个问题严重过度 - 我们不需要数据一致性和关系系统提供的其他严格性.而且,根据我的经验,Sql插件比RavenDB中的文档存储要慢得多(对于日志记录系统来说是一个重要的考虑因素).

所以,有选择......你怎么看?我错过了什么吗?

可能相关:在RavenDB中指定集合名称

Mik*_*ike 5

解决此问题的"官方"方式似乎是多态指数:https://ravendb.net/docs/article-page/3.0/csharp/indexes/indexing-polymorphic-data

这是一篇博客文章,详细讨论了这种方法:http://www.philliphaydon.com/2011/12/14/ravendb-inheritance-revisited/

这里还有一个视频:http://youtu.be/uk2TVs-d6sg