Создание выражения LINQ для поиска элементов, связанных со всеми потомками узла дерева - PullRequest
3 голосов
/ 08 марта 2011

Сценарий

Я построил структуру базы данных, которая представляет дерево категорий, чтобы помочь классифицировать некоторые данные, которые мы сохранили.Реализация состоит в том, что каждая запись в таблице 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).Возможно, я сделал здесь ошибочное предположение, и именно поэтому я пришел за советом.

Проблема

Мне трудно увидеть лес длядеревья.Я не могу определить, должен ли я начать создавать предикат из категории или из членства в категории, и не могу понять необходимый код, который бы выполнил то, что я хотел бы.Я надеюсь, что кто-то еще, кто уже построил подобную древовидную структуру для базы данных, мог бы помочь мне ориентироваться в классах 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 для запрашиваемого идентификатора и создать список идентификаторов из него и всех членов Subcategories рекурсивно, а затем использовать этот список для оценки.Contains () сравнение в этом списке, но мне было интересно, если бы не было других вариантов, которые не выглядели бы так ужасно.

Ответы [ 2 ]

1 голос
/ 08 марта 2011

Для решения требовалось создать табличную функцию из рекурсивного общего табличного выражения, описанного Дэвидом Б., и выполнить запрос к результату функции в LINQ-to-SQL с помощью .Contains () в первичном ключе моей тестовой категории.Подробности того, как это сделать, приведены ниже.

Табличная функция 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 из проводника сервера, раскрывая подпапку «Функции» вашего подключения к базе данных.К сведению: это также можно увидеть в SQL Server Management Studio 2008 в разделе MyDatabase> Программируемость> Функции> Табличные функции.Теперь эта функция становится членом любого объекта контекста данных, который вы создаете.

Чтобы использовать эту функцию при решении вышеуказанных требований, я построил выражение 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 это были дети этого родителя.

1 голос
/ 08 марта 2011

Невозможно выполнить рекурсию с ограничением данных в запросе linq to sql (где вы хотите выполнять рекурсию, пока нет больше данных для извлечения).Это потому, что переводчик запросов должен знать, когда прекратить генерировать запрос, и он не может просмотреть данные, чтобы узнать, что.

Вы можете использовать Общее выражение таблицы в TSqlделать рекурсию с ограничением данных ... Если вы просто добавите CTE в представление, вы можете запросить представление от linq до sql.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...