Каков наилучший способ хранения / запроса нескольких типов в коллекции RavenDB? - PullRequest
8 голосов
/ 25 февраля 2012

Я проектирую систему ведения журнала, которая будет хранить свои записи журнала в RavenDB, и для этой конкретной системы я хочу хранить (и более поздние запросы) документы, которые имеют различные структуры данных в зависимости от типа регистрируемого события.Рассмотрим следующие события, которые я, возможно, захочу зарегистрировать:

  1. Пользователь входит в систему - Сохранить идентификатор пользователя
  2. Пользователь удаляет файл - Сохраните идентификатор пользователя и имя удаляемого файла

У меня есть несколько разных способов, которыми я могу пойти сюда ...

Вариант А. Создайте два совершенно разных типа

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(), я получил ноль результатов.Чтобы вернуть документы, мне нужно было бы запросить их отдельные типы, что фактически делает это эквивалентным варианту А. выше.

Вариант С. Создание различных классов свойств

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, который предоставляет подробную информацию, относящуюся к типу события.Сначала этот вариант казался лучшим, поскольку он хранит все записи журнала в одной коллекции событий и упрощает запрос всей коллекции.Однако, допустим, я хочу только события FileDelete, где Filename имеет значение «test.txt».Это становится немного сложнее.

Например, следующее выдает несколько непонятную ошибку о том, что поле «Имя файла» не индексируется:

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

Iможет сделать это довольно тривиально и эффективно выполнять запросы, используя шаблон EF для таблицы наследования.Недостатком этого подхода является то, что Sql серьезно излишним для этой проблемы - нам не нужна согласованность данных и другая строгость, которую обеспечивают реляционные системы.И, по моему опыту, вставки Sql намного, намного медленнее, чем хранение документов в RavenDB (важное соображение для системы ведения журналов).

Итак, есть варианты ... как вы думаете?Я что-то пропустил?

Возможно, связано: Указание имени коллекции в RavenDB

Ответы [ 3 ]

5 голосов
/ 26 февраля 2012

«Официальным» способом решения этой проблемы является Полиморфный Индекс: 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 голосов
/ 25 февраля 2012

Иди с базовым классом. Хитрость заключается в том, чтобы использовать полиморфизм и установить для всех конкретных типов одинаковое имя тега типа. Теперь вы можете легко их запросить, поскольку они находятся в одной коллекции.

FindTypeTagName = type =>
{
    if (typeof (LoginEvent).IsAssignableFrom(type) ||
        typeof (FileDeleteEvent).IsAssignableFrom(type))
        return "event";
    return DocumentConvention.DefaultTypeTagName(type);
}
0 голосов
/ 25 февраля 2012

Базовый класс.Всегда старайтесь использовать правильный упс.

Вам необходимо указать, что все подклассы должны храниться в одной коллекции

...