记录一下学习EFCore中的基础知识
安装dotnet-ef工具
dotnet-ef是对.Net命令行工具dotnet 的扩展,
例如创建并应用从旧模型到新模型的迁移,以及从现有数据库模型生成代码
命令如下
- 检查是否安装dotnet-ef为全局工具
dotnet tool list --global
- 卸载现有版本
dotnet tool uninstall --global dotnet ef
- 安装最新版本
dotnet tool install --global dotnet-ef
- 安装指定版本
dotnet tool install --global dotnet-ef --version 5.0.0
- 安装SqlServer的 NuGet包
dotnet add package Microsoft.EntityFrameworkCore.SqlServer
同样添加 指定版本的包 需要末尾添加 --version 5.0.0
- 安装设计包(用于迁移)
dotnet add package Microsoft.EntityFrameworkCore.Design
CodeFirst模式定义EF Core 模型
EFCore使用约定、注解特性和Fluent API语句的结合,在运行时构建实体模型。
实体类表示表的结构,类的实例表示表中的一行
EF Core 约定
我们编写的代码都需要遵循以下约定
- 表的名称与 DBContext类(例如Products类)中的DbSet属性名匹配
- 数据库列的名称与类中的属性名匹配,例如ProductID
- 假定.Net类型为string 是数据库的 nvarchar类型
- 对于名为ID的属性,如果类名为Product,就可以将此属性重命名为ProductID,那么这个属性是主键
EF Core 注解特性
约定通常不足以将类完全映射到数据库
可以向模型添加更多只能特性的简单方法就是应用注解特性
例如数据库中的名称中的最大长度为 40个字符,并不能为空
ProductName NVARCHAR(40) NOT NULL
Description "NTEXT"
在类中,可以应用特性来指定名称的长度和不能为空
[Required]
[StringLength(40)]
public string ProductName {get; set;}
.NET类型和数据库类型之间没有明显的映射时,可以使用特性加上映射关系
[Column(TypeName = "ntext")]
public string Description { get; set;}
官方文档
EF Core Fluent API
使用Fluent API可以替代注解特性,也可以用来作为特性的补充
使用Fluent API具有绝对的优先级
将注解特性在数据库上下文类的onModelCreating方法中替换为等效的FluentAPI语句
比如:
[Required]
[StringLength(40)]
public string ProductName {get; set;}
可以替换为
modelBuilder.Entity<Procuct>().Property(procucts => procucts.ProductName).IsRequired().HasMaxLength(40);
数据播种
可以使用Fluent API提供初始数据以填充数据库
例如如果想要确保新数据库在Product表中至少有一行,调用HasData方法
modelBuilder.Entity<Category>()
.HasData(new Procuct{ProcuctID = 1,ProcuctName = "Bob",Cost = 8.99M })
官网链接
创建实体类(产品和产品类别)
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;/// <summary>
/// 类别
/// </summary>
public class Category
{[Key] //主键public int categoryID{ get ; set ; }public string categoryName{ get ; set ; }[Column(TypeName ="ntext")] //定义类型为 NTEXTpublic decimal? Description{ get ; set ; }/// <summary>/// 定义导航属性,一对多定义 virtual定义可以使用延迟加载 允许继承覆盖属性并提供额外的特性/// </summary>/// <value></value>public virtual ICollection<Procuct> Procucts {get;set;}public Category(){//若要使开发人员能够将产品添加到类别,我们必须将导航属性初始化为空集合this.Procucts = new HashSet<Procuct>();}
}/// <summary>
/// 产品
/// </summary>
public class Procuct
{[Key] //这里可以不用声明 因为类目加 ID默认就是主键public int ProcuctID { get;set;}[Required][StringLength(40)]public string ProcuctName {get; set;}[Column("UnitPrice",TypeName ="money")] //将属性命名为 UnitPrice 类型为 moneypublic decimal? Cost {get; set;}[Column("UnitsInStock")]public short? Stock {get; set;}public bool Discoutinued {get; set;}public int categoryID {get; set;}/// <summary>/// 允许覆盖属性 提供额外的特性/// </summary>/// <value></value>public virtual Category Category {get; set;}
}
创建DBContext上下文类
上下文类必须继承 DbContext类,并且在类中声明实体的DbSet属性
using System;
using Microsoft.EntityFrameworkCore;public class MyDBContext :DbContext
{public DbSet<Procuct> procucts {get;set;}public DbSet<Category> categories {get;set;}protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder){//server=LAPTOP-89N28DCH;database = 数据库名称;uid=;pwd=optionsBuilder.UseSqlServer("Server=jlf;Database=CodeFirstTest;Trusted_Connection=True;"); //Window身份验证}protected override void OnModelCreating(ModelBuilder modelBuilder){//这里写 Fluent API语句}
}
迁移
迁移常用命令
VSCode迁移命令 | VS 迁移命令 | 说明 |
---|---|---|
dotnet ef migrations add InitialCreate | Add-Migration InitialCreate | 添加迁移信息 ,在项目中创建一个名为“Migrations”的目录,并生成一些文件 |
dotnet ef database update | Update-Database | 将最近添加的迁移执行到数据库 |
dotnet ef migrations remove | Remove-Migration | 将最近的的迁移删除(应避免将已更新数据库的迁移删除) |
dotnet ef dbcontext info | 获取DBContext上下文信息 | |
dotnet ef migrations list | Get-Migration | 列出所有迁移 |
使用迁移
dotnet ef migrations add InitialCreate
在终端执行以上命令生成迁移后会生成以下几个迁移信息文件
dotnet ef database update
这时执行update命令即可将迁移执行到数据库
后续在开发过程中必然会更改实体模型,按照以上步骤先 add 后 update更新数据库
使用DBFirst方式构建模型(反向工程)
这时需要先创建数据库表!
Category产品类别 & Products 产品
然后看下边的语句
dotnet ef dbcontext scaffold
"Server=jlf;Database=Test;Trusted_Connection=True;" //数据库连接字符串,在构建语句直接声明,系统会警告不安全,应把连接字符串放到web.config里
Microsoft.EntityFrameworkCore.SqlServer //数据库提供者
--table Categories --table Products //需要生成那些表
--output-dir AutoGenModels //生成的文件夹
--namespace WorkingWithEFCore.AutoGenModels //命名空间
--data-annotations //使用数据注解和FluentAPI
--context MyContext //上下文别名
这时可以看到,AutoGenModels文件夹中生成了三个文件,包括上下文
需要注意:
- EFCore使用InverseProperty属性来表示外键
- EFCore中INDEX特性来指明那些字段拥有索引
- dotnet-ef目前不能使用可空引用类型
- 类是使用 partial声明的,这样就可以通过创建partial类来添加额外的代码
查询EFCore模型
现在有了使用SqlServer数据库映射来的模型,可以使用一些简单的linq查询来获取数据了
简单查询实体
using Microsoft.EntityFrameworkCore;
using System.Linq;using(var db = new MyContext()){IQueryable<Category> cats = db.Categories; foreach (Category item in cats){System.Console.WriteLine(item.CategoryName);}
}
使用using对上下文类进行封装
查询导航属性
using(var db = new MyContext()){IQueryable<Category> cats = db.Categories.Include(c=>c.Products); //使用 Include 访问Categories类中的导航属性Productsforeach (Category item in cats){System.Console.WriteLine($"{item.CategoryName} 类别有 {item.Products.Count()} 个产品");}
}
获取实体时 只是 db.Categories获取不到实体中的导航属性,也就是外键关系的表的数据,需要使用 Include 来获取
过滤导航属性中的数据
IQueryable cats = db.Categories.Include(c => c.Products.Where(p => p.UnitsInStock > 100)); //过滤产品中的库存数据 >100
排序和过滤
using(var db = new MyContext()){decimal price = 100.23M;//过滤UnitPrice大于100的数据,并根据UnitPrice倒序排序IQueryable<Product> products = db.Products.Where(p => p.UnitPrice > price).OrderByDescending(p => p.UnitPrice);foreach (var item in products){System.Console.WriteLine($"{item.ProductId},{item.ProductName},{item.UnitPrice}");}
}
获取生成的SQL(EFCore 5.0)
在上边方法中加入 以下代码输出生成的SQL
System.Console.WriteLine(products.ToQueryString());
控制台输入的就是查询的sql
记录EFCore操作
- 定义两个类,一个实现 ILoggerProvider 接口,一个实现ILogger接口
- 各自实现接口的方法
- ConsoleLoggerProvider类会返回ConsoleLogger实例,因此没有任何非托管资源,Dispose不需要实现
- ConsoleLogger 类的log方法将日志写入控制台
using Microsoft.Extensions.Logging;public class ConsoleLoggerProvider : ILoggerProvider
{//创建public ILogger CreateLogger(string categoryName){return new ConsoleLogger();}public void Dispose(){//throw new NotImplementedException();}
}
public class ConsoleLogger : ILogger
{//开启逻辑操作的作用域public IDisposable BeginScope<TState>(TState state){return null;}//检查是否启用了给定的 logLevel。 (那些日志信息需要跟踪)本方法将 Trace Information None 排除public bool IsEnabled(LogLevel logLevel){switch(logLevel){case LogLevel.Trace: //日志的详细信息 ? case LogLevel.Information: //跟踪应用程序的常规流的日志case LogLevel.None:return false;case LogLevel.Debug:case LogLevel.Warning: //case LogLevel.Error: //错误case LogLevel.Critical: // 描述不可恢复的应用程序或系统崩溃或灾难性事件的日志 需要立即关注的故障return true;default:return true;}}///// <summary>/// 写入日志 这里暂时控制台输出 应该写入文件/// </summary>/// <param name="logLevel">日志级别 LogLevel 枚举中的值</param>/// <param name="eventId">事件ID 比如LINQ转SQL查询的事件ID是 20100</param>/// <param name="state">要写入的条目</param>/// <param name="exception">与此条目相关的异常</param>/// <param name="formatter"></param>/// <typeparam name="TState"></typeparam>public void Log<TState>(LogLevel logLevel, EventId eventId, TState state, Exception exception, Func<TState, Exception, string> formatter){//if(eventId.Id = 20100) 可以指定事件去处理日志,20100是linq转sqlSystem.Console.WriteLine($"级别:{logLevel} 事件ID:{eventId.Id}");if(state != null){System.Console.WriteLine($"状态:{state}");}if(exception != null){System.Console.WriteLine($"异常:{state}");}}
}
然后在数据库上下文的using快中添加语句获取日志工厂,并注册自定义控制台日志记录器
using Microsoft.Extensions.Logging;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.Extensions.DependencyInjection;using(var db = new MyContext()){var loggerFactory = db.GetService<ILoggerFactory>(); //创建日志工厂loggerFactory.AddProvider(new ConsoleLoggerProvider()); //注册自定义日志方法///下边 写排序或者过滤之类的LINQ方法
}
使用查询标记进行日志批注
IQueryable<Product> products = db.Products.TagWith("日志记录")
.Where(p => p.UnitPrice > price).OrderByDescending(p => p.UnitPrice)
EF Core2.2引入了查询标记特性,以允许向日志中添加SQL注释
模式匹配 LIKE
using (var db = new MyContext()){// var loggerFactory = db.GetService<ILoggerFactory>();// loggerFactory.AddProvider(new ConsoleLoggerProvider()); //注册日志System.Console.WriteLine("输入产品名称:");string input = Console.ReadLine();IQueryable<Product> products = db.Products.Where(p => EF.Functions.Like(p.ProductName,$"%{input}%"));foreach (var item in products){System.Console.WriteLine($"id:{item.ProductId},名称:{item.ProductName}");}
}
提示用户输入产品名称,然后使用 EF.Functions.Like 方法搜索 p.ProductName 属性的任何位置
定义全局过滤器
modelBuilder.Entity<Product>().HasQueryFilter(p => p.ProductName.Contains("Chang"));
在数据库上下文的OnModelCreating方法中声明以下语句,过滤出Product中的ProductName中包含Chang的数据
这时再执行上边的模糊查询,同时也会筛选条件内数据
EFCore加载模式
EFCore通常使用三种加载模式:延迟加载、立即加载、显示加载
立即加载实体
立即加载就是将实体类中的相关数据(包括外键表)一次性查询出来
使用Include查询可以实现立即加载
using(var db = new MyContext()){IQueryable<Category> cats = db.Categories.Include(c=>c.Products); //使用 Include 访问Categories类中的导航属性Productsforeach (Category item in cats){System.Console.WriteLine($"{item.CategoryName} 类别有 {item.Products.Count()} 产品");}
}
可以将Include方法后的代码注释一下执行看,这里查询是获取不到导航属性的
延迟加载
延迟加载: 每当我们尝试读取导航属性时,延迟加载将检查它们是否已加载,如果没有加载,就立即执行sql语句加载它们,将当前的导航属性加载出,其余导航属性不动,然后返回导航属性查出来的数据
使用代理的延迟加载 (全局)
使用全局代理的延迟加载,必须每个导航属性带有 virtual修饰
public virtual Category Category { get; set ; }
- 为代理引用NuGet包
dotnet add package Microsoft.EntityFrameworkCore.Proxies --version 5.0.0
- 配置延迟代理以使用代理
在数据库上下文OnConfiguring中输入 optionsBuilder.UseLazyLoadingProxies() - 使用时应该避免使用循环处理导航属性
避免使用下边注释的代码循环处理导航属性,应一次性将数据取出,防止多次循环数据库
using(var db = new MyContext()){IQueryable<Category> cats = db.Categories; //.Include(c=>c.Products); //使用 Include 访问Categories类中的导航属性ProductsICollection<Product> items = cats.FirstOrDefault().Products;// var product = items.Products;// foreach (Category item in cats)// {// System.Console.WriteLine($"{item.CategoryName} 类别有 {item.Products.Count} 产品");// }
}
不使用代理的延迟加载
使用依赖注入的方式为单个对象使用延迟加载,这里的导航属性没有必须使用virtual修饰
首先需要导入Abstractions包
然后将ILazyLoader 注入到类中,使用LazyLoader .Load()方法检查实体,并在使用时更新实体数据,将实体使用ref 引用出
dotnet add package Microsoft.EntityFrameworkCore.Abstractions --version 5.0.0
using Microsoft.EntityFrameworkCore.Infrastructure;public class Blog
{private ICollection<Post> _posts;public Blog(){}private Blog(ILazyLoader lazyLoader){LazyLoader = lazyLoader;}private ILazyLoader LazyLoader { get; set; }public int Id { get; set; }public string Name { get; set; }public ICollection<Post> Posts{get => LazyLoader.Load(this, ref _posts);set => _posts = value;}
}public class Post
{private Blog _blog;public Post(){}private Post(ILazyLoader lazyLoader){LazyLoader = lazyLoader;}private ILazyLoader LazyLoader { get; set; }public int Id { get; set; }public string Title { get; set; }public string Content { get; set; }public Blog Blog{get => LazyLoader.Load(this, ref _blog);set => _blog = value;}
}
这里借用 官网的例子
显示加载
显示加载与延迟加载的工作方式类似,不同之处在于显示加载可以控制加载那些相关数据以及何时加载
using(var db = new MyContext()){IQueryable<Category> cats = db.Categories;db.ChangeTracker.LazyLoadingEnabled = false; //禁用延迟加载//返回唯一元素 (如果存在多个则会异常)var categorySingle = db.Categories.Single(b => b.CategoryName == "Beverages");/// <summary>/// Entry:获取提供的实体,提供对实体操作的跟踪/// Collection :将此实体与其他实体相关联的属性/// Load 加载实体中的数据 /// </summary>/// <returns></returns>db.Entry(categorySingle).Collection(c => c.Products).Load();System.Console.WriteLine($"{categorySingle.CategoryName} has {categorySingle.Products.Count}");
}
显示加载主要在于Load方法
/// <summary>///categorySingle 提供一个符合条件的实体
/// Entry:获取提供的实体,提供对实体操作的跟踪
/// Collection :将此实体与其他实体相关联的属性
/// Load 加载实体中的数据
/// </summary>
/// <returns></returns>
db.Entry(categorySingle).Collection(c => c.Products).Load();
官网
使用EF Core 操作数据
操作数据就相对简单了很多,DbContext能够自动维护更改和跟踪,因此本地实体可以跟踪多个更改,包括添加新实体,修改实体和删除实体
插入实体
using(var db = new MyContext()){Product product = new Product{ProductId = 78,ProductName = "Bob",UnitPrice = 2.3M};db.Products.Add(product);if(db.SaveChanges() == 1){System.Console.WriteLine("新增成功");}}
修改实体
using(var db = new MyContext()){Product updateProduct = db.Products.FirstOrDefault(p => p.ProductName =="Chang");updateProduct.UnitPrice += 1M;if(db.SaveChanges() == 1){System.Console.WriteLine("修改成功"); }
}
删除实体
using(var db = new MyContext()){Product updateProduct = db.Products.FirstOrDefault(p => p.ProductName.StartsWith("C"));db.Products.RemoveRange(updateProduct);int affected = db.SaveChanges();if(affected == 1){System.Console.WriteLine("删除成功");}
}
池化数据库上下文
官网介绍
事务
每次调用 SaveChanges方法时,都会启动隐式事务,以便出现问题时回滚所有的修改
事务通过应用锁来防止在发生一系列更改时进行读写操作,从而维护数据库的完整性
定义显示事务
using(var db = new MyContext()){using(IDbContextTransaction t = db.Database.BeginTransaction()){ //开始事务Product updateProduct = db.Products.FirstOrDefault(p => p.ProductName.StartsWith("C"));db.Products.RemoveRange(updateProduct);int affected = db.SaveChanges();t.Commit(); //提交事务if(affected == 1){System.Console.WriteLine("删除成功");}}
}
官方说明
该文章是学习过程中的记录,知识基本都摘自书中
还望指点错误,以及其他应学习知识点 !
记录一下学习EFCore中的基础知识相关推荐
- 汇编学习(1)——基础知识
汇编学习(1)--基础知识 ---谨以此系列文章记录我的汇编学习. 关于汇编 说起汇编语言,那自然不得不想到机器语言,在汇编语言尚未诞生之际,程序猿们只能非常苦逼的敲着0和1,还要记住一大堆复杂难记 ...
- Python学习--最完整的基础知识大全
##Python学习–最完整的基础知识大全 关于python的基础知识学习,网上有很多资料,今天我就把我收藏的整理一下分享给大家! #####菜鸟教程python2 #####菜鸟教程python3 ...
- 小猪的Python学习之旅 —— 1.基础知识储备
小猪的Python学习之旅 -- 1.基础知识储备 引言: (文章比较长,建议看目录按需学习-) 以前刚学编程的时候就对Python略有耳闻,不过学校只有C,C++,Java,C#. 和PHP有句&q ...
- 《Java并发编程实践》学习笔记之一:基础知识
<Java并发编程实践>学习笔记之一:基础知识 1.程序与进程 1.1 程序与进程的概念 (1)程序:一组有序的静态指令,是一种静态概念: (2)进程:是一种活动,它是由一个动作序列组成 ...
- 学习python需要什么基础-学习Python需要哪些基础知识?
今天是腊月二十七,给各位朋友拜个早年! Python学习可以分为几个阶段,入门.进阶.应用. 先说说入门需要哪些基本的知识储备. Python因为易于学习的特点,入门很简单,掌握基本的Python知识 ...
- python基础知识资料-学习Python列表的基础知识汇总
千里之行,始于足下.要练成一双洞悉一切的眼睛,还是得先把基本功扎扎实实地学好.今天,本喵带大家仔细温习一下Python的列表.温故而知新,不亦说乎. 当然,温习的同时也要发散思考,因为有些看似无关紧要 ...
- 学习hadoop需要具备基础知识
学习hadoop需要具备基础知识 首先整体上了解Hadoop,包括hadoop是什么,能够做什么,使用场景等,不需要考虑细节问题.在有了整体上的了解后,就开始准备系统地学习hadoop.建议:勿一味学 ...
- 学python需要什么基础-学习Python需要哪些基础知识?
今天是腊月二十七,给各位朋友拜个早年! Python学习可以分为几个阶段,入门.进阶.应用. 先说说入门需要哪些基本的知识储备. Python因为易于学习的特点,入门很简单,掌握基本的Python知识 ...
- 前端学习笔记(js基础知识)
前端学习笔记(js基础知识) JavaScript 输出 JavaScript 数据类型 常见的HTML事件 DOM 冒泡与捕获 流程控制语句 for..in 计时器 let,var,const的区别 ...
- 210学习日记(18)_ARM基础知识
210学习日记(18) --ARM基础知识 注意: 以下大部分类容都来自网上现成的(直接拷贝过来的,然后经整理)!!!! 问1:ARM处理器工作模式有几种?各种工作模式下分别有什么特点? 答1:ARM ...
最新文章
- 黑客是如何绕过WAF的
- php 对象里还有哪些对象_PHP面向对象(OOP)之实例化对象的方法
- Spring注解之@Transactional对于事务异常的处理
- Pytho学习笔记:错误,测试,调试(合)
- python画六边形
- es6 babel编译
- jdk1.8中文帮助文档
- 安卓机型刷写第三方twrp 刷写第三方rom的教程和一些问题解决
- vscode c语言插件_推荐学习C语言或CPP使用的代码编辑器
- fir302c虚拟服务器,斐讯 FIR302C 无线路由器WDS无线桥接设置
- 往年报名破千人,南京大学计算机系2022年夏令营来袭
- linux 查看定时任务
- MIDIPLUS/迷笛studio m pro 2 valve声卡安装调试教程
- vue项目如何集成金格WebOffice2015
- python gdal 基于栅格shp文件裁剪geotif图
- 天津铸源宝利缘系统成立仪式在津召开
- SVN在Eclipse中的安装步骤以及使用方法和建立分支
- Symantec Backup Exec恢复数据库
- lanker php 大码,GitHub - TREYWANGCQU/LANKERS: CQU-LANKERS
- 创龙科技全志T3国产评估板(4核ARM Cortex-A7)-性能及参数资料