3

場景構建LINQ表達找到相關的樹節點

我已經建立了一個代表類別樹,以幫助進行分類一些我們所儲存的數據的數據庫結構的所有後代項目。實現方式是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表達的對象,這將有助於我作出這樣的決定。這當然是基於假設CategoryMembership表是開始檢索IQueryable(Of Item)的地方。我可能在這裏做了一個錯誤的假設,這就是我尋求建議的原因。

的問題

我有一個很難只見樹木不見森林。我無法確定是應該從Category還是從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 

不過,我認識到,一個謂詞建設者可能是沒有用的,在這裏和任何可能需要不同的方向。

我認爲總會有選擇Category紀錄被請求的ID,並從中建立ID的列表和Subcategories所有成員遞歸然後使用該名單,以評估對一。載有()比較的可能性名單,但我想知道是否沒有其他選項不那麼難受。

回答

1

您不能在linq中執行數據限制遞歸到sql查詢(您想要遞歸直到沒有更多數據要獲取)。這是因爲查詢翻譯器需要知道何時停止生成查詢,並且無法查看數據以瞭解該情況。

您可以在TSql中使用Common Table Expression來執行數據限制遞歸......如果您只是在視圖中拍攝該CTE,則可以查看從linq到sql的視圖。

+0

我討厭聽到「你不能這樣做」,但我理解你的意思。這是一個失望,我不能只是禁用延遲執行,以便允許數據限制。不幸的是,編程解決方案爲我最終確定的.Any()重載引發了一個不支持的異常,這會導致我想要的行爲(如您所示)。感謝您指點我CTE。一旦完成,我將發佈存儲過程的結果。 – lsuarez 2011-03-08 05:56:44

1

該解決方案需要從David B描述的遞歸公用表表達式中創建表值函數,並使用我的測試類別的主鍵上的.Contains()來查詢LINQ到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 

) 

此表值函數現在可以包含在您的DBML從通過擴大你的數據庫連接的「功能」子文件夾中的服務器資源管理器。僅供參考:在MyDatabase>可編程性>函數>表值函數下,SQL Server Management Studio 2008中也可以看到它。該函數現在成爲您實例化的任何數據上下文對象的成員。

爲了解決上述規定應用這個功能,我構造的LINQ到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的成員。

+0

我最終將表值函數轉換爲存儲過程(在腳本中實際上不需要重寫),以便稍後在ADO.NET實體模型中使用。在這種情況下,VB.NET代碼的用例根本不會改變。 – lsuarez 2011-03-09 22:31:42