【问题标题】:Hierarchical data from DB - to join or not to join来自数据库的分层数据 - 加入或不加入
【发布时间】:2010-12-30 14:19:54
【问题描述】:

我正在尝试为这个问题找到一个干净且高效的解决方案,但不知何故被卡住了。

琐事:
-ASP.Net C# 应用程序 (.Net 3.5)
-MS-SQL Server 2005

数据如下所示:
类别 -> 模板 -> 实例

一个类别可以包含多个模板。
一个模板可以包含多个实例。

这 3 个中的每一个都有一个类和一个对应的包含很多列的数据库表。

我想将数据库中的完整类别加载到 C# Category 类对象中,包括所有相关的模板和实例对象。

我现在有两个选择:
1) 对所有 3 个表进行连接,并一次读取所有数据。
优势:在数据库方面要快得多,一个查询中的所有信息。
缺点:我传输了大量冗余数据,因为在每一行中,每个实例都有相同的类别和模板数据。

示例(简化):

CategoryID | CategoryName | TemplateID | TemplateName | InstanceID | InstanceName  
1 | FirstCategory | 1 | FirstTemplate | 1 | FirstInstance   
1 | FirstCategory | 1 | FirstTemplate | 2 | SecondInstance  
1 | FirstCategory | 1 | FirstTemplate | 3 | ThirdInstance  
1 | FirstCategory | 1 | SecondTemplate | 4 | FourthInstance  

2) 我自己查询每张表,先收集分类数据,再收集带有分类ID的相关模板数据等。
优势 :直观的方法,在代码方面更容易处理,不会获取冗余数据。
缺点:对服务器的多个查询,可能更慢。

去这里最好的方式是什么?我错过了一个选项吗?
解决方案 1 似乎具有更好的性能,但对我来说它看起来“不干净”。我必须从一大堆数据行中获取某个类别的数据。

如果我选择解决方案 1,获取类别和模板数据的最佳方式是什么?
从第一个数据行读取它并在值更改后创建一个新实例?
进行某种分组?

提前致谢!这个问题让我头疼了好几天。

【问题讨论】:

  • 我很困惑,如果您使用选项#1,为什么会有冗余数据?如果您以外部类 Category 仅包含内部类的 ID 或主键的方式组织类,则它不是冗余数据。对于大多数 ORM,我使用的正是它的完成方式。
  • 也许这个简化的例子有误导性:在选项 #1 中,完整的类别和模板数据(不仅是 ID 和名称)将包含在每一行中。因此,为每个不改变的实例多次获取信息是非常多余的。
  • 我需要类别和模板的信息来用数据填充类,但是如果我按照选项 #1 加入,我会为每个实例行多次使用它。示例:类别 ID |类别名称 |分类位置 |类别XYZ |模板ID |模板名称 |模板位置 |模板XYZ |实例ID |实例名称 | ...
  • 这两种关系是 1:N 还是 N:M 的可能性?
  • 其实Category和Template的关系是N:M。为了简化起见,我将其省略了,因为当我仅获取 1 个特定类别的数据时,它并没有真正影响问题。但是,如果要获取所有类别的数据,则必须考虑这一点。

标签: c# asp.net sql database hierarchical-data


【解决方案1】:

我目前正在做的一个项目中使用实体框架。在某些情况下对其进行分析时,它确实使用了选项 1 并带回了带有冗余数据的表。因此,微软似乎选择了这种方法,并且他们拥有整个堆栈,因此大概知道如何对这个确切的问题做出正确的决定。

可能有一些启发式方法决定在某些情况下使用选项 2,但我在我的分析中没有看到它。此外,我还没有看到 EF 在单个查询中返回多个结果集。

【讨论】:

  • 非常感谢您的洞察力!我想我会将我的代码更改为选项 #1 以获得最佳性能。我不确定冗余数据是否不是糟糕设计的标志,但 Entity Framework 采用相同的方式提供了一些信心。我会将您的答案标记为已接受,但我认为这里的大多数答案对于了解解决问题的不同可能方法及其优势非常有帮助。
  • 很高兴为您提供帮助。在您的系统启动并运行后,如果您发现任何有趣的事情,请返回此处并告诉我们。这绝对是我会在我的应用程序中密切关注的事情。
【解决方案2】:

假设:您正在使用 ADO / Stored procs 并且您有一个规范化的数据结构。

您可以从一个存储过程调用中返回 3 个结果集。

1) select c.* from category c where c.id = @categoryId

2) select t.* from templates t
    join category c on t.categoryid = c.id 
    where c.id = @categoryId

3) select i.* from Instance i
    join templates t on i.templateid = t.id  
    join category c on t.categoryid = c.id
    where c.id = @categoryId

并使用 sqldatareader.read() 和 sqldatareader.Nextresult() 通过 sqldatareader 顺序填充您的对象

这确实取决于您返回的数据的大小以及您请求此数据的频率,以及您是否使用基于集合的数据检索或类似的方法。

【讨论】:

  • 是的,所有假设都是正确的。我从未使用过来自 1 个存储过程的多个结果集,我会研究一下,谢谢!数据将被非常频繁地请求。它是一个 ASP.Net 应用程序,每次用户打开类别页面时,都会进行 DB 调用。
【解决方案3】:

还有第三种选择:对每个表执行“选择 *”,然后在内存中执行连接。您可以使用 LINQ 进行一些惰性求值:

  class Category
  {
    public int CategoryId { get; set; }
    public List<Template> Templates
    {
      get
      {
        return Repository.Templates.Where(t => t.CategoryId == this.CategoryId).ToList();
      }
    }
  }

编辑:您可以对模板/实例关系使用相同的逻辑:

  class Template
  {
    public int CategoryId { get; set; }
    public int TemplateId { get; set; }
    public List<Instance> Instances
    {
      get
      {
        return Repository.Instances.Where(i => i.TemplateId == this.TemplateId).ToList();
      }
    }
  }

【讨论】:

  • 嘿,谢谢。我没有想到。似乎比选项 #2 更好,因为我只需要执行 3 个查询(每个表 1 个)。但是,确保我只收集属于我的类别的实例对象的最佳方法是什么?在带有 CategoryID 的 SQL Select 中,是通过加入 Template 表还是通过在代码中收集所有相关的 Template ID,然后在 SQL 查询中使用 IN()?
  • 您不需要任何额外的 SQL,因为一切都在内存中。我进行了编辑以显示模板/实例关系的逻辑。
  • 但是如果我只想获取 1 个特定类别的数据(我这样做),我不需要在 Instance 表(包含大量数据)上执行完整的“SELECT *” .
  • 除非您将所有内容预加载到您的存储库中,否则这将导致对数据库进行大量往返调用。
  • @atticae:我错过了您只想阅读一个类别而不是所有类别的观点。所以是的,您必须在代码中收集所有相关的模板 ID,然后使用大型 IN() 子句构建 SQL 查询。但也许您可以在您的 Instance 表中添加一个 CategoryId 列。这将是多余的,但会使您的查询更简单。
【解决方案4】:

对于少量数据选项1听起来不错,但是你应该改变类结构并为模板和实例做组合,这意味着模板类应该有一个实例的集合,类别将有一个集合模板,你可以摆脱冗余数据。

如果数据量很大并且您的网络带宽非常适合进行频繁的数据库调用,则选项 2 是首选。

【讨论】:

  • 我已经有了你描述的类结构,我只是没有在问题中详细描述这些类。例如 Category.Templates 是一个 List
【解决方案5】:

如果层次结构不是太深并且每个级别上的子级数量非常少,我通常会从选项 #2 开始。您描述的直观方法。它让我们可以摆脱使用我们已经拥有的任何方法(getTemplates()、getInstances(234) 等)。

但从性能角度来看,使用 3 表连接执行一个查询并按排序顺序处理记录可能是更快的替代方案(选项 #1)。

【讨论】:

    猜你喜欢
    • 2015-01-11
    • 2011-01-30
    • 1970-01-01
    • 2020-02-27
    • 2013-11-23
    • 1970-01-01
    • 1970-01-01
    • 2019-07-17
    • 1970-01-01
    相关资源
    最近更新 更多