Я проектирую систему ведения журнала, которая будет хранить свои записи журнала в RavenDB, и для этой конкретной системы я хочу хранить (и более поздние запросы) документы, которые имеют различные структуры данных в зависимости от типа регистрируемого события.Рассмотрим следующие события, которые я, возможно, захочу зарегистрировать:
- Пользователь входит в систему - Сохранить идентификатор пользователя
- Пользователь удаляет файл - Сохраните идентификатор пользователя и имя удаляемого файла
У меня есть несколько разных способов, которыми я могу пойти сюда ...
Вариант А. Создайте два совершенно разных типа
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