Хранить объекты с общим базовым классом в базе данных - PullRequest
10 голосов
/ 03 сентября 2011

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

  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 и т. Д.).

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

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

База данных SQL Server 2008

Редактировать: Нашел похожий вопрос здесь Проектирование базы данных SQL для представления иерархии классов OO

Ответы [ 3 ]

13 голосов
/ 03 сентября 2011

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

Вот пример того, как это делается в коде Entity Framework сначала

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 () { ... }
}

Ссылка EF 4.1 Code First Walkthrough насконфигурируйте эту модель с EF.

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

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

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

5 голосов
/ 06 сентября 2011

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

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

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

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

  • Таблица CommandBase
    • ID (int или guid) - идентификатор команды
    • TypeID (int) - идентификатор типа команды
  • Таблица команд A
    • ID (int или guid) - тот же идентификатор из таблицы CommandBase
    • X (int)
    • Y (int)
  • Таблица командB
    • 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
    

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

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

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

1 голос
/ 12 сентября 2011

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

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

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

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...