安装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)——基础知识

    汇编学习(1)--基础知识 ---谨以此系列文章记录我的汇编学习.  关于汇编 说起汇编语言,那自然不得不想到机器语言,在汇编语言尚未诞生之际,程序猿们只能非常苦逼的敲着0和1,还要记住一大堆复杂难记 ...

  2. Python学习--最完整的基础知识大全

    ##Python学习–最完整的基础知识大全 关于python的基础知识学习,网上有很多资料,今天我就把我收藏的整理一下分享给大家! #####菜鸟教程python2 #####菜鸟教程python3 ...

  3. 小猪的Python学习之旅 —— 1.基础知识储备

    小猪的Python学习之旅 -- 1.基础知识储备 引言: (文章比较长,建议看目录按需学习-) 以前刚学编程的时候就对Python略有耳闻,不过学校只有C,C++,Java,C#. 和PHP有句&q ...

  4. 《Java并发编程实践》学习笔记之一:基础知识

    <Java并发编程实践>学习笔记之一:基础知识 1.程序与进程 1.1 程序与进程的概念 (1)程序:一组有序的静态指令,是一种静态概念:  (2)进程:是一种活动,它是由一个动作序列组成 ...

  5. 学习python需要什么基础-学习Python需要哪些基础知识?

    今天是腊月二十七,给各位朋友拜个早年! Python学习可以分为几个阶段,入门.进阶.应用. 先说说入门需要哪些基本的知识储备. Python因为易于学习的特点,入门很简单,掌握基本的Python知识 ...

  6. python基础知识资料-学习Python列表的基础知识汇总

    千里之行,始于足下.要练成一双洞悉一切的眼睛,还是得先把基本功扎扎实实地学好.今天,本喵带大家仔细温习一下Python的列表.温故而知新,不亦说乎. 当然,温习的同时也要发散思考,因为有些看似无关紧要 ...

  7. 学习hadoop需要具备基础知识

    学习hadoop需要具备基础知识 首先整体上了解Hadoop,包括hadoop是什么,能够做什么,使用场景等,不需要考虑细节问题.在有了整体上的了解后,就开始准备系统地学习hadoop.建议:勿一味学 ...

  8. 学python需要什么基础-学习Python需要哪些基础知识?

    今天是腊月二十七,给各位朋友拜个早年! Python学习可以分为几个阶段,入门.进阶.应用. 先说说入门需要哪些基本的知识储备. Python因为易于学习的特点,入门很简单,掌握基本的Python知识 ...

  9. 前端学习笔记(js基础知识)

    前端学习笔记(js基础知识) JavaScript 输出 JavaScript 数据类型 常见的HTML事件 DOM 冒泡与捕获 流程控制语句 for..in 计时器 let,var,const的区别 ...

  10. 210学习日记(18)_ARM基础知识

    210学习日记(18) --ARM基础知识 注意: 以下大部分类容都来自网上现成的(直接拷贝过来的,然后经整理)!!!! 问1:ARM处理器工作模式有几种?各种工作模式下分别有什么特点? 答1:ARM ...

最新文章

  1. 黑客是如何绕过WAF的
  2. php 对象里还有哪些对象_PHP面向对象(OOP)之实例化对象的方法
  3. Spring注解之@Transactional对于事务异常的处理
  4. Pytho学习笔记:错误,测试,调试(合)
  5. python画六边形
  6. es6 babel编译
  7. jdk1.8中文帮助文档
  8. 安卓机型刷写第三方twrp 刷写第三方rom的教程和一些问题解决
  9. vscode c语言插件_推荐学习C语言或CPP使用的代码编辑器
  10. fir302c虚拟服务器,斐讯 FIR302C 无线路由器WDS无线桥接设置
  11. 往年报名破千人,南京大学计算机系2022年夏令营来袭
  12. linux 查看定时任务
  13. MIDIPLUS/迷笛studio m pro 2 valve声卡安装调试教程
  14. vue项目如何集成金格WebOffice2015
  15. python gdal 基于栅格shp文件裁剪geotif图
  16. 天津铸源宝利缘系统成立仪式在津召开
  17. SVN在Eclipse中的安装步骤以及使用方法和建立分支
  18. Symantec Backup Exec恢复数据库
  19. lanker php 大码,GitHub - TREYWANGCQU/LANKERS: CQU-LANKERS
  20. 创龙科技全志T3国产评估板(4核ARM Cortex-A7)-性能及参数资料

热门文章

  1. 基因结构图的0_在线网站绘制基因结构图!
  2. Linux多启动U盘,syslinux 制作多系统启动U盘
  3. Java一个汉字占几个字节(详解与原理)(转载)
  4. 转page类事件执行顺序
  5. 教程:如何制作一个可控制的人体骨骼模型
  6. 如何查看 Codeigniter 版本号?
  7. 共享打印机服务器脱机状态,共享打印机脱机无法打印
  8. 2021年新版python学习课程网盘分享
  9. 【CCF CSP】201903-1小中大
  10. 计算机网卡不连接网络连接怎么办,台式机无线网卡连接不上网络怎么办