EFコアにおける木構造自己参照表の構成法とその利用



EFコアにおける木構造自己参照表の構成法とその利用


私がから得ている非常に一般的な質問の1つ.NET Communityは、EFコアのツリー構造の設定と使用方法です.この物語はそれを可能にする方法の一つです.
共通のツリー構造はファイルツリー、カテゴリ階層などである.例えば、フォルダツリーとしてください.エンティティークラスはフォルダとなります.
public class Folder
{
    public Guid Id { get; set; }
    public string Name { get; set; }      
    public Folder Parent { get; set; }
    public Guid? ParentId { get; set; }
    public ICollection<Folder> SubFolders { get; } = new List<Folder>();
}
これは、DBContextClassのモデルメソッドをオーバーライドしてDBスキーマを構成する方法です.これはエンティティークラスのConfiguration Property属性を介して行うことができますが、この方法でDBスキーマを定義することを好みます.
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<Folder>(entity =>
    {
        entity.HasKey(x => x.Id);
        entity.Property(x=> x.Name);
        entity.HasOne(x=> x.Parent)
            .WithMany(x=> x.SubFolders)
            .HasForeignKey(x=> x.ParentId)
            .IsRequired(false)
            .OnDelete(DeleteBehavior.Restrict);
    });

    // ...
}
これは、DBのデータをフォルダのツリーとして読み込む方法です.フォルダ「ノード」のプレーンリストとして平らにし、ノードレベルやノード親などのツリー構造に関する詳細を取得します.
{
    List<Folder> all = \_dbContext.Folders.Include(x => x.Parent).ToList();

    TreeExtensions.ITree<Folder> virtualRootNode = all.ToTree((parent, child) => child.ParentId == parent.Id);

    List<TreeExtensions.ITree<Folder>> rootLevelFoldersWithSubTree = virtualRootNode.Children.ToList();

    List<TreeExtensions.ITree<Folder>> flattenedListOfFolderNodes = virtualRootNode.Children.Flatten(node => node.Children).ToList();

    // Each Folder entity can be retrieved via node.Data property:
    TreeExtensions.ITree<Folder> folderNode = flattenedListOfFolderNodes.First(node => node.Data.Name == "MyFolder");

    Folder folder = folderNode.Data;
    int level = folderNode.Level;
    bool isLeaf = folderNode.IsLeaf;
    bool isRoot = folderNode.IsRoot;
    ICollection<TreeExtensions.ITree<Folder>> children = folderNode.Children;
    TreeExtensions.ITree<Folder> parent = folderNode.Parent;
    List<Folder> parents = GetParents(folderNode);
}
このメソッドは、特定のノードのすべての親をツリーから取得する方法を示します
private static List<T> GetParents<T>(TreeExtensions.ITree<T> node, List<T> parentNodes = null) where T : class
{
    while (true)
    {
        parentNodes ??= new List<T>();

        if (node?.Parent?.Data == null) return parentNodes;

        parentNodes.Add(node.Parent.Data);

        node = node.Parent;
    }
}
ツリー操作の拡張メソッド、および任意のエンティティをツリーノードオブジェクトにラップするヘルパーインターフェイス.それで、技術的には、あなたのエンティティは、親親子関係子を持つクラス( flattenを使ってツリーからノードの平凡なリストを得る)と子-> parent ( totreemethodを使ってリストからツリーを構築する)クラスです.
public static class TreeExtensions
{
    /// <summary> Generic interface for tree node structure </summary>
    /// <typeparam name="T"></typeparam>
    public interface ITree<T>
    {
        T Data { get; }
        ITree<T> Parent { get; }
        ICollection<ITree<T>> Children { get; }
        bool IsRoot { get; }
        bool IsLeaf { get; }
        int Level { get; }
    }

    /// <summary> Flatten tree to plain list of nodes </summary>
    public static IEnumerable<TNode> Flatten<TNode>(this IEnumerable<TNode> nodes, Func<TNode, IEnumerable<TNode>> childrenSelector)
    {
        if (nodes == null) throw new ArgumentNullException(nameof(nodes));

        return nodes.SelectMany(c => childrenSelector(c).Flatten(childrenSelector)).Concat(nodes);
    }

    /// <summary> Converts given list to tree. </summary>
    /// <typeparam name="T">Custom data type to associate with tree node.</typeparam>
    /// <param name="items">The collection items.</param>
    /// <param name="parentSelector">Expression to select parent.</param>
    public static ITree<T> ToTree<T>(this IList<T> items, Func<T, T, bool> parentSelector)
    {
        if (items == null) throw new ArgumentNullException(nameof(items));

        var lookup = items.ToLookup(item => items.FirstOrDefault(parent => parentSelector(parent, item)),
            child => child);

        return Tree<T>.FromLookup(lookup);
    }

    /// <summary> Internal implementation of <see cref="ITree{T}" /></summary>
    /// <typeparam name="T">Custom data type to associate with tree node.</typeparam>
    internal class Tree<T> : ITree<T>
    {
        public T Data { get; }

        public ITree<T> Parent { get; private set; }

        public ICollection<ITree<T>> Children { get; }

        public bool IsRoot => Parent == null;

        public bool IsLeaf => Children.Count == 0;

        public int Level => IsRoot ? 0 : Parent.Level + 1;

        private Tree(T data)
        {
            Children = new LinkedList<ITree<T>>();
            Data = data;
        }

        public static Tree<T> FromLookup(ILookup<T, T> lookup)
        {
            var rootData = lookup.Count == 1 ? lookup.First().Key : default(T);
            var root = new Tree<T>(rootData);
            root.LoadChildren(lookup);
            return root;
        }

        private void LoadChildren(ILookup<T, T> lookup)
        {
            foreach (var data in lookup[Data])
            {
                var child = new Tree<T>(data) {Parent = this};
                Children.Add(child);
                child.LoadChildren(lookup);
            }
        }
    }
}
助ける望み.をお楽しみください.