netcore 导航属性

导航属性”是实体框架用得算是比较频繁的概念。

首先,它是类型成员,其次,他是属性,这不是 F 话,而是明确它的本质。
那么,什么场景下会用到导航属性呢?重点就落在“导航”一词上了,
实体 A 需要引用实体 B 时,实体 A 中需要公开一个属性,通过这个属性,能找到关联的实体 B。

又或者,X 实体表示你的博客,P 实体表示你发的一篇博文。你的博客肯定会发很多博文的,所以,X 实体中可能需要一个 List<P> 类型的属性,这个属性包含了你的博客所发表的文章。通过一个实体的属性成员,可以定位到与之有关联的实体,这就是导航的用途了
就像你开着车去穿越神农架一样,迷路了就打开高德导航(前提是不存在定位干扰)。

现在跑江湖的人多,通过各种江湖骗术发家致富。
有了不正常的财富积累后,他们开始大量买车,还买地打造个人车库。
于是,Person 实体代表这些有钱人,CarData 实体表示他们买的各种壕车。

   public class Person
    {
        public int PID { get; set; }
        public string Name { get; set; }
        public int Age { get; set; }
        public List<CarData> Cars { get; set; }
    }

    public class CarData
    {
        public Guid CarID { get; set; }
        public string CarAttribute { get; set; }
        public decimal Cost { get; set; }
    }

每个 Person 都有 Cars 属性,表示各自所购买的车。这个 Cars 就是导航属性,通过这个属性能找到关联的 CarData 实体。

再定义一个数据上下文类

public class MyContext : DbContext
    {
        public DbSet<Person> Persons
        {
            get { return Set<Person>(); }
        }
    }

公开一个 Persons 属性,便于访问,当然了,你觉得我那样写代码太多,你可以直接来这样。

 public DbSet<Person> Persons { get; set; }

两种写法都是可以的。

这一次,我选择用 SQLite 数据库,新的 .net core 框架没有包含访问 SQLIte 的程序集,不过没关系,有 Nuget 啥都能裹进来。怎么安装 nuget 包就不用我教了,你会的。最简单不粗暴的方法就是直接在 nuget 控制台中执行 install-package 命令。

PM> install-package microsoft.entityframeworkcore.sqlite

 回到 MyContext 类,进行一下连接字符串的配置。重写 OnConfiguring 方法,再调用 UseSqlite 扩展方法,就可以设置连接字符串了。

protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
        {
            optionsBuilder.UseSqlite("data source=TMD.db");
        }

还要重写 OnModelCreating 方法,要做两件事情:一是为每个实体设置主键;二是为两个实体建立关系

protected override void OnModelCreating(ModelBuilder modelBuilder)
        {
            // 设置主键
            modelBuilder.Entity<Person>().HasKey(p => p.PID);
            modelBuilder.Entity<CarData>().HasKey(c => c.CarID);
            // 映射实体关系,一对多
            modelBuilder.Entity<Person>().HasMany(p => p.Cars);
        }

在本例中,你懂的,一个人可以有 N 辆车,因此 Person 与 CarData 之间是“一对多”的关,
故而实体 Person 可以 HasMany 个 CarData 对象,其中,Cars 即是导航属性

注意:由于 MyContext 类重写了 OnConfiguring 方法,
所以,在 MyContext 类的构造函数中,无需接收 DbContextOptions<MyContext> 的依赖注入 ,
在 Startup.ConfigureServices 方法中也无需再调用 UseSqlite 方法,
你只需 Add 一下就可以了。

public void ConfigureServices(IServiceCollection services)
        {
            services.AddDbContext<MyContext>();
            services.AddMvc();
        }

在 Main 入口点中,先创建 host 实例。

var host = new WebHostBuilder()
                .UseKestrel()
                .UseContentRoot(Directory.GetCurrentDirectory())
                .UseEnvironment(EnvironmentName.Development)
                .UseUrls("http://localhost:7552")
                .UseStartup<Startup>()
                .Build();

此时,不要急着调用 Run 方法。因为咱们还没创建数据库呢。
当然,你可以用老周上一篇中介绍的方法,在 nuget 控制台中,
用 Add-Migration 命令添加迁移,然后用 Update-Database 命令创建数据库。
不过,本文中,老周将通过代码在运行阶段创建数据库。

using (IServiceScope scope = host.Services.CreateScope())
            {
                MyContext cxt = scope.ServiceProvider.GetRequiredService<MyContext>();
                if (cxt.Database.EnsureCreated())
                {
                    // 插入一些记录
                    Person p1 = new Person
                    {
                        Name = "王老三",
                        Age = 65,
                        Cars = new List<CarData>
                        {
                            new CarData
                            {
                                CarAttribute= "黄色兰博基尼",
                                Cost = 1500020002.00M
                            }
                        }
                    };
                    cxt.Persons.Add(p1);
                    Person p2 = new Person
                    {
                        Name = "朱大日",
                        Age = 72,
                        Cars = new List<CarData>
                        {                           
                           new CarData
                           {
                               CarAttribute = "奥迪A4L",
                               Cost = 401000M
                           }
                        }
                    };
                    cxt.Persons.Add(p2);
                    // 更新到数据库
                    cxt.SaveChanges();
                }
            }

初始化数据库后,可以运行 host 了。 

 host.Run();

添加一个控制器,为了简单,咱们不创建 View 了,就直接返回 JSON 数据好了,就当 Web API 来使用。

[Route("[controller]/[action]")]
    public class TestController : Controller
    {
        readonly MyContext context;
        public TestController(MyContext c)
        {
            context = c;
        }

        [HttpGet]
        public ActionResult ListData()
        {
            return Json(context.Persons);
        }
    }

现在可以运行了,用诸如 Postman 等测试工具,请求 <root url>/test/listdata,结果发现惊人一幕。

[
    {
        "pid": 1,
        "name": "王老三",
        "age": 65,
        "cars": null
    },
    {
        "pid": 2,
        "name": "朱大日",
        "age": 72,
        "cars": null
    }
]

好,不卖关子了。出现这个问题,是因为导航属性的状态在默认情况下不会自动去还原的,不然的话,会增加对象引用,所以默认是不加载的。那么,你会问,那么 CarData 实体的数据记录到底加载了没?加载了的

现在我们有这个需求,要求还原导航属性的状态,那咋办呢?再次回到 ListData 方法,把它改成这样。

[HttpGet]
        public ActionResult ListData()
        {
            var persons = context.Persons.Include(p => p.Cars).ToList();
            return Json(persons);
        }

调用 Include 方法记得引入 Microsoft.EntityFrameworkCore 命名空间,这个不用我多说了。Incluse 扩展方法的意思就是加载导航属性中的内容,它会自动还原状态,知道哪些 CarData 实例与 Person 实例有关。

再次运行,请求一下 <root url>/test/listdata,这下你就放心了,有数据了

原文

人各有命,上天注定,有人天生为王,有人落草为寇。脚下的路,如果不是你自己的选择,那么旅程的终点在哪,也没人知道。你会走到哪,会遇到谁,都不一定。
原文地址:https://www.cnblogs.com/ZkbFighting/p/15590922.html