Warning: file_get_contents(/data/phpspider/zhask/data//catemap/2/csharp/281.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181

Warning: file_get_contents(/data/phpspider/zhask/data//catemap/8/linq/3.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181

Warning: file_get_contents(/data/phpspider/zhask/data//catemap/6/cplusplus/143.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
C# 类别树搜索的优化解决方案_C#_Linq_Optimization_Asp.net Mvc 2_Business Logic Toolkit - Fatal编程技术网

C# 类别树搜索的优化解决方案

C# 类别树搜索的优化解决方案,c#,linq,optimization,asp.net-mvc-2,business-logic-toolkit,C#,Linq,Optimization,Asp.net Mvc 2,Business Logic Toolkit,我正在创建某种拍卖应用程序,我必须确定解决此问题的最佳方式。我使用BL工具包作为我的或映射器,它有很好的Linq支持和ASP.NETMVC2 出身背景 我有多个动态创建的类别对象,它们作为此类的表示保存在我的数据库中: class Category { public int Id { get; set; } public int ParentId { get; set; } public string Name { get; set; } } 现在,每个类别对象都可以

我正在创建某种拍卖应用程序,我必须确定解决此问题的最佳方式。我使用BL工具包作为我的或映射器,它有很好的Linq支持和ASP.NETMVC2

出身背景 我有多个动态创建的类别对象,它们作为此类的表示保存在我的数据库中:

class Category
{
    public int Id { get; set; }
    public int ParentId { get; set; }
    public string Name { get; set; }
}
现在,每个类别对象都可以关联多个InformationClass对象,这些对象表示该类别中的单个信息,例如价格或颜色。这些类也由管理员动态创建并存储在数据库中。对于一组类别,有特定的定义。表示它的类如下所示:

class InformationClass
{
    public int Id { get; set; }
    public InformationDataType InformationDataType { get; set; }
    public string Name { get; set; }
    public string Label { get; set; }
}
现在我有了第三个表,表示它们之间的连接,如下所示:

class CategoryInformation
{
    public int InformationClassId { get; set; }
    public int AuctionCategoryId { get; set; }
}
class CategoryInformation
{
    public int CategoryId { get; set; }
    public int InformationClassId { get; set; }
}
var categoryId = ...;
var infoClasses = db.CategoryInformation
    .Where(cinf => db.CategoryTC.Where(tc => tc.Descendant == categoryId)
                                .Any(tc => tc.Ancestor == cinf.CategoryId))
    .Select(cinf => db.InformationClass
                      .FirstOrDefault(ic => ic.Id == cinf.InformationClassId));
问题 现在的问题是,我需要继承子类别中的所有类别InformationClass。例如,每个产品都有一个价格,所以我只需要将这个InformationClass添加到我的根类别中。频率信息可以添加到基本CPU类别中,它应该可以在AMD和Intel类别中使用,这些类别将从CPU类别派生

我必须知道哪些InformationClass对象在我的应用程序中经常与指定的类别相关

这是我的问题。这个问题的最佳解决方案是什么?我有一些想法,但我不能决定

将所有类别从数据库加载到应用程序表,并每次从这个位置获取它们-只要类别不会经常更改,这将减少数据库请求的数量,但仍然需要使用Linq对对象进行树搜索 发明我不知道是否可能有一些奇特的Linq查询,可以进行树搜索并获得所有信息类id,而不会对数据库造成太大压力。 还有别的好主意吗?
我将对每一个答案和想法表示感谢。谢谢大家的建议。

听起来像是我曾经有过的一个想法的一个案例,我在博客上谈到了:

基本思想是:除了Category表之外,还有CategoryTC表,其中包含父子关系的传递闭包。它允许您快速有效地检索特定类别的所有祖先或后代类别的列表。这篇博文解释了如何在每次创建、删除新类别或更改父子关系时保持可传递闭包的最新状态—每次最多两个查询

这篇文章使用SQL来表达这个想法,但我相信你可以把它翻译成LINQ

您在问题中没有指定InformationClass表是如何链接到Category表的,因此我必须假设您有一个CategoryInformation表,看起来像这样:

class CategoryInformation
{
    public int InformationClassId { get; set; }
    public int AuctionCategoryId { get; set; }
}
class CategoryInformation
{
    public int CategoryId { get; set; }
    public int InformationClassId { get; set; }
}
var categoryId = ...;
var infoClasses = db.CategoryInformation
    .Where(cinf => db.CategoryTC.Where(tc => tc.Descendant == categoryId)
                                .Any(tc => tc.Ancestor == cinf.CategoryId))
    .Select(cinf => db.InformationClass
                      .FirstOrDefault(ic => ic.Id == cinf.InformationClassId));
然后,您可以使用以下方法获取与特定类别关联的所有InformationClass:

class CategoryInformation
{
    public int InformationClassId { get; set; }
    public int AuctionCategoryId { get; set; }
}
class CategoryInformation
{
    public int CategoryId { get; set; }
    public int InformationClassId { get; set; }
}
var categoryId = ...;
var infoClasses = db.CategoryInformation
    .Where(cinf => db.CategoryTC.Where(tc => tc.Descendant == categoryId)
                                .Any(tc => tc.Ancestor == cinf.CategoryId))
    .Select(cinf => db.InformationClass
                      .FirstOrDefault(ic => ic.Id == cinf.InformationClassId));

这有意义吗?任何问题,请提问。

在过去的SQLServer2005和LINQ之前,当处理这种结构或更一般的有向无环图时,使用连接表实现,以便项可以有多个父项,我要么将整个图加载到内存中,或者在数据库中创建一个跳跳虎更新的查找表,该表缓存在祖先到后代的关系中

两者都有优点,哪一个胜出取决于更新频率、父子关系之外对象的复杂性以及更新频率。一般来说,加载到内存中允许更快的单个查找,但对于大型图形,由于每个Web服务器中使用的内存量,它在本地无法扩展,因为webfarm情况下,将项目缓存在内存中会带来额外的问题,这意味着你必须非常小心如何保持事物的同步以对抗这种效果

现在可用的第三个选项是使用递归CTE执行祖先查找:

CREATE VIEW [dbo].[vwCategoryAncestry]
AS
WITH recurseCategoryParentage (ancestorID, descendantID)
AS
(
    SELECT parentID, id
    FROM Categories
    WHERE parentID IS NOT NULL

    UNION ALL

    SELECT ancestorID, id
    FROM recurseCategoryParentage
        INNER JOIN Categories ON parentID = descendantID
)
SELECT DISTINCT ancestorID, descendantID
FROM recurseCategoryParentage
假设根类别由空parentID表示

我们使用UNION ALL,因为我们将在以后选择DISTINCT,这样我们就有了一个单独的DISTINCT操作,而不是重复它

这使我们能够在不冗余非规范化表的情况下执行查找表方法。效率权衡明显不同,通常比使用表时更差,但在选择时不会有太多轻微的命中,在插入和删除时会有轻微的增益,可忽略的空间增益,但对正确性的保证更大

我忽略了LINQ在这方面的适用性问题,因为无论以何种方式进行查询,权衡都是一样的。LINQ可以更好地处理具有单个主键的表,因此我们可以更改select子句,将DISTINCT castancestorID选择为bigint*0x100000000+DERANDANTID作为id、ancestorID、DERANDANTID,并将其定义为t中的主键 他[专栏]的属性。当然,所有列都应该表示为DB generated

编辑。还有一些关于所涉及的权衡问题

将CTE方法与数据库中维护的查找进行比较:

专业:

CTE代码很简单,上面的视图是您需要的所有额外DB代码,而C代码是相同的。 DB代码都在一个地方,而不是在不同的表上同时存在一个表和一个触发器。 插入和删除速度更快;这不会影响他们,而触发器会影响他们。 虽然在语义上是递归的,但在某种程度上它是查询规划人员能够理解和处理的,因此它通常只在两个索引扫描中实现任何深度,这两个索引扫描可能聚集在两个轻量级假脱机、一个串联和一个独特的排序中,而不是在您可能想象的许多扫描中实现。因此,虽然扫描肯定比简单的表查找更重,但它远没有一开始想象的那么糟糕。事实上,即使这两个索引扫描的性质是相同的表,不同的行也会使它比您在阅读时想象的要便宜。 如果以后的经验证明这是一条可行的道路,那么用表查找来代替它是非常容易的。 从本质上讲,查找表将使数据库非规范化。撇开纯度问题不谈,所涉及的臭味意味着这将必须向任何新开发人员解释和证明,因为在那之前,它可能看起来只是错误的,他们的直觉会让他们在试图消除它时陷入白费力气。 Pro查找表:

虽然CTE的选择速度比人们想象的要快,但查找速度仍然更快,尤其是当用作更复杂查询的一部分时。 虽然CTE和用于创建CTE的WITH关键字是SQL 99标准的一部分,但它们相对较新,一些开发人员不了解它们,尽管我认为这个特定的CTE非常简单易读,因此它算得上是一个很好的学习示例,所以可能这实际上是支持CTE的! 虽然CTE是SQL 99标准的一部分,但某些SQL数据库(包括仍在使用的旧版本的SQLServer)并没有实现CTE,这可能会影响任何移植工作。虽然Oracle和Postgres等公司都支持它们,但在这一点上,这可能不是一个真正的问题。 如果以后的经验表明您应该使用CTE版本,那么用CTE版本替换它是相当容易的。 将db重载选项与内存缓存进行比较

内存中的Pro:

除非您的实现真的很糟糕,否则它将比DB查找快得多。 这使得一些二次优化成为可能。 如果稍后的评测显示内存中是一条可行之路,那么从DB更改为内存中是相当困难的。 Pro查询数据库:

在内存中,启动时间可能非常慢。 对数据的更改要简单得多。大部分要点都是这方面的。实际上,如果您选择内存中的方式,那么如何处理使缓存信息无效的更改就成为项目生命周期中一个全新的持续关注的问题,而不是一个微不足道的问题。 如果您在内存中使用,您可能不得不使用此内存存储,即使对于与此无关的操作,这可能会使它与其余数据访问代码的匹配变得复杂。 无需跟踪更改和缓存新鲜度。 没有必要确保web服务器场和/或web花园解决方案中的每个web服务器在一定程度上的成功将使其具有完全相同的新鲜度。 类似地,通过将Web服务器和DB从属服务器的数量增加一倍,可以获得接近100%的额外性能。 在内存中,如果对象数量较多或对象字段(尤其是字符串、集合和本身具有sting或collection的对象)的大小较大,则内存使用率可能会非常高。可能我们需要更大的Web服务器内存量,这适用于农场中的每台机器。 7a。随着项目的发展,大量内存的使用会继续增加。 除非更改导致内存存储立即刷新,否则内存解决方案将意味着负责管理这些类别的人员使用的视图将与客户看到的视图不同,直到重新同步。 内存中的重新同步可能非常昂贵。除非你非常聪明,否则它会随机地给用户带来巨大的性能峰值。如果你很聪明的话,它会加剧其他问题,特别是在保持不同机器的新鲜度方面。 除非你在记忆方面很聪明,否则这些尖峰会累积,使机器长期处于停滞状态。如果你聪明地避免了这一点,你可能会激怒其他问题。 从内存中移动到命中db是非常困难的,如果这证明了方法的正确性。 所有这些都不能百分之百确定地依赖于一种或另一种解决方案,因此
我们不会给出一个明确的答案,因为这样做是过早的优化。你能做的是事先做出合理的决定,哪一个可能是最佳解决方案。无论你选择哪一种,你都应该在事后进行分析,特别是当代码确实成为瓶颈并且可能发生变化时。您还应该在产品的生命周期内这样做,因为对代码修复和新功能的更改以及对数据集的更改都肯定会改变哪个选项是最佳的。事实上,它可以在生命周期内从一个选项更改到另一个选项,然后再更改回前一个选项。这就是为什么我把从一种方法转移到另一种方法的方便性纳入上述利弊清单中的原因。

@lukasz拍卖应用的方式是什么。。。像易趣之类的。。。这个问题也可能与任何一家商店有关,这些商店的类别中都有与之相关的特定信息。看起来不错,但我看到了这个问题。当我将有更多的类别,将更多的嵌套,那么TC记录的数量将是巨大的。而且,它不是比将所有类别放入内存效率更低吗?我的意思是-它们不会经常更改,这是一个重要的假设-您的解决方案似乎适合一般使用。@ukaszW.pl-① 不,TC桌很小。每行只有两个整数!即使是一百万个父/子链接,也只有8MB,可能比在类别表中添加一个新的字符串列要少。-② 如果您的所有类别都适合内存,并且由于您一直在查询它们而值得这样做,那么SQL Server将已经这样做了。为什么要保留一个副本,迫使您担心当它过时时必须更新它,等等,而SQL Server已经为您这样做了吗?我刚刚注意到在“2”小节的博客文章中有一个错误。修复了它。您能更简单地解释一下我在SQL Server中不是很好,为什么这个视图是一个好方法。我的意思是我必须告诉我的同事我为什么选择这个解决方案。这对我来说很好,因为它很简单,但我必须知道为什么这种查询对数据库来说不太重要,因为在我看来,递归解决方案对我来说意味着它的性能应该很差。。。当我将所有数据加载到我的应用程序时,我可以执行此操作一次。当我使用我的视图时,它不会总是再次执行它吗?当然。这些考虑并不是无关紧要的,所以我修改了上面的答案。