Хранить объекты с общим базовым классом в базе данных

Скажем, у меня есть общий базовый класс/интерфейс

  interface ICommand
  {
    void Execute();
  }

Затем есть несколько команд, наследующих от этого интерфейса.

  class CommandA : ICommand
  {
    int x;
    int y;
    public CommandA(int x, int y)
    {  ...  }
    public void Execute () { ... }
  }
  class CommandB : ICommand
  {
    string name;
    public CommandB(string name)
    {  ...  }
    public void Execute () { ... }
  }

Теперь я хочу сохранить эти команды в базе данных с помощью общего метода, а затем загрузить все из них в базу данных List<ICommand> и выполнить из них метод Execute.

Сейчас у меня только одна таблица в командах, называемых БД, и здесь я храню сериализацию строки объекта. В основном столбцы в таблице: id|commandType|commaSeparatedListOfParameters. Хотя это очень легко и полезно для загрузки всех команд, я не могу легко запросить команды без использования подстроки и других неясных методов. Я хотел бы иметь простой способ SELECT id,x,y FROM commandA_commands WHERE x=... и в то же время иметь общий способ загрузки всех команд из таблицы команд (я думаю, это будет какой-то UNION/JOIN команд commandA_commands, commandB_commands и т.д.).

Важно, чтобы для добавления новой команды требовалось не много ручного возиться в БД или ручное создание методов сериализации/parse-методов. У меня их много, а новые добавляются и удаляются все время. Я не возражаю против создания команды + table + query generation tool, хотя, если это потребуется для лучшего решения.

Лучшее, что я могу думать о себе, - это обычная таблица типа id|commandType|param1|param2|param3|etc.., которая не намного лучше (на самом деле хуже?), чем мое текущее решение, так как многим командам нужны нулевые параметры, и тип данных будет изменяться, поэтому у меня есть снова обратиться к общему преобразованию строк и размеру каждого поля, достаточно большого для самой большой команды.

База данных - это SQL Server 2008

Изменить: Найти аналогичный вопрос здесь Проектирование базы данных SQL для представления иерархии классов OO

Ответ 1

Эта проблема довольно распространена, и я не видел решения, которое не имеет недостатков. Единственная возможность точно хранить иерархию объектов в базе данных - использовать некоторую базу данных NoSQL.

Однако, если реляционная база данных обязательна, я обычно использую этот подход:

  • одна таблица для базового класса/интерфейса, которая хранит общие данные для всех типов
  • одна таблица в нисходящем классе, которая использует тот же самый первичный ключ из базовой таблицы (это хороший вариант использования для последовательностей SQL Server 2011, кстати)
  • одна таблица, в которой хранятся типы, которые будут сохранены
  • представление, которое объединяет все эти таблицы вместе, чтобы обеспечить легкую загрузку/запрос объектов

В вашем случае я бы создал:

  • Таблица CommandBase
    • ID (int или guid) - идентификатор команды
    • TypeID (int) - идентификатор типа команды
  • Таблица CommandA
    • ID (int или guid) - тот же идентификатор из таблицы CommandBase
    • X (int)
    • Y (int)
  • Таблица CommandB
    • ID (int или guid) - тот же идентификатор из таблицы CommandBase
    • Имя (nvarchar)
  • Таблица CommandTypes
    • ID (int) - идентификатор типа команды
    • Имя (nvarchar) - Название типа команды ( "CommandA", "CommandB",...)
    • TableName (nvarchar) - Имя таблицы, в которой хранится тип - полезно, если требуется какой-либо динамический sql - в противном случае необязательный
  • Просмотреть команды, что-то вроде:

    select cb.ID, a.X, a.Y, b.Name 
     from CommandBase cb
       left outer join CommandA a on a.ID = cb.ID
       left outer join CommandB b on b.ID = cb.ID
    

Поверхность этого подхода заключается в том, что он отражает структуру вашего класса. Это легко понять и использовать.

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

Лично я бы использовал этот подход, если бы знал, что количество подклассов относительно невелико и относительно фиксировано, так как оно требует создания (и поддержания) новой таблицы для каждого нового типа. Однако модель довольно проста, поэтому можно создать инструмент / script, который мог бы создать и поддерживать для вас.

Ответ 2

Вы можете использовать ORM для сопоставления наследования команд с базой данных. Например, вы можете использовать технику "Таблица на иерархию", предоставляемую ORM (например: Entity Framework, nHibernate и т.д.). ORM будет создавать правильный подкласс при их получении.

Вот пример того, как это сделать в коде Entity Framework Code

abstract class Command : ICommand
{
   public int Id {get;set;}

   public abstract void Execute();
}

class CommandA : Command
{
   public int X {get;set;}
   public int Y {get;set;}

   public override void Execute () { ... }
}
class CommandB : Command
{
   public string Name {get;set;}

   public override void Execute () { ... }
}

Refera EF 4.1 Code First Walkthrough, чтобы настроить эту модель с помощью EF.

Если ваши команды принимают совершенно другой набор параметров, вы можете рассмотреть использование моделирования наследования типа "Таблица для каждого типа". Здесь вы заплатите значительное снижение производительности из-за большого количества Союзов и объединений таблиц, участвующих в этом.

Альтернативный подход состоял в том, чтобы сохранить параметры команды как конфигурацию XML, где вы (де) сериализуете вручную. Таким образом, вы можете сохранить все свои команды в одной таблице, не жертвуя производительностью. Опять же, это имеет недостаток, когда вы не можете фильтровать параметры команды.

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

Ответ 3

Мы используем XML в SQL все больше и больше для наших мягких данных. Это может быть немного болезненно для запроса (с использованием XPath), но это позволяет нам хранить метаданные для каждой строки, проверенные против схемы и т.д.

Затем XML может быть проанализирован кодом, а параметры могут быть сопоставлены посредством отражения в параметрах в де-сериализованном объекте.

Эффективно это повторяет функциональность ORM, но с плоской структурой базы данных, упрощает запросы, а параметры даже запрашиваются через XPath.

И помните детей, взгляды злы.