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

时间:2011-09-03 15:06:11

标签: c# .net sql-server-2008 database-design inheritance

我们说我有一个共同的基类/接口

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

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

现在我在数据库中只有一个名为命令的表,这里我存储了对象的字符串序列化。基本上表中的列是: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

修改:在Designing SQL database to represent OO class hierarchy

找到类似的问题

3 个答案:

答案 0 :(得分:13)

您可以使用ORM将命令继承映射到数据库。例如,您可以使用ORM提供的“每个层次结构表”技术(例如:实体框架,nHibernate等)。 ORM将在您检索它们时实例化正确的子类。

以下是首先在实体框架代码中执行此操作的示例

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参数存储为您手动(de)序列化的XML配置。这样,您可以将所有命令保存在单个表中,而不会牺牲性能。同样,这有一个缺点,你无法使用命令参数进行过滤。

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

答案 1 :(得分: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
    • 名称(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
    

这种方法的优点在于它反映了你的班级结构。它易于理解和使用。

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

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

答案 2 :(得分:1)

我们越来越多地在SQL中使用XML来处理软数据。查询(使用XPath)可能有点痛苦,但它允许我们每行存储元数据,根据模式进行验证等。

然后可以通过代码解析XML,并且可以通过反射将参数与反序列化对象中的参数进行匹配。

实际上,这复制了ORM功能,但是使用平面数据库结构,简化了查询,甚至可以通过XPath查询参数。

记住孩子,观点是邪恶的。