【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="">
<input type="submit" value="新增" />
</td>
</tr>
</table>
</form>
</div>
<div>
<table border="">
@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 = "",
Money = 50.00M,
Time = new DateTime(, , , , , )
};
Charge c2 = new Charge
{
PhoneNo = "",
Money = 100.00M,
Time = new DateTime(, , , , , )
};
Charge c3 = new Charge
{
PhoneNo = "",
Money = 30.1M,
Time = new DateTime(, , , , , )
};
_dbcontext.Charges.AddRange(c1, c2, c3);
_dbcontext.SaveChanges();
}
} public ActionResult Index()
{
return Json(_dbcontext.Charges);
}
}
运行结果如下。
好了,今天的内容就到这里了,文中示例的源代码可以拼命点 这里 下载。
【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 EF Core 中调用存储过程. 我们知道,EF Core 是不支持直接调用存储过程的,那它又提供了什么样的方式去执行存储过程呢?有如下方法: 1.F ...
- .net core EF Core 视图的应用
由之前的一篇文章<.net core Entity Framework 与 EF Core>我们都已经知道 EF Core 增加了许多特性,并且性能上也有了很大的提升. 但是EF Core ...
- 在vs2015上使用asp.net core+ef core
官方的文档https://docs.asp.net/en/latest/tutorials/first-mvc-app/start-mvc.html 先来看一下实现的效果
- .Net Core EF Core之Sqlite使用及部署
1.添加引用Nuget包 Microsoft.EntityFrameworkCore.Sqlite Microsoft.EntityFrameworkCore.Design Microsoft.Ent ...
- asp.net Core EF core ( Entity Framework 7 ) 数据库更新维护
CreateDatabaseIfNotExists等之前的API已经废弃,现在采用的是微软封装好,简化.高效的API,migrations 因为,旧API,要付出高昂的代价,以及局限性 打开VS20 ...
- asp.net core-16.EF Core Migration
左边的是基于visual studio code 右边的是基于visual studio 如果想要在数据库的AspNetUsers表里添加一列 然后可以发现 在Data下的Migrations文件夹下 ...
随机推荐
- BZOJ4589 Hard Nim FWT 快速幂 博弈
原文链接https://www.cnblogs.com/zhouzhendong/p/BZOJ4589.html 题目传送门 - BZOJ4589 题意 有 $n$ 堆石子,每一堆石子的取值为 $2$ ...
- Codeforces 982E Billiard 扩展欧几里德
原文链接http://www.cnblogs.com/zhouzhendong/p/9055728.html 题目传送门 - Codeforces 928E 题意 一束与坐标轴平行或者成$45^\ci ...
- POJ3321Apple Tree Dfs序 树状数组
出自——博客园-zhouzhendong ~去博客园看该题解~ 题目 POJ3321 Apple Tree 题意概括 有一颗01树,以结点1为树根,一开始所有的结点权值都是1,有两种操作: 1.改变其 ...
- tensorflow基础架构 - 处理结构+创建一个线性回归模型+session+Variable+Placeholder
以下仅为自己的整理记录,绝大部分参考来源:莫烦Python,建议去看原博客 一.处理结构 因为TensorFlow是采用数据流图(data flow graphs)来计算, 所以首先我们得创建一个数据 ...
- 设计模式之单例模式及应用demo
单例模式是创建型模式之一. 单例模式顾名思义是单例的,也就是只有一个实例化对象,这都来源于它的私有化构造函数. 单例模式特点: 1.单例类只能有一个实例. 2.单例类必须自己创建自己的唯一实例. 3. ...
- 爬虫3 requests基础之下载图片用content(二进制内容)
res = requests.get('http://soso3.gtimg.cn/sosopic/0/11129365531347748413/640') # print(res.content) ...
- python 操作mongo
1. 导包: import pymongo 2. 建立连接 client = pymongo.MongoClient("127.0.0.1",27017) 3. 获取数据库 db ...
- win10安装Oracle11g,出现INS-13001环境不满足最低要求问题
今天安装Oracle11g,出现INS-13001环境不满足最低要求问题: 解决方法 在安装时点击setup.exe之后,出现了:[INS-13001]环境不满足最低要求 这时,打开你的解压后的dat ...
- POJ 3258 River Hopscotch (最大最小距离)【二分】
<题目链接> 题目大意:现在有起点和终点两个石块,这两个石块之间有N个石块,现在对这N个石块移除M个石块,使得这些石块之间的最短距离最大,注意,起点和终点这两个石块不能被移除. 解题分析: ...
- Manjaro安装配置笔记
简单介绍: Manjaro和Ubuntu的都使用有段时间了,还是AUR大法用着舒服趁着由KDE桌面更换deepin时系统崩溃,直接重装了系统,版本:Manjaro-deepin-17.1.7-x86_ ...