EF基础知识小记六(使用Code First建模自引用关系,常用于系统菜单文件目录等有层级之分的实体)

Posted Green.Leaf

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了EF基础知识小记六(使用Code First建模自引用关系,常用于系统菜单文件目录等有层级之分的实体)相关的知识,希望对你有一定的参考价值。

日常开发中,经常会碰到一些自引用的实体,比如系统菜单、目录实体,这类实体往往自己引用自己,所以我们必须学会使用Code First来建立这一类的模型.

以下是自引用表的数据库关系图:

ok,下面开始介绍从零创建一个Code First版的自引用模型.

1、往目标项目中添加EF包,通过NuGet程序包添加

 

导入相关的程序集.

2、创建自引用实体类

    public class Category
    {
        [Key]
        [DatabaseGenerated(DatabaseGeneratedOption.Identity)]
        public int CategoryId { get; private set; }
        public string Name { get; set; }
        public int? ParentCategoryId { get; private set; }
        [ForeignKey("ParentCategoryId")]
        public virtual Category ParentCategory { get; set; } 
        public virtual List<Category> Subcategories { get; set; }
        public Category()
        {
            Subcategories = new List<Category>();
        }
    }

3、创建一个数据库上下文,该上下文必须继承DbContext,代码如下:

    public class EF6RecipesContext : DbContext
    {
        public DbSet<Category> Categories { get; set; }
        public EF6RecipesContext() 
            : base("name=EF6RecipeEntities")
        {
        }
        protected override void OnModelCreating(DbModelBuilder modelBuilder)
        {
            base.OnModelCreating(modelBuilder);
            modelBuilder.Entity<Category>().HasMany(cat => cat.Subcategories).WithOptional(cat => cat.ParentCategory);
        }
    }

4、截至这一步,分析下代码,典型的目录实体,从实体类可以看出该实体拥有单个父类型、子类型集合,这里比较特殊的是,这里的父类型和子类型都是自己,也就是自引用.注意:一个没有付类型的实体,该实体就是整个继承类型的最顶端.

5、编写测试代码:

        static void Main(string[] args)
        {
            Example();
        }
        static void Example()
        {
            using (var context = new EF6RecipesContext())
            {
                var first = new Category { Name = "第一级菜单" };
                var second = new Category { Name = "第二级菜单" };
                first.Subcategories.Add(second);
                second = new Category { Name = "第二级菜单" };
                first.Subcategories.Add(second);
                second = new Category { Name = "第二级菜单" };
                first.Subcategories.Add(second);
                var top = new Category { Name = "顶级菜单" };
                top.Subcategories.Add(first);
                context.Categories.Add(top);
                context.SaveChanges();
            }
            using (var context = new EF6RecipesContext())
            {
                var roots = context.Categories.Where(c => c.ParentCategory == null);
                roots.ToList().ForEach(root => Print(root, 0));
            }
            Console.ReadKey();
        }
        static void Print(Category cat, int level)
        {
            StringBuilder sb = new StringBuilder();
            Console.WriteLine("{0}{1}", sb.Append(\' \', level).ToString(), cat.Name);
            cat.Subcategories.ForEach(child => Print(child, level + 1));//递归,直到最后遍历的节点没有子节点集合,则跳出递归循环
        }

 

简单解释下测试代码的逻辑:

(1)、从所有的节点中获取没有父节点的节点,该节点为顶级节点

(2)、然后通过递归将该顶级节点下面的所有的子节点全部遍历出来,每当递归到的节点含有子节点集合,则递归的深度加1.当一个继承链遍历完毕,继续遍历第二个继承链.

 

以上是关于EF基础知识小记六(使用Code First建模自引用关系,常用于系统菜单文件目录等有层级之分的实体)的主要内容,如果未能解决你的问题,请参考以下文章

使用Code First建模自引用关系笔记

EF,Code First - 如何在插入时设置自定义Guid标识值

如何在 EF Core Code First 中自定义迁移生成?

ef code first db first 哪种好

使用自定义 ID 插入数据的 Code-First 实体框架

EF Code First自定义数据库(服务器及数据库名)连接配置