在数据库中存储具有公共基类的对象

aer*_*ero 10 .net c# inheritance database-design sql-server-2008

假设我有一个共同的基类/接口

  interface ICommand
  {
    void Execute();
  }
Run Code Online (Sandbox Code Playgroud)

然后有一些从该接口继承的命令.

  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 () { ... }
  }
Run Code Online (Sandbox Code Playgroud)

现在我想用常用方法将这些命令存储在数据库中,然后将所有这些命令从DB加载到a中List<ICommand>并执行它们的Execute方法.

现在我只在DB中有一个名为命令的表,这里我存储了对象的字符串序列化.基本上表中的列是:id|commandType|commaSeparatedListOfParameters.虽然这很容易并且适用于加载所有命令,但我无法在不使用子字符串和其他模糊方法的情况下轻松查询命令.我想有一个简单的方法,SELECT id,x,y FROM commandA_commands WHERE x=...同时有一个从命令表加载所有命令的通用方法(我想这将是某种命令的UNION/JOIN,commandB_commands等).

重要的是,添加新命令不需要在DB中手动摆弄或手动创建序列化/解析方法.我有很多它们,并且新的一直被添加和删除.我不介意创建一个命令+表+查询生成工具,但这是否是最佳解决方案所必需的.

我能想到的最好的是一个常见的表id|commandType|param1|param2|param3|etc..,比起我当前的解决方案并不是更好(实际上更糟糕?),因为许多命令需要空参数,数据类型会有所不同所以我不得不求助于常见的字符串转换再次并确定每个字段的大小足以容纳最大的命令.

该数据库是SQL Server 2008

编辑:在此处找到类似的问题设计SQL数据库来表示OO类层次结构

Era*_*nga 13

您可以使用ORM将命令继承映射到数据库.例如,您可以使用ORM提供的"每个层次结构表"技术(例如:实体框架,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 () { ... }
}
Run Code Online (Sandbox Code Playgroud)

参考EF 4.1 Code First Walkthrough,使用EF配置此模型.

如果您的命令采用完全不同的参数集,则可以考虑使用"每类型表"继承建模.在这里你将付出一些重大的性能损失,因为很多联盟和表连接都涉及到这一点.

替代方法是将Command参数存储为您手动(de)序列化的XML配置.这样,您可以将所有命令保存在单个表中,而不会牺牲性能.同样,这有一个缺点,你无法使用命令参数进行过滤.

每种方法都有其优点和缺点.您可以选择适合您要求的策略.


SWe*_*eko 5

这个问题很常见,我没有看到没有缺点的解决方案.在数据库中精确存储对象层次结构的唯一选择是使用一些NoSQL数据库.

但是,如果强制要求关系数据库,我通常采用这种方法:

  • 一个用于基类/接口的表,用于存储所有类型的公共数据
  • 每个降序类一个表,它使用与基表完全相同的主键(这是SQL Server 2011序列的一个很好的用例,顺便说一句)
  • 一个表包含将要存储的类型
  • 将所有这些表连接在一起的视图,以便轻松加载/查询对象

在你的情况下,我会创建:

  • 表CommandBase
    • ID(int或guid) - 命令的ID
    • TypeID(int) - 命令类型的ID
  • 表CommandA
    • ID(int或guid) - CommandBase表中的相同ID
    • X(int)
    • Y(int)
  • 表CommandB
    • ID(int或guid) - CommandBase表中的相同ID
    • 名称(nvarchar)
  • 表CommandTypes
    • ID(int) - 命令类型的ID
    • Name(nvarchar) - 命令类型的名称("CommandA","CommandB",...)
    • TableName(nvarchar) - 存储类型的表的名称 - 如果需要某些动态sql,则为usefull - 否则为可选
  • 查看命令,类似于:

    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
    
    Run Code Online (Sandbox Code Playgroud)

这种方法的优点是它反映了你的类结构.它易于理解和使用.

缺点是,当你添加新类时会变得越来越麻烦,很难对多层次的层次结构进行建模,如果有很多后代,视图可以长达一英里.

就个人而言,如果我知道子类的数量相对较小且相对固定,我会使用这种方法,因为它需要为每个新类型创建(和维护)一个新表.但是,该模型非常简单,因此可以创建一个可以为您创建和维护的工具/脚本.