web-dev-qa-db-ja.com

カテゴリの親ID自己参照テーブル構造をEFコアエンティティにマップします

データベーステーブル:

画像

このアプローチを試して、カテゴリテーブルをEFコアにマッピングしました。

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<Category>(entity =>
    {
        entity
            .HasMany(e => e.Children)
            .WithOne(e => e.Parent) 
            .HasForeignKey(e => e.ParentId);
    });
}

エンティティ:

[Table("Category"]
public class Category : EntityBase
{
    [DataType(DataType.Text), MaxLength(50)]
    public string Name { get; set; }

    public int? ParentId { get; set; }

    public int? Order { get; set; }

    [ForeignKey("ParentId")]
    public virtual Category Parent { get; set; }

    public virtual ICollection<Category> Children { get; set; }
}

次に、リポジトリ内:

public override IEnumerable<Category> GetAll()
{ 
    IEnumerable<Category> categories = Table.Where(x => x.Parent == null).Include(x => x.Children).ThenInclude(x=> x.Children);
    return categories;
}

これは機能しましたが、Include()またはThenInclude()を何度呼び出しても、3レベル以降は返されませんでした。

子カテゴリに再帰関数を設定するコードを自分で作成することになりました。

public override IEnumerable<Category> GetAll()
{
    IEnumerable<Category> categories = Table.Where(x => x.Parent == null).ToList();
    categories = Traverse(categories);
    return categories;
}

private IEnumerable<Category> Traverse(IEnumerable<Category> categories)
{
    foreach(var category in categories)
    {
        var subCategories = Table.Where(x => x.ParentId == category.Id).ToList();
        category.Children = subCategories;
        category.Children = Traverse(category.Children).ToList();
    }
    return categories;
}

テーブル階層を取得し、例で提供したカテゴリエンティティにマップするストアドプロシージャを作成するためのより良い方法を知っている人はいますか?

9
lschmid

EF(および一般的にLINQ)には、再帰式/ CTEサポートがないため、データのようなツリーの読み込みに問題があります。

ただし、(フィルタリングされたツリーブランチではなく)wholeツリーをロードする場合は、単純なIncludeベースのソリューションがあります。必要なのは単一のIncludeだけで、EFナビゲーションプロパティの修正が自動的に機能します。また、サンプルのようにルートノードのみを取得する必要がある場合は、クエリがマテリアライズされた後(およびナビゲーションプロパティが修正された後)に、LINQ to Objectsコンテキストに切り替えて(AsEnumerable()を使用)、フィルターを適用するのがコツです。いつものように)。

したがって、以下は単一のSQLクエリで目的の結果を生成するはずです。

public override IEnumerable<Category> GetAll()
{ 
    return Table
       .Include(x => x.Children)
       .AsEnumerable()
       .Where(x => x.Parent == null)
       .ToList();
}
11
Ivan Stoev