ef 更新数据库表结构_EF Core中的树结构:如何配置和使用自引用表

本文介绍了如何在EF Core中配置和使用树结构,以处理如文件树、类别层次结构等常见场景。通过覆盖DbContext类的方法来定义数据库模式,并展示了如何加载、展平数据,获取节点详情,以及进行树操作的方法。

ef 更新数据库表结构

One of the very common questions I am getting from .NET community is how to configure and use the tree structures in EF Core. This story is one of the possible ways to do it.

我从.NET社区获得的一个非常常见的问题之一是如何在EF Core配置和使用树结构。 这个故事是实现它的可能方法之一。

The common tree structures are file tree, categories hierarchy, and so on. Let it be folders tree for example. The entity class will be a Folder:

常见的树结构是文件树,类别层次结构等。 例如,让它成为文件夹树。 实体类将是Folder

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>();
}

This is how to configure DB schema via overriding OnModelCreating method of your DbContext class. This could be done via configuration property attributes on our entity class, but I prefer to define DB schema this way.

这是通过覆盖DbContext类的OnModelCreating方法配置数据库架构的方法。 这可以通过我们实体类上的配置属性属性来完成,但是我更喜欢以这种方式定义数据库模式。

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);
    });
    // ...
}

This is how to load data from DB as a tree of folders, flatten it as a plain list of folder “nodes”, and get some details related to tree structure like node level, node parents, etc.:

这是如何从数据库中将数据作为文件夹树加载,将其展平为文件夹“节点”的简单列表,以及获取与树结构相关的一些详细信息,例如节点级别,节点父级等:

{
    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);
}

This method demonstrates how to get all parents from the tree for specific node:

此方法演示如何从树中为特定节点获取所有父级:

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;
    }
}

Tree operation extension methods below and helper interface for wrapping any entity into the tree node object. So technically your entity can be any class where you have relation parent->children (to get the plain list of nodes from the tree using Flatten) and child-> parent (to build the tree from the list using ToTree method):

下面的树操作扩展方法和帮助程序接口,用于将任何实体包装到树节点对象中。 因此,从技术上讲,您的实体可以是您具有以下关系的任何类: parent->children (使用Flatten从树中获得节点的纯列表)和child-> parent (使用ToTree方法从列表中构建树):

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);
            }
        }
    }
}

Hope that helps. Enjoy coding with the Coding Machine

希望能有所帮助。 享受使用编码机进行编码

翻译自: https://habr.com/en/post/516596/

ef 更新数据库表结构

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值