【问题标题】:Store objects with common base class in database在数据库中存储具有公共基类的对象
【发布时间】:2011-11-09 18:29:19
【问题描述】:

假设我有一个通用的基类/接口

  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> 并执行它们的执行方法。

现在我在数据库中只有一个名为 commands 的表,在这里我存储了对象的字符串序列化。基本上表中的列是:id|commandType|commaSeparatedListOfParameters。虽然这非常简单,并且适用于加载所有命令,但如果不使用子字符串和其他晦涩的方法,我就无法轻松查询命令。我想有一个简单的SELECT id,x,y FROM commandA_commands WHERE x=... 方法,同时有一种从命令表中加载所有命令的通用方法(我想这将是 commandA_commands、commandB_commands 等的某种 UNION/JOIN)。

添加新命令不需要在数据库中进行太多手动操作或手动创建序列化/解析方法,这一点很重要。我有很多,并且一直在添加和删除新的。我不介意创建命令+表+查询生成工具,但如果这是最佳解决方案所必需的。

我能想到的最好的就是像id|commandType|param1|param2|param3|etc.. 这样的普通表,它并不比我当前的解决方案好多少(实际上更糟?),因为许多命令都需要空参数并且数据类型会有所不同,所以我有再次求助于普通的字符串转换,并将每个字段的大小设置为足够大以容纳最大的命令。

数据库是 SQL Server 2008

编辑:在这里找到类似的问题Designing SQL database to represent OO class hierarchy

【问题讨论】:

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


    【解决方案1】:

    您可以使用 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 配置此模型。

    如果您的命令采用完全不同的参数集,您可以考虑使用“每个类型的表”继承建模。在这里,由于涉及到大量的联合和表连接,您将付出一些显着的性能损失。

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

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

    【讨论】:

    • 听起来很有趣。它在实践中有效吗?它可以扩展吗?数据是否仍可手动查询?
    • @aero 继承映射很常见,但如果您必须同时处理 1000 条命令,那么 ORM 将需要相当长的时间
    • 性能方面,值得创建一个包含大量数据的示例数据库,尽可能接近实际生产数据库,然后使用多种方法运行所有建议的测试用例,包括您当前的实施。使用 Dapper 直接访问具有自定义参数的公用表实际上可能比任何建议的 ORM 都快。
    • @SlavaGu 是的,您需要对其进行负载测试。但该解决方案最重要的部分之一是 ORM 能够创建正确的子类,而 AFAIK Dapper 不会这样做。
    【解决方案2】:

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

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

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

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

    • 表命令库
      • ID(int 或 guid)- 命令的 ID
      • TypeID (int) - 命令类型的 ID
    • 表命令A
      • ID(int 或 guid)- 与 CommandBase 表中的 ID 相同
      • X(整数)
      • Y(整数)
    • 表命令 B
      • ID(int 或 guid)- 与 CommandBase 表中的 ID 相同
      • 名称(nvarchar)
    • 表命令类型
      • 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
      

    这种方法的好处是它反映了您的班级结构。它易于理解和使用。

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

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

    【讨论】:

    • 这是自从我发布问题以来我一直在向自己倾斜的解决方案。视图/连接显然会有大量的空值,因为行一次只有一个特定类的列。这会影响 ODBC 或 SQLConnection 的查询/传输性能吗?这是否足够聪明,可以忽略不必要的数据,还是将每一行都按原样传输,所有可能有一百列的空值。我想我将不得不尝试这在实践中是如何工作的,否则这似乎是一个很好的解决方案以及一些自动化脚本。一层层级没问题。
    • 我对最多 10 个后代使用了这种方法(我当前的项目使用这种模式和 4 个后代)。如果有数百个后代,性能是否会受到影响,我没有经验。
    • 连接很昂贵,尤其是对于非常大的表!
    • @threed:如果两个表都使用相同的键作为聚集索引,则不然。
    【解决方案3】:

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

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

    这有效地复制了 ORM 功能,但具有扁平的数据库结构,简化了查询,甚至可以通过 XPath 查询参数。

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

    【讨论】:

    • 哇,我不知道 sql server 有这样的功能。起初,我似乎是一种更奇特的方式来返回字符串中的逗号分隔值,但由于实际上存在对存储和查询值的原生支持,这可能是一个非常好的选择。
    • 它的扩展性非常很好——只有一个表,所以没有连接或视图来混淆执行时间。您还可以为 XML 等定义模式 - 但在我们的应用程序中,并没有发现它对性能影响有多大好处 - 尽管它可能会提高 XPath 查询速度。尽管数据层涉及一些代码,将 XML 解析为对象模型,但对于完整的表结构来说,情况不可避免。无论哪种方式,使用它,不要使用它:)
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2019-07-14
    • 2019-02-08
    • 1970-01-01
    • 1970-01-01
    • 2020-06-24
    • 2011-06-20
    • 1970-01-01
    相关资源
    最近更新 更多