【问题标题】:What's the best way to store/query multiple types within a RavenDB collection?在 RavenDB 集合中存储/查询多种类型的最佳方式是什么?
【发布时间】:2012-02-25 06:22:20
【问题描述】:

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

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

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

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

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

class FileDeleteEvent
{
  public int UserId { get; set; }
  public string Filename { get; set; }
}

这种方法会在 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; }
}

我尝试了这种方法,但 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; }
}

使用这种方法,我们总是存储一个 Event 类型的条目,但我们使用相应的 Info 类填充它的 Info 属性,该类提供特定于事件类型的详细信息。起初,这个选项似乎是最好的,因为它将所有日志条目存储在一个事件集合中,并使查询完整集合变得容易。但是,假设我只想要文件名为“test.txt”的 FileDelete 事件。这变得有点棘手。

例如,以下会引发一个关于“文件名”字段未编入索引的有点模糊的错误:

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

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

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

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

var events = session.Query<Event>()
  .Select(a => a.Info)
  .ToArray();

因此,尽管从数据存储的角度来看,此选项可能不错,但从可查询性的角度来看,它却失败了。 (假设我正在构建正确的查询——我可能没有考虑另一种方式)。

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

enum EventType { Login, FileDelete }

class Event
{
  public EventType EventType { get; set; }
  public int UserId { get; set; }
  public string Filename { get; set; }
  .
  .
  .
}

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

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

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

那么,有很多选择……你觉得呢?有什么我错过的吗?

可能相关:Specifying Collection Name in RavenDB

【问题讨论】:

    标签: entity-framework logging nosql relational-database ravendb


    【解决方案1】:

    解决这个问题的“官方”方法似乎是多态索引: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

    【讨论】:

      【解决方案2】:

      使用基类的东西。诀窍是使用多态性并将所有具体类型设置为使用相同的类型标记名称。现在,您可以轻松查询它们,因为它们在同一个集合中。

      FindTypeTagName = type =>
      {
          if (typeof (LoginEvent).IsAssignableFrom(type) ||
              typeof (FileDeleteEvent).IsAssignableFrom(type))
              return "event";
          return DocumentConvention.DefaultTypeTagName(type);
      }
      

      【讨论】:

      • 谢谢,这解决了多个集合的问题,但我仍然不明白如何在文件名为“test.txt”的 FileDelete 事件中执行查询。看来 OfType Linq 运算符不受支持,因此我不能使用它来仅检索这些类型。
      【解决方案3】:

      基类。始终尝试使用正确的 oop。

      您需要指定所有子类都应存储在同一个集合中

      【讨论】:

        猜你喜欢
        • 2022-09-27
        • 1970-01-01
        • 2010-09-12
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2018-07-01
        • 2019-10-21
        • 2011-08-09
        相关资源
        最近更新 更多