【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 = ,
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 = ,
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 实例的主键。加载数据时,就是通过这一列来还原两个实体之间的关系的。
【ASP.NET Core】EF Core - “导航属性”的更多相关文章
- 国产化之路-统信UOS + Nginx + Asp.Net MVC + EF Core 3.1 + 达梦DM8实现简单增删改查操作
专题目录 国产化之路-统信UOS操作系统安装 国产化之路-国产操作系统安装.net core 3.1 sdk 国产化之路-安装WEB服务器 国产化之路-安装达梦DM8数据库 国产化之路-统信UOS + ...
- asp.net core+ef core
asp.net core+ef core 官方的文档https://docs.asp.net/en/latest/tutorials/first-mvc-app/start-mvc.html 先来看一 ...
- Asp.net Core + EF Core + Bootstrap搭建的MVC后台通用管理系统模板(跨平台版本)
Asp.net Core + EF Core + Bootstrap搭建的MVC后台通用管理系统模板(跨平台版本) 原创 2016年07月22日 10:33:51 23125 6月随着.NET COR ...
- .net core EF Core 视图的应用
由之前的一篇文章<.net core Entity Framework 与 EF Core>我们都已经知道 EF Core 增加了许多特性,并且性能上也有了很大的提升. 但是EF Core ...
- .net core EF Core 调用存储过程
在这里,我们将尝试去学习一下 .net core EF Core 中调用存储过程. 我们知道,EF Core 是不支持直接调用存储过程的,那它又提供了什么样的方式去执行存储过程呢?有如下方法: 1.F ...
- EntityFramework Core指定更新导航属性了解一下?
前言 本文来自和何镇汐大哥的探讨,很多时候我习惯于和别人交流过后会思考一些问题,无论是天马行空还是浅薄的想法都会记录下来,或许看到此博文的您能给我更多的思考,与人交流总能收获很多东西,出发点不一样则结 ...
- 在vs2015上使用asp.net core+ef core
官方的文档https://docs.asp.net/en/latest/tutorials/first-mvc-app/start-mvc.html 先来看一下实现的效果
- EF的导航属性
在EF中,外键被称为导航属性. 在EF core中,查询的时候默认是只查自身而不会去查询外键表的.如果想要让查询结果包含外键实体,则需要使用include方法来让查询结果包含外键实体.如 db.Stu ...
- MVC3+EF4.1学习系列(五)----- EF查找导航属性的几种方式
文章索引和简介 通过上一篇的学习 我们把demo的各种关系终于搭建里起来 以及处理好了如何映射到数据库等问题 但是 只是搭建好了关系 问题还远没有解决 这篇就来写如何查找导航属性 和查找导航属性的几种 ...
- CodeFirst EF中导航属性的个人理解
>导航属性: 01.个人理解就是Ef中的属性在实体数据表中不存在(先这么认为); 02.就是除了根据表中列映射出的属性 之外根据表与表之间的关系的关联属性.方便操作与之关联的表; 例如: 有 表 ...
随机推荐
- BZOJ1010 [HNOI2008]玩具装箱toy 动态规划 斜率优化
原文链接http://www.cnblogs.com/zhouzhendong/p/8687797.html 题目传送门 - BZOJ1010 题意 一个数列$C$,然后把这个数列划分成若干段. 对于 ...
- 数据库构架设计中的Shared Everthting、Shared Nothing、和Shared Disk
Shared Everthting:一般是针对单个主机,完全透明共享CPU/MEMORY/IO,并行处理能力是最差的,典型的代表SQLServer Shared Disk:各个处理单元使用自己的私有 ...
- css图片根据div宽高比例自适应
1.div布局 <div class="card-img-show"> <div class="upload-img-conss" > ...
- tp5数据库链接
1在config/database.php中配置 1.1直接 return [ // 数据库类型 'type' => 'mysql', // 服务器地址 'hostname' => '12 ...
- 使用sparksql往kafka推送数据
一.相关配置参数 1.同级目录resource文件夹下配置 brokers_list=kafkaxxx02broker01:9092,kafkaxxx02broker02:9092,kafkaxxx0 ...
- 6491: Daydream
题目描述 You are given a string S consisting of lowercase English letters. Another string T is initially ...
- Java内存管理-你真的理解Java中的数据类型吗(十)
勿在流沙筑高台,出来混迟早要还的. 做一个积极的人 编码.改bug.提升自己 我有一个乐园,面向编程,春暖花开! 作为Java程序员,Java 的数据类型这个是一定要知道的! 但是不管是那种数据类型最 ...
- asp.net core下的如何给网站做安全设置
首先,我们来看下stack overflow网站的请求头文件: 可以看到一些我们熟悉或是陌生的HTTP头部文件字段.在这里我们在对HTTP输入流的头部文件中,做一些基本的防护.首先要明确,既然我们是对 ...
- angular笔记_2
<!DOCTYPE html><html lang="en"><head> <meta charset="UTF-8" ...
- 在用UEditor往后台传数据写入数据库时,出现错误:从客户端(NewsContent="<p><img src="http://...")中检测到有潜在危险的 Request.。。。
解决办法: 把传数据的方式换了一下,加上 [ValidateInput(false)]就不报错了. 建议看看这个:http://www.360doc.com/content/10/0521/15/46 ...