Хранить объекты с общим базовым классом в базе данных
Скажем, у меня есть общий базовый класс/интерфейс
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.
И помните детей, взгляды злы.