You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何基于MVVM用无限层级EF模型填充WPF TreeView

Fixing Infinite Hierarchical Categories with Entity Framework & WPF TreeView

Hey there! The problem you're facing is that your current code only manually loads two levels of categories, but we can adjust this to support unlimited nesting without much hassle. Let's start with fixing the model first, then move on to loading the hierarchical data correctly, and confirm the WPF setup is right.

Step 1: Correct the Entity Framework Model

First, there's a small issue with your Category model that might be causing navigation property confusion. The SubCategories collection shouldn't have the ForeignKey attribute—instead, that attribute belongs to a single ParentCategory property, which represents the parent of the current category. Here's the corrected model:

public class Category {
    public Category() { SubCategories = new ObservableCollection<Category>(); }
    
    [Column("id")] 
    public int Id { get; set; }
    
    [StringLength(100)] 
    public string Name { get; set; }
    
    [Column("ParentID")] 
    public int? ParentID { get; set; }
    
    // ForeignKey points to the parent category entity
    [ForeignKey("ParentID")] 
    public virtual Category ParentCategory { get; set; }
    
    // Collection of child categories
    public virtual ObservableCollection<Category> SubCategories { get; set; }
}

This fixes the EF navigation property mapping so it correctly understands the parent-child relationship.

Step 2: Load Infinite Hierarchical Data

We have two solid approaches to load unlimited category levels, depending on your data size:

Option 1: Recursive Loading (Simple, Small Datasets)

If your category tree isn't too large, a recursive method will work perfectly. It loads root categories first, then recursively loads each node's children:

// Initialize root categories (ParentID is null)
Categories = new ObservableCollection<Category>(db.Categories.Where(x => x.ParentID == null));

// Recursively load child categories for each root
foreach (var rootCat in Categories)
{
    LoadChildCategories(rootCat);
}

// Recursive helper method
private void LoadChildCategories(Category parentCategory)
{
    // Get all direct children of the current parent
    var childCategories = db.Categories.Where(x => x.ParentID == parentCategory.Id).ToList();
    
    foreach (var childCat in childCategories)
    {
        parentCategory.SubCategories.Add(childCat);
        // Recurse to load this child's children
        LoadChildCategories(childCat);
    }
}

Option 2: Single Database Query with CTE (Better for Large Datasets)

For larger category trees, recursive database queries (using Common Table Expressions, CTE) are more efficient—they pull all categories in one go, then we build the hierarchy in memory:

// Use a CTE to fetch all categories with their hierarchy level
var allCategories = db.Categories.FromSqlRaw(@"
    WITH CategoryHierarchy AS (
        SELECT Id, Name, ParentID, 0 AS Level
        FROM Categories
        WHERE ParentID IS NULL
        UNION ALL
        SELECT c.Id, c.Name, c.ParentID, ch.Level + 1
        FROM Categories c
        INNER JOIN CategoryHierarchy ch ON c.ParentID = ch.Id
    )
    SELECT * FROM CategoryHierarchy
").ToList();

// Build the hierarchy in memory using a lookup for fast access
var categoryLookup = allCategories.ToLookup(c => c.ParentID);
var rootCategories = new ObservableCollection<Category>(allCategories.Where(c => c.ParentID == null));

foreach (var category in allCategories)
{
    // Add all children to the current category's SubCategories collection
    foreach (var childCat in categoryLookup[category.Id])
    {
        category.SubCategories.Add(childCat);
    }
}

Categories = rootCategories;

This approach minimizes database round-trips, which is crucial for performance with large datasets.

Step 3: Fix the WPF TreeView XAML

Your TreeView setup is almost right—just a small typo in the DataType (it should be data:Category, singular, since your model class is named Category):

<TreeView Grid.Row="0" ItemsSource="{Binding Categories}" MinWidth="220">
    <TreeView.Resources>
        <HierarchicalDataTemplate DataType="{x:Type data:Category}" ItemsSource="{Binding SubCategories}">
            <StackPanel Orientation="Horizontal">
                <TextBlock Text="{Binding Id}" Margin="3 2" />
                <TextBlock Text=" - "/>
                <TextBlock Text="{Binding Name}" Margin="3 2" />
            </StackPanel>
        </HierarchicalDataTemplate>
    </TreeView.Resources>
    <e:Interaction.Behaviors>
        <behaviours:BindableSelectedItemBehavior SelectedItem="{Binding SelectedTreeCategory, Mode=TwoWay}" />
    </e:Interaction.Behaviors>
</TreeView>

The HierarchicalDataTemplate will automatically render child nodes recursively as long as the SubCategories collection is properly populated—no extra work needed here!

Bonus: Lazy Loading (Optional)

If you enable lazy loading in EF (it's enabled by default in some versions), you could avoid manual loading entirely. EF will automatically load child categories when you access the SubCategories collection. However, this can lead to N+1 database queries (one query for each node), which is not ideal for large trees. Stick with the recursive or CTE methods for better performance.

内容的提问来源于stack exchange,提问作者Ahmed Mohammed

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.13 07:31:20