zoukankan      html  css  js  c++  java
  • 【ASP.NET Core】EF Core

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

    首先,它是类型成员,其次,他是属性,这不是 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 类,进行一下连接字符串的配置。

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

    重写 OnConfiguring 方法,再调用 UseSqlite 扩展方法,就可以设置连接字符串了。

    还要重写 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
                                },
                                new CarData
                                {
                                    CarAttribute = "景泰蓝 吉利瑞博GE",
                                    Cost = 138_000M
                                }
                            }
                        };
                        cxt.Persons.Add(p1);
                        Person p2 = new Person
                        {
                            Name = "朱大日",
                            Age = 72,
                            Cars = new List<CarData>
                            {
                                new CarData
                                {
                                    CarAttribute = "玛瑙红 别克VELITE 5",
                                    Cost = 289_500M
                                },
                                new CarData
                                {
                                    CarAttribute = "雅韵金 本田INSPIRE",
                                    Cost = 171000M
                                },
                               new CarData
                               {
                                   CarAttribute = "奥迪A4L",
                                   Cost = 401000M
                               }
                            }
                        };
                        cxt.Persons.Add(p2);
                        // 更新到数据库
                        cxt.SaveChanges();
                    }
                }

    IServiceScope 是个有趣的玩意儿,它创建一个基于当前作用域的服务列表,从该对象中获取的服务实例,其生命周期只在当前 scope 中有效。这特别适用于临时实例化服务的情景。比如这里,MyContext 只是暂时实例化,等创建数据库并写入测试数据后,就可以 Dispose 了。

    初始化数据库后,可以运行 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
        }
    ]

    我相信,很多人都遇到了这个问题,所以,本文老周也顺便解释一下这个问题。如你所见,cars 属性是 null,明明是添加了 CarData 对象的,为啥会 null,你是不是开始怀疑人生了?千万不要轻易怀疑人生,那样是很不负责任的。

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

            [HttpGet]
            public ActionResult CarList()
            {
                var cars = context.Set<CarData>().ToList();
                return Json(cars);
            }

    然后,你访问一下 <root url>/test/carlist,看看下面的结果。

    [
        {
            "carID": "36e97ed0-56b1-4d92-bb2d-aeec9f9e1b43",
            "carAttribute": "黄色兰博基尼",
            "cost": 1500020002
        },
        {
            "carID": "0fd6c2a0-d4ef-4838-bc08-43a5cb024eef",
            "carAttribute": "景泰蓝 吉利瑞博GE",
            "cost": 138000
        },
        {
            "carID": "c9eb20c8-931e-4563-b380-cbee926015c8",
            "carAttribute": "玛瑙红 别克VELITE 5",
            "cost": 289500
        },
        {
            "carID": "3d563693-5ae0-4682-bd53-c7fc87e951de",
            "carAttribute": "雅韵金 本田INSPIRE",
            "cost": 171000
        },
        {
            "carID": "2294a556-fd02-49c3-b4b2-559f15413e75",
            "carAttribute": "奥迪A4L",
            "cost": 401000
        }
    ]

    我没骗你吧,有数据的呀。

    现在我们有这个需求,要求还原导航属性的状态,那咋办呢?再次回到 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,这下你就放心了。

    [
        {
            "pid": 1,
            "name": "王老三",
            "age": 65,
            "cars": [
                {
                    "carID": "36e97ed0-56b1-4d92-bb2d-aeec9f9e1b43",
                    "carAttribute": "黄色兰博基尼",
                    "cost": 1500020002
                },
                {
                    "carID": "0fd6c2a0-d4ef-4838-bc08-43a5cb024eef",
                    "carAttribute": "景泰蓝 吉利瑞博GE",
                    "cost": 138000
                }
            ]
        },
        {
            "pid": 2,
            "name": "朱大日",
            "age": 72,
            "cars": [
                {
                    "carID": "c9eb20c8-931e-4563-b380-cbee926015c8",
                    "carAttribute": "玛瑙红 别克VELITE 5",
                    "cost": 289500
                },
                {
                    "carID": "3d563693-5ae0-4682-bd53-c7fc87e951de",
                    "carAttribute": "雅韵金 本田INSPIRE",
                    "cost": 171000
                },
                {
                    "carID": "2294a556-fd02-49c3-b4b2-559f15413e75",
                    "carAttribute": "奥迪A4L",
                    "cost": 401000
                }
            ]
        }
    ]

    怎么样,满意了吧。

    接下来,咱们再看看反过来的情况,咋返过来呢?我们假设以汽车为主,现在是每辆车都对应着一位车主信息,每个人只与一辆车关联,所以,车与人之间是“一对一”的关系。

    先定义实体类,结构与前面的差不多。

        public class Person
        {
            public int PID { get; set; }
            public string Name { get; set; }
        }
    
        public class CarData
        {
            public int CarID { get; set; }
            public string CarAttribute { get; set; }
            public decimal Cost { get; set; }
            public Person Owner { get; set; }
        }

    这一次,如你所见,导航属性是 CarData 类的 Owner 属性,即该车的车主信息,引用一个 Person 实例。

    下面定义 DbContext。

        public class MyContext : DbContext
        {
            public DbSet<Person> Persons { get; set; }
            public DbSet<CarData> Cars { get; set; }
        }

    重写 OnModelCreating 方法。

            protected override void OnModelCreating(ModelBuilder modelBuilder)
            {
                modelBuilder.Entity<Person>().HasKey(p => p.PID);
                modelBuilder.Entity<CarData>().HasKey(c => c.CarID);
    
                modelBuilder.Entity<CarData>().HasOne(c => c.Owner);
            }

    除了每两个实体设置主键外,请注意看最后一行,这一次,CarData 实体只对应着一个 Person 实例,所以是 HasOne,导航属性是 Owner。

    重写 OnConfiguring 方法,配置连接字符串,这一次就用 SQL Server LocalDB,轻量级的。

            protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
            {
                optionsBuilder.UseSqlServer("server=(localdb)\MSSQLLocalDB;database=DemoDt");
            }

    Main 方法中的做法与前面一样,初始化 WebHost 后,先创建数据库,填一下垃圾数据,然后再启动 host。

                var host = new WebHostBuilder()
                    .UseKestrel()
                    .UseEnvironment("debug")
                    .UseUrls("http://localhost:19230")
                    .UseStartup<Startup>()
                    .UseContentRoot(Directory.GetCurrentDirectory())
                    .Build();
    
                using(IServiceScope scope = host.Services.CreateScope())
                {
                    MyContext cxt = scope.ServiceProvider.GetRequiredService<MyContext>();
                    if (cxt.Database.EnsureCreated())
                    {
                        Person p1 = new Person
                        {
                            Name = "王阿基"
                        };
                        Person p2 = new Person
                        {
                            Name = "刘二打"
                        };
                        Person p3 = new Person
                        {
                            Name = "李无牙"
                        };
                        CarData c1 = new CarData
                        {
                            CarAttribute = "三无产品 A款",
                            Cost = 150000M,
                            Owner = p1
                        };
                        CarData c2 = new CarData
                        {
                            CarAttribute = "三无产品 F款",
                            Cost = 67500M,
                            Owner = p2
                        };
                        CarData c3 = new CarData
                        {
                            CarAttribute = "三无产品 2018款",
                            Cost = 76000M,
                            Owner = p3
                        };
                        cxt.Persons.Add(p1);
                        cxt.Persons.Add(p2);
                        cxt.Persons.Add(p3);
                        cxt.Cars.Add(c1);
                        cxt.Cars.Add(c2);
                        cxt.Cars.Add(c3);
                        cxt.SaveChanges();
                    }
                }
    
                host.Run();

    接下来,创建一个控制器。

        public class SampleController : Controller
        {
        }  

    通过依赖注入,获得 MyContext 实例。

            readonly MyContext context;
            public SampleController(MyContext cxt)
            {
                context = cxt;
            }

    定义一个获取数据列表的 Action。

            [HttpGet]
            public ActionResult List()
            {
                var cars = context.Cars.Include(c => c.Owner);
                return Json(cars.ToList());
            }

    Include 方法的调用与前面一样,只是注意这次是以 CarData 实体为主,顺便加载导航属性 Owner 的内容。

    Postman 测试结果。

    [
        {
            "carID": 1,
            "carAttribute": "三无产品 A款",
            "cost": 150000,
            "owner": {
                "pid": 1,
                "name": "王阿基"
            }
        },
        {
            "carID": 2,
            "carAttribute": "三无产品 F款",
            "cost": 67500,
            "owner": {
                "pid": 2,
                "name": "刘二打"
            }
        },
        {
            "carID": 3,
            "carAttribute": "三无产品 2018款",
            "cost": 76000,
            "owner": {
                "pid": 3,
                "name": "李无牙"
            }
        }
    ]

    同样,这也达到预期的效果了。

    我们查看一下所生成的数据库,你会发现,Cars 表中生成了一列,名为 OwnerPID,引用的是关联的 Person 实例的主键。加载数据时,就是通过这一列来还原两个实体之间的关系的。

  • 相关阅读:
    牛客小白赛23
    三分查找模板和例题
    链式前向星存图模板
    树形dp求解树的重心和例题
    P3915-树的分解-(dfs)
    P2119 魔法阵-(桶排序+前后缀和)
    小阳的贝壳-(差分+线段树+gcd)
    NOJ1370: [蓝桥杯2018初赛]测试次数-(dp)
    begin.lydsy 入门OJ题库:3611-3613:神炎皇、降雷皇、幻魔皇
    1797: [Noi2010]海拔
  • 原文地址:https://www.cnblogs.com/twodog/p/12135709.html
Copyright © 2011-2022 走看看