情景
我已经构建了一个表示类别树的数据库结构,以帮助对我们存储的一些数据进行分类。实现是Category
表中的每个记录都有一个可以为空的外键回到Category
表中以表示此类别的父Category
(一对多),基本上允许对于更广泛的父级别中的子类别。有一个CategoryMembership
表将Item
表中的记录链接到各自的Category
(多对多)。我为这个数据库创建了DBML,它有一个包含以下内容的成员访问结构:
Dim aCategory As New Category()
Dim aParentCategory As Category = aCategory.Parent
Dim aChildCategoryCollection As EntitySet(Of Category) = aCategory.Subcategories
Dim aMembershipCollection As EntitySet(Of CategoryMembership) = aCategory.CategoryMemberships
aMembershipCollection
中的每个项目都具有以下成员访问结构:
Dim aMembership As CategoryMembership = aMembershipCollection.First()
Dim aLinkedCategory As Category = aMembership.Category
Dim aLinkedItem As Item = aMembership.Item
要求
我正在尝试构建一个LINQ表达式,该表达式允许我确定哪个Items
具有CategoryMemberships
Category
(即aCategory.id = myID
)或成员资格的成员资格请求Category
的后代,我的想法是我想要父类别中的所有Items
或其子类别的多个级别。
本质上,查询将以类似于:
的方式构建Dim results As IQueryable(Of Item) = _
From cm In db.CategoryMemberships.Where(myInCategoryPredicate(myID)) _
Select cm.Item
...其中myInCategoryPredicate
返回LINQ Expression对象,这有助于我做出决定。这当然是假设CategoryMembership
表是开始检索IQueryable(Of Item)
的地方。我可能在这里做了一个错误的假设,这就是我寻求建议的原因。
问题
我很难看到森林里的树木。我无法确定是否应该从类别或CategoryMembership开始构建谓词,也无法理解完成我想要的必要代码。我希望已经为数据库构建了类似树结构的其他人可以帮助我绕过DBML类。
可用资源
我以前曾使用过PredicateBuilder并且对它的工作方式比较熟悉但是我无法设法向上遍历树并以递归方式构建谓词,这表明是否项目属于要求类别或其子项的类别。到目前为止,我已经生成了以下内容,标记为SomeRecursiveCall()的显着间隙:
Private Function InCategory(ByVal myID As Integer) As Expression(Of Func(Of CategoryMembership, Boolean))
Dim predicate = PredicateBuilder.False(Of CategoryMembership)()
predicate = predicate.Or(Function(cm) cm.fkCategoryID = myID OrElse SomeRecursiveCall())
Return predicate
End Function
但是,我意识到谓词构建器在这里可能毫无用处,可能需要不同的方向。
我认为总是有可能为所请求的ID选择Category
记录,然后递归地建立一个ID列表和Subcategories
的所有成员,然后使用该列表来评估.Contains()比较该列表,但我想知道是否没有其他选项不那么难看。
答案 0 :(得分:1)
你不能在linq to sql查询中进行数据限制递归(你想要递归,直到没有更多的数据要提取)。这是因为查询翻译需要知道何时停止生成查询,并且无法查看数据以了解该信息。
您可以在TSql中使用Common Table Expression进行数据限制递归...如果您只是在视图中拍摄CTE,则可以查询从linq到sql的视图。
答案 1 :(得分:1)
该解决方案需要从David B描述的递归公用表表达式创建表值函数,并在我的测试类别的主键上使用.Contains()查询LINQ-to-SQL中的函数结果。有关如何完成的详细信息如下。
使用以下脚本声明了表值函数GetAllCategories。当给定参数@ParentCategoryID时,它返回该父项以及所有子类别以及每个记录相对于父项的相应深度作为名为CategoryLevel的新字段。
USE MyDatabase
GO
IF OBJECT_ID (N'dbo.GetAllCategories') IS NOT NULL
DROP FUNCTION dbo.GetAllCategories
GO
CREATE FUNCTION dbo.GetAllCategories(@ParentCategoryID int)
RETURNS TABLE
AS RETURN
(
WITH AllCategories (pkCategoryID, fkParentID, Name, Description, CategoryLevel)
AS
(
-- Anchor member definition
SELECT c.pkCategoryID, c.fkParentID, c.Name, c.Description,
0 AS CategoryLevel
FROM dbo.Category AS c
WHERE c.pkCategoryID = @ParentCategoryID
UNION ALL
-- Recursive member definition
SELECT c.pkCategoryID, c.fkParentID, c.Name, c.Description,
CategoryLevel + 1
FROM dbo.Category AS c
INNER JOIN AllCategories AS ac
ON c.fkParentID = ac.pkCategoryID
)
SELECT *
FROM AllCategories
)
此表值函数现在可以通过展开数据库连接的“Functions”子文件夹从服务器资源管理器中包含在DBML中。仅供参考:在MyDatabase>下的SQL Server Management Studio 2008中也可以看到它。可编程性>功能>表值函数。此函数现在成为您实例化的任何数据上下文对象的成员。
为了在解决上述要求时使用此函数,我构造了一个LINQ-to-SQL表达式,如下所示:
Using db As New MyDatabaseDataContext()
Dim results As IQueryable(Of Item) =
From cm In db.CategoryMemberships _
Where (From i In db.GetAllCategories(searchValue) _
Select i.pkCategoryID).Contains(cm.Category.pkCategoryID) _
Select cm.Item
End Using
该表达式从函数结果中投射所有主键的列表,并使用.Contains()
扩展名测试其中每个CategoryMembership
记录Category
是否存在主键。如果成功,则会选择相应的Item
成员资格。
这会返回Items
成员的所有Category
,其中主键等于searchValue
,或者是该父母子女的任何Category
成员。< / p>