【ASP.NET Core】EF Core 模型与数据库的创建
大家好,欢迎收看由土星卫视直播的大型综艺节目——老周吹逼逼。
今天咱们吹一下 EF Core 有关的话题。先说说模型和数据库是怎么建起来的,说装逼一点,就是我们常说的 “code first”。就是你先创建了数据模型,然后再根据模型来创建数据库。这种做法的一个好处是让面向对象的逻辑更好地表现出来。以前,咱们通常是先创建数据库的。
像 EF 这么嗨的东西,ASP.NET Core 中自然也是少不了的,即 EF Core。
好了,以上就是理论部分,比较乏味,是吧。那好,下面咱们干点正事。
构建模型
建立模型很简单,就是定义一个类(为了好理解,老周暂且不说关系模型)。来,看看,就像下面这个类,假设它表示的是某工厂生产的山寨产品信息。
public class Product{public int ProdID { get; set; }public string ProdName { get; set; }public DateTime FinishDate { get; set; }public double Weight { get; set; }}
有人会问:完事了?嗯,完事了,这就是一个模型了,但还是不能创建数据库的。
继承 DBContext
虽然咱们有了山寨产品的模型类,但你还得实现一个数据上下文。通常呢,数据上下文是映射到某个数据库的。上下文的定义是从 DbContext 类派生出一个类,然后,把它与模型类关联起来。
public class MyDBContext : DbContext{public DbSet<Product> Products { get; set; }}
DbSet 会映射到数据库中的一个表。
为了实现依赖注入,以及能够在 Startup 类中进行配置,你可以在自己实现的 DBContext 子类中公开构造函数,并且接收一个 DbContextOptions<TContext> 类型的参数注入,TContext 就是咱们自己定义的从 DBContext 类派生的类。
public class MyDBContext : DbContext{public MyDBContext(DbContextOptions<MyDBContext> options): base(options){// 暂无其他代码 }public DbSet<Product> Products { get; set; }}
注册服务
有了模型和数据上下文,接下来咱们要在 Startup 类中注册一下相关的服务,并且配置一下像连接字符串之类的参数。
public void ConfigureServices(IServiceCollection services){services.AddMvc();services.AddDbContext<MyDBContext>(option =>{option.UseSqlServer("server=(localdb)\\MSSQLLocalDB;database=DemoDB");}); }
创建数据库“迁移”
创建迁移的好处是灵活,如果你的模型后面修改了(比如添加了一个属性),那么你可以在原有的迁移基础上再添加新的迁移,这些数据迁移会不断叠加,所以,你不需要删除过去的迁移版本,因为后面添加的不会重复,只会包含更新数据模型的代码。
创建迁移有多种方式:1、dotnet 命令行;2、VS 中的 nuget 控制台;3、直接用代码。
dotnet cli 即使用 dotnet 命令行工具来对数据模型进行迁移,其命令为 dotnet ef <...>。这里老周演示的是用 VS 中的 nuget 控制台来处理,dotnet cli 的方法类似,你可以输入 dotnet ef --help 来查看帮助。
在 VS 中,打开 【工具】-【NuGet 包管理器】-【程序包管理器】菜单项,随后就能打开控制台窗口。你可以输入以下命令查看帮助文档。
get-help about_EntityFrameworkCore
你要是觉得名字太长了,可以这样输入
get-help about_*core
星号是通配符,它会查找所有以 about_ 开头,以 Core 结尾的说明文档。
好,下面咱们为前面已定义好的 MyDBContext 生成数据迁移代码,使用的命令是 Add-Migration。用法如下。
Add-Migration [-Name] <String> [-OutputDir <String>] [-Context <String>] [-Project <String>] [-StartupProject <String>]
其实后面还有个参数列表的,但用不上,就不列出来了。注意,只有位于第一个位置的 -Name 参数名可以省略,后面的都不能省略参数名。即对于迁移点的命名,你可以输入
Add-Migration -Name "demo001"
也可以输入
Add-Migration "demo001"
-OutputDir 指的是生成的代码放在哪个目录下面,默认叫 Migrations。注意它是相对于项目目录的路径。-Context 指定的是你自己定义的 DBContext 的子类的名称,包含命名空间名称,如果是当前项目,可以不写。
-Project 和 -StartupProject 一般不用刻意指定,如果解决方案中有多个项目,可以指定一下,生成的迁移属于哪个项目。-StartupProject 可以不指定,让它选择与解决方案配置一致的启动项目。
好,下面咱们为刚刚定义的 MyDBContext 生成数据迁移。输入
add-migration "demo001" -Context "MyDBContext" -OutputDir "CustMigrations"
执行后就呵呵了,出现一个警告和一个异常。
警告信息只是 SDK 与运行时版本没统一而已,这个可以不鸟它,不影响命令执行。最大的问题是发生异常,这会导致命令不能执行。发生异常是因为我们上面定义的那个 Product 类,没有声明主键。
于是,我们就让 ProdID 作为主键,方法有两种。第一,通过“数据批注”,就像这样。
public class Product{ [Key]public int ProdID { get; set; }public string ProdName { get; set; }public DateTime FinishDate { get; set; }public double Weight { get; set; }}
如果你认为用特性来批注很难看,那就用第二种方法,在继承 DBContext 的类中重写 OnModelCreating 方法。
public class MyDBContext : DbContext{……protected override void OnModelCreating(ModelBuilder modelBuilder){modelBuilder.Entity<Product>().HasKey(p => p.ProdID); }}
现在再执行一次 Add-Migration 命令,就顺利创建数据迁移了。
假如现在我觉得模型要修改,新增一个 Remark 属性。
public class Product{public int ProdID { get; set; }public string ProdName { get; set; }public DateTime FinishDate { get; set; }public double Weight { get; set; }public string Remark { get; set; } }
此时,你不用删除前面创建的迁移,你只需要再加一个迁移即可,它会自动累积的。
add-migration "demo002" -Context "MyDBContext" -OutputDir "CustMigrations"
你能看到,demo002 迁移生成的代码,仅仅是添加了 Remark 列。
public partial class demo002 : Migration{protected override void Up(MigrationBuilder migrationBuilder){migrationBuilder.AddColumn<string>(name: "Remark",table: "Products",nullable: true); }protected override void Down(MigrationBuilder migrationBuilder){migrationBuilder.DropColumn(name: "Remark",table: "Products"); }}
所以,看得出来,它不会重复生成表结构的。Up 方法表示的是当前的状态,Down 方法是在执行 Remove-Migration 时进行回退,回退时删除 Remark 列。
创建数据库
有了上面的步聚,现在可以创建数据库了。这里老周以 SQLLocalDB 为例,在 CMD 中启动默认的 MSSQLLocalDB 实例。
sqllocaldb start mssqllocaldb
回到 VS 中,执行 Update-Database 命令。
update-database
无参数的情况下,执行所有迁移中的内容,为了使创建的数据库结构完整,应该执行所有迁移。
SQLLocalDB 创建的数据库默认存放在你的用户目录下,即 C:\\Users\\Your name\\ 下面,路径变量是 %userprofile%。
当你想删除数据库时,可以输入以下命令。
drop-database -Context "MyDBContext"
这时候,它会问你,真的要删库跑路吗?
此时你心意已决,删库跑路,输入 Y 或 A,确认。
测试数据库
好了,现在,模型也建好了,数据库也有了,可以来测一下了。
先创建个控制器。
public class DemoController : Controller{MyDBContext _dbcontext;public DemoController(MyDBContext context){_dbcontext = context;}[HttpGet]public ActionResult Products(){return View(_dbcontext.Products.ToList());}[HttpPost]public ActionResult Products(Product p){if (ModelState.IsValid){_dbcontext.Products.Add(p);_dbcontext.SaveChanges();}return View(_dbcontext.Products.ToList());}}
db context 可以在构造函数能过依赖注入来获取,因为前面我们已经在 Startup.ConfigureServices 方法中注册了相关服务。添加新记录时直接把方法参数接收到的 Product 实例 Add 到 DbSet 中即可,但要记得调用 SaveChanges 方法,因为调用方法后数据才会真正写入数据库。
控制器中包含了两个 Products 的 action 方法,使用以下路由规则,可以匹配出两个方法。
app.UseMvc(route =>{route.MapRoute("test", "{controller=Demo}/{action=Products}");});
解决方法就是,无参数的 Products 方法以 GET 方式访问,而带参数的 Products 方法以 POST 方式访问。
创建一个与 Products 方法同名的视图。在视图中用 @model 指令定义 Model 的类型为 List<Product>,因为上面控制器中,调用 View 方法时,传递给视图的是 List<Product> 类型的 Model。
视图代码如下。
@using Web7362 @model List<Product> @addTagHelper *,Microsoft.AspNetCore.Mvc.TagHelpers <html> <body><div><form method="post"><table><tr><td>产品名称:</td><td><input type="text" name="ProdName" /></td></tr><tr><td>完成日期:</td><td><input name="FinishDate" type="date"/></td></tr><tr><td>产品重量:</td><td><input name="Weight"/></td></tr><tr><td>产品备注:</td><td><input name="Remark" type="text" /></td></tr><tr><td colspan="2"><input type="submit" value="新增" /></td></tr></table></form></div><div><table border="1">@foreach(var p in Model){<tr><td>@p.ProdID</td><td>@p.ProdName</td><td>@p.FinishDate</td><td>@p.Weight</td><td>@p.Remark</td></tr>}</table></div> </body> </html>
第一个 div 中的 form 用于提交新的山寨产品记录,第二个 div 用来显示产品列表。
当提交时,如何把 form 中输入的内容传递给 Product 新对象,你可能会想到使用 asp-for 标签帮助器。但此处不能使用 asp-for 帮助器,因为 Model 的类型是 List<Product> ,不是 Product 类型。
那咋办呢,可以利用 input 元素的 name 值,将 name 值设置为与 Product 类的各属性名称相同的值即可。
<input type="text" name="ProdName" /><input name="FinishDate" type="date"/><input name="Weight"/><input name="Remark" type="text" />
这样设置后,在提交时 Model Binder 就可以自动识别并填充 Product 实例的各个属性了。
你也会问了,为啥没有为 ProdID 属性弄个 input 元素?因为这个属性是主键,其值由数据库生成,不必手动输入。
来来来,看看效果。
IDesignTimeDbContextFactory<out TContext> 接口
这个接口有两种情况下,你可以考虑使用。
1、默认项目模板生成的 Main 方法被你修改了。准确地说,是你删除了 CreateWebHostBuilder 方法。默认生成的 Main 是这样的。
public static void Main(string[] args){CreateWebHostBuilder(args).Build().Run();}public static IWebHostBuilder CreateWebHostBuilder(string[] args) =>WebHost.CreateDefaultBuilder(args).UseStartup<Startup>();
然后,你嫌它生成的代码不好看,也觉得日志太多影响性能,所以改为这样。
public static void Main(string[] args){var host = new WebHostBuilder().UseContentRoot(Directory.GetCurrentDirectory()).UseStartup<Startup>().UseKestrel().UseUrls("http://localhost:7676").UseEnvironment(EnvironmentName.Development).UseSetting(WebHostDefaults.ApplicationKey, "大飞侠充值系统").Build();host.Run();}
这样一来,你想执行 Add-Migration 命令,就会收到这条错误。
2、设计时需要。有时候,你用来开发测试的数据库服务器和正式投入使用的不是同一个服务器。这时候,你可以实现 IDesignTimeDbContextFactory<out TContext> 接口,创建用于测试的数据上下文(尤其是连接字符串)。
下面用另一个示例来演示一下。
先创建一个模型类。
public class Charge{public int ID { get; set; }public DateTime Time { get; set; }public decimal Money { get; set; }public string PhoneNo { get; set; }}
然后是实现数据上下文。
public class DemoDBContext : DbContext{public DemoDBContext(DbContextOptions<DemoDBContext> options): base(options) { }public DbSet<Charge> Charges { get; set; }protected override void OnModelCreating(ModelBuilder modelBuilder){modelBuilder.Entity<Charge>().HasKey(o => o.ID);}}
由于默认的 Main 函数被修改了,执行 Add-Migration 命令,会发生错误。
其实,错误信息中已经告诉你解决方法了,就是实现 IDesignTimeDbContextFactory<out TContext> 接口。所以,就实现一下呗。
public class CustDesigntimeContext : IDesignTimeDbContextFactory<DemoDBContext>{public DemoDBContext CreateDbContext(string[] args){var optionsBuilder = new DbContextOptionsBuilder<DemoDBContext>();// 设置连接字符串optionsBuilder.UseSqlServer("server=(localdb)\\mssqllocaldb;database=test_db");// 创建上下文实例return new DemoDBContext(optionsBuilder.Options);}}
现在,再执行 Add-Migration 命令就正常了。
add-migration "check01" -outputdir "MgChecks" -context "DemoDBContext"
然后可以创建数据库。
update-database
接下来用 Web API 来测一下。先在 Startup.ConfigureServices 方法中注册一下相关服务。
public void ConfigureServices(IServiceCollection services){services.AddMvc();services.AddDbContext<DemoDBContext>(opt =>{opt.UseSqlServer("server=(localdb)\\mssqllocaldb;database=test_db");});}
实现 IDesignTimeDbContextFactory 接口只用于设计阶段,不用于应用程序运行阶段,所以,相关的配置还是要做的。
定义控制器。
[Route("charger/[action]")]public class ChargerController : Controller{private readonly DemoDBContext _dbcontext;public ChargerController(DemoDBContext cxt){_dbcontext = cxt;// 初始化一些数据if (!_dbcontext.Charges.Any()){Charge c1 = new Charge{PhoneNo = "13325236411",Money = 50.00M,Time = new DateTime(2018, 10, 9, 20, 16, 0)};Charge c2 = new Charge{PhoneNo = "15900254200",Money = 100.00M,Time = new DateTime(2018, 6, 22, 19, 0, 0)};Charge c3 = new Charge{PhoneNo = "13500001122",Money = 30.1M,Time = new DateTime(2018, 10, 13, 15, 20, 10)};_dbcontext.Charges.AddRange(c1, c2, c3);_dbcontext.SaveChanges();}}public ActionResult Index(){return Json(_dbcontext.Charges);}}
运行结果如下。
好了,今天的内容就到这里了,文中示例的源代码可以拼命点 这里 下载。
【ASP.NET Core】EF Core 模型与数据库的创建相关推荐
- asp.net mysql 创建变_[ASP.net教程]EF Core使用CodeFirst在MySql中创建新数据库以及已有的Mysql数据库如何使用DB First生成域模型...
[ASP.net教程]EF Core使用CodeFirst在MySql中创建新数据库以及已有的Mysql数据库如何使用DB First生成域模型 0 2018-06-30 07:00:08 官方教程: ...
- efcore调用函数_.net core EF Core调用存储过程的方式
前言 在这里,我们将尝试去学习一下 .net core EF Core 中调用存储过程. 我们知道,EF Core 是不支持直接调用存储过程的,那它又提供了什么样的方式去执行存储过程呢?有如下方法: ...
- .net core EF Core 调用存储过程
在这里,我们将尝试去学习一下 .net core EF Core 中调用存储过程. 我们知道,EF Core 是不支持直接调用存储过程的,那它又提供了什么样的方式去执行存储过程呢?有如下方法: 1.F ...
- 国产化之路-统信UOS /Nginx /Asp.Net Core+ EF Core 3.1/达梦DM8实现简单增删改查操作
引言 经过前期的准备工作,.net core 3.1的运行环境和WEB服务器已经搭建完毕,这里需要注意一下,达梦DM8数据库对于Entity Framework Core 3.1 的驱动在NuGet官 ...
- javaweb简单的登录增删改查系统_国产化之路统信UOS /Nginx /Asp.Net Core+ EF Core 3.1/达梦DM8实现简单增删改查操作...
引言 经过前期的准备工作,.net core 3.1的运行环境和WEB服务器已经搭建完毕,这里需要注意一下,达梦DM8数据库对于Entity Framework Core 3.1 的驱动在NuGet官 ...
- Asp.net Core + EF Core + Bootstrap搭建的MVC后台通用管理系统模板(跨平台版本)
6月随着.NET CORE PREVIEW2的发布,JUCHEAP 的CORE版本也由之前的JuCheap Core 1.0升级到了JuCheap Core 2.0,并且已经在将core版本应用到了生 ...
- efcore 实体配置_.NET 云原生架构师训练营(模块二 基础巩固 EF Core 基础与配置)--学习笔记...
2.4.3 EF Core -- 基础与配置连接字符串 异步编程 日志 DbContext池 类和配置表 属性和列配置 并发token 索引 连接字符串 在 http://ASP.NET Core 配 ...
- EF Core的学习之路01
本文章是我听B站杨中科的所做的笔记 杨中科B站视频链接:.NET 6教程,.Net Core 2022视频教程,杨中科主讲_哔哩哔哩_bilibili 什么时ORM 1.说明:本课程需要你有数据库.S ...
- efcore 批量_【EF Core】Entity Framework Core 批处理语句
在Entity Framework Core (EF Core)有许多新的功能,最令人期待的功能之一就是批处理语句.那么批处理语句是什么呢?批处理语句意味着它不会为每个插入/更新/删除语句发送单独的请 ...
最新文章
- 『中级篇』k8s基础网络Cluster Network(66)
- 螺丝孔槽中的螺丝拧花了的物理原理分析
- IDEA中使用Maven构建SSM项目
- php对mysql基础操作_php+mysql的基础操作
- 现代软件工程讲义 2 开发技术 - 效能分析
- Riverbed调查:惊人发现95%的中国受访者高度关注应用性能
- linux---多线程---信号量--不懂
- 纯粹经济学 —— 基本概念
- 自己实现LinkedList类
- Struts2类型转换--浪曦视频第三讲
- 求三维空间中的三角形外接圆圆心坐标的算法
- python语言程序设计基础笔记(三)计算机思维
- 如何用css绘制一个三角形
- html个人所得税计算器,个税计算器2021 - 个人所得税计算器2021 -
- 北极星指标——定义与制定标准
- 《计算机科学导论》第四章 数据运算
- 小程序的支付以及取消之后再次支付
- Docker 常用命令收录 -- 持续更新
- 小程序获取发票详情getinvoiceinfo一直报72031
- 走进VR游戏开发的世界
热门文章
- python怎么安装各种模块_Python2.7安装和常用模块安装
- idea 查看jsp是否被引用_全网最全的IDEA热部署方案,看完再也不用加班了!
- linux kernel and user space通信机制,Linux内核与用户空间通信机制研究.pdf
- 华为mate40pro更新鸿蒙时间,确认入网!鸿蒙系统将首发mate40Pro4G版,华为旧旗舰也迎来升级...
- 判断表格中的一列是否有重复项
- mysql len s.number,mysql内置函数
- 智能车竞赛技术报告 | 智能车视觉 - 山东大学(威海) - 山魂五队
- 2021年春季学期-信号与系统-第九次作业参考答案
- 220V黄金光的LED灯带测试与结构
- js 判断变量是否有值返回bool_基础 |判断 JS 中的变量类型竟然可以如此简单