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

时间:2012-02-25 06:22:21

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

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

  1. 用户登录 - 存储UserID
  2. 用户删除文件 - 存储UserID和正在删除的文件名
  3. 我有几种不同的方式可以去这里......

    选项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属性,该类提供特定于事件类型的详细信息。起初,这个选项似乎是最好的,因为它将所有日志条目存储在单个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();
    

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

    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

3 个答案:

答案 0 :(得分: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

答案 1 :(得分:2)

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

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

答案 2 :(得分:0)

基础课程。总是尝试使用适当的oop。

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