efcore技巧贴-也许有你不知道的使用技巧

前言

.net 环境近些年也算是稳步发展。在开发的过程中,与数据库打交道是必不可少的。早期的开发者都是DbHelper一撸到底,到现在的各种各样的ORM框架大行其道。孰优孰劣谁也说不清楚,文无第一武无第二说的就是这个理。没有什么最好的,只有最适合你的。

本人也是从DbHelper开始,期间用过SugarSql,再到EFCODE。本着学习分享的初衷分享本人工作中总结的一些小技巧,希望能帮助更多开发者,期望能达到共同进步。文中若有错误地方,欢迎大家不吝赐教。

1. DbContext配置

在asp.net中,通常情况下,通过在Startup类的ConfigureServices方法中,将ef服务注入。

示例代码如下:

services.AddDbContext<DemoDbContext>(opt=>opt.UseMySql("server=.;Database=demo;Uid=root;Pwd=123;Port=3306;"));

以上代码表示使用MySql数据库。如果使用SqlServer数据库,可以把UseMySql改为UseSqlServer,其他数据库的使用方式也是通过调用不同的方法进行选择。但需要安装对应的扩展方法的程序包,如 Microsoft.EntityFrameworkCore.SqlServer 或 Microsoft.EntityFrameworkCore.Sqlite。

另外,UseMySql方法还包含了一个可空的Action <MySqlDbContextOptionsBuilder>类型的参数,可以通过此参数进行一些个性化的配置,比如配置重试机制。如下所示:

services.AddDbContext<DemoDbContext>(opt => opt.UseMySql("server=.;Database=demo;Uid=root;Pwd=123456;Port=3306;",provideropt => provideropt.EnableRetryOnFailure(3,TimeSpan.FromSeconds(10),new List<int>(){0} )));

这个重试机制在某些场景下还是比较有用的。比如,由于网络波动或访问量导致的一瞬间的连接超时。如果不设置重试机制,则会直接触发异常,设置了超时后,则会根据设置的时间间隔以及重试次数进行重试。EnableRetryOnFailure方法的最后一个参数是用来设置错误代码的,只有设置了错误代码的错误,才会触发重试。获取错误代码的方法有很多种,个人比较推荐的是,通过异常信息进行获取,比如,使用MySql数据时,触发的异常类型是MySqlException,此类的Number属性的值EnableRetryOnFailure方法所需要的Number

2. DbContext线程问题

efcore不支持在同一个DbContext实例上运行多个并行操作,这包括异步查询的并行执行以及从多个线程进行的任何显式并发使用。因此,始终 await 异步调用,或对并行执行的操作使用单独的 DbContext 实例。

当 EF Core 检测到并行操作或多个线程同时尝试使用 DbContext 实例时,你将看到一条 InvalidOperationException,其中包含类似于下面的消息:

A second operation started on this context before a previous operation completed. Any instance members are not guaranteed to be thread safe.

意思是,在上一个操作没有执行完毕之前,又启动了一个新的操作,所以不能保证线程是安全的。

下面是一段错误的,可以触发这个异常的示例代码:

所以,请始终await异步调用。如果在多个多个线程中使用DbContext,需保证每个线程的DbContext的实例是唯一的。

3. 数据库使用连接池

使用 services.AddDbContextPool比使用 services.AddDbContext吞吐量提升在10~20的百分点(非官方说法,对性能提高数据是本人测试后得到的结果)。

需要注意的是,连接池大小并不是越大越好。

4. 日志记录

在使用ef时,基本上绝大多数和数据库的交互都是通过linq实现的,然后ef将linq翻译成对应的sql语句,在排查问题的时候,在开发或者排查问题时,往往需要关注最终执行的sql脚本,所以就需要通过日志的方式查看。

efcore2.x的版本默认是注入日志服务,所以不需要额外的操作,就可以查看对应的sql脚本。但efcore3.x的版本默认移除了日志服务,具体原因参照:https://docs.microsoft.com/zh-cn/ef/core/what-is-new/ef-core-3.0/breaking-changes#adddbc。

可通过自定义DbContext的方式注入日志任务,示例代码如下:

public static readonly ILoggerFactory MyLoggerFactory= LoggerFactory.Create(builder => { builder.AddConsole(); });
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{base.OnConfiguring(optionsBuilder);optionsBuilder.UseLoggerFactory(MyLoggerFactory);
}

当执行ef代码时,可在控制台中查看相关的sql脚本,如下图所示:

5. 增

插入数据到数据库常用的场景有:普通单表单行插入,多表级联插入,批量插入。

普通单表单行插入比较简单,实例代码如下:

var student = new Student {CreateTime = DateTime.Now, Name = "zjjjjjj"};
await _context.Students.AddAsync(student);
await _context.SaveChangesAsync();

多表级联插入,需要在实体映射中配置属性导航。

比如Blog表和Post是的关系是1对多的关系。则在Blog的实体中,定义一个类型为List <Post>的属性。示例代码如下:

[Table("blog")]
public class Blog 
{[Column("id")]public long Id { get; set; }[Column("title")]public string Title { get; set; }public List<Post> Posts { get; set; }[Column("create_date")]public DateTime CreateDate { get; set; }
}

对应的插入语句如下所示:

var blog = new Blog
{Title = "测试标题",Posts = new List<Post>{new Post{Content = "评论1"},new Post{Content = "评论2"},new Post{Content = "评论3"},}
};
await _context.Blog.AddAsync(blog);
await _context.SaveChangesAsync();

执行此代码,会生成如下的日志:

从日志中可以看出,通过这种方式实现了级联插入的效果。

批量插入实现方式有两种,一种是EF默认实现,适用于数据源较少的情况。另一种,我们基于EF开发一个大数据量批量插入的服务,适合于数据源大于1000的场景。在万级及以上的数据量上,较EF默认的批量插入性能上有非常明显的提升。具体参考:https://www.cnblogs.com/fulu/p/13370335.html

EF默认实现:

var list = new List<Student>();
for (int i = 0; i < num; i++)
{list.Add(new Student { CreateTime = DateTime.Now, Name = "zjjjjjj" });
}
await _context.Students.AddRangeAsync(list);
await _context.SaveChangesAsync();

ISqlBulk实现:

var list = new List<Student>();
for (int i = 0; i < 100000; i++)
{list.Add(new Student { CreateTime = DateTime.Now, Name = "zjjjjjj" });
}
await _bulk.InsertAsync(list);

自增 OR GUID

int自增的优点:

1、需要很小的数据存储空间,仅仅需要4 byte 。

2、insert和update操作时使用INT的性能比GUID好,所以使用int将会提高应用程序的性能。

3、index和Join 操作,int的性能最好。

4、容易记忆。

int自增的缺点:

1、使用INT数据范围有限制。如果存在大量的数据,可能会超出INT的取值范围。

2、很难处理分布式存储的数据表。

GUID做主键的优点:

1、唯一性。

2、适合大量数据中的插入和更新操作。

3、跨服务器数据合并非常方便。

GUID做主键的缺点:

1、存储空间大(16 byte),因此它将会占用更多的磁盘大小。

2、很难记忆。join操作性能比int要低。

3、没有内置的函数获取最新产生的guid主键。

4、EF默认生成的GUID是无序的,会影响数据插入性能。

结论:

在数据量比较少的场景下,建议使用int自增,比如分类。对于大数据量,建议使用有序GUID。因为默认.net生成GUID是无序的,而数据库中主键默认是聚集索引,而聚集索引在物理上的存储是有序的,当插入数据时,如果插入的是无序的GUID,可能就会涉及到移动数据的情况,进而影响插入的性能,特别是百万级数据量的时候,性能影响则较为明显。参考资料:https://www.cnblogs.com/CameronWu/p/guids-as-fast-primary-keys-under-multiple-database.html

其他可选方案:

经过个人多番了解,目前市面上常用的分布式id生成算法和Twitter发布的雪花算法大同小异,个人也在项目中使用过雪花算法,有兴趣的朋友可以在博客园找下相关的内容。不过目前用.net封装的雪花算法普遍较基础,很难在docker或者k8s环境下简单的使用,所以在此预告下,本人根据雪花算法编写的可用于k8s环境的即将开源,敬请期待。

6. 查

EF使用Linq查询数据库中的数据,使用Linq可编写强类型的查询。当命令执行时,EF先将Linq表达式转换成sql脚本,然后再提交给数据库执行。可在日志中查看生成的sql脚本。

根据条件查询:
await _context.Blog.Where(x=>x.Id>0).ToListAsync();

上述代码执行时生成的sql脚本如下所示:

SELECT `x`.`id`, `x`.`create_date`, `x`.`title`FROM `blog` AS `x`WHERE `x`.`id` > 0
获取单个实体

可实现获取单个实体的方式有First,FirstOrDefault,Single,SingleOrDefault

其中First,FirstOrDefault执行时生成的sql脚本如下:

 SELECT `x`.`id`, `x`.`create_date`, `x`.`title`FROM `blog` AS `x`WHERE `x`.`id` > 10LIMIT 1

Single,SingleOrDefault执行时生成的sql脚本如下:

 SELECT `x`.`id`, `x`.`create_date`, `x`.`title`FROM `blog` AS `x`WHERE `x`.`id` > 10LIMIT 2

细心的你应该已经发现了两者的区别,Single需要查询2条数据,当返回的数据多余一条时,Single,SingleOrDefault方法就会报Source sequence contains more than one element.异常。所以Single方法仅适用于查询条件对应的数据只有一条的场景,比如查询主键的值。如下所示:

await _context.Blog.SingleOrDefaultAsync(x => x.Id==100);

后缀带OrDefault和不带后缀的区别是,当sql脚本执行查询不到数据时,带后缀的会返回空值,而不带后缀的则会直接报异常。

判断数据库是否存在

可通过Any()和Count()方法实现是否存在数据。示例代码如下:

await _context.Blog.AnyAsync(x => x.Id > 100);
await _context.Blog.CountAsync(x => x.Id > 100)>0;

生成的sql脚本对应如下:

SELECT CASEWHEN EXISTS (SELECT 1FROM `blog` AS `x`WHERE `x`.`id` > 100)THEN TRUE ELSE FALSEEND
SELECT COUNT(*)FROM `blog` AS `x`WHERE `x`.`id` > 100

乍一看,Any方法生成的脚本貌似更复杂些,但实际上,Any方法的性能在大数据量下比Count方法高了很多。所以在判断是否存在时,请使用Any方法。

连接查询

连接查询是关系数据库中最主要的查询,主要包括内连接、外连接(左连接、外连接)和交叉连接等。通过连接运算符可以实现多个表查询。本文主要讲解下常用的内连接和左连接。

内连接的示例代码如下:

var query = from post in _context.Postjoin blog in _context.Blog on post.BlogId equals blog.Idwhere blog.Id > 0select new {blog, post};

左连接的示例代码如下:

var query = from post in _context.Postjoin blog in _context.Blog on post.BlogId equals blog.Idinto pbsfrom pb in pbs.DefaultIfEmpty()where pb.Id>0 && post.Content.Contains("1")select new {post,pb.Title};
级联查询

在很多场景中,可能会涉及到查询与父表关联的子表数据,在这样的场景中,会有一部分人先查出主表数据,然后根据主表的主键再去查询子表的数据,笔者在使用ef初期也是这种处理方式的。但借助Include的方法可以让我们更方便的解决父子表级联查询的问题。示例代码如下:

var result = await _context.Blog.Include(b => b.Posts) .SingleOrDefaultAsync(x=>x.Id==157);

如果有更多的层级,可以借助ThenInclude进行查询。

有的时候,还有这样的场景:我们不是简单的查询子表的数据,而是需要查询满足指定条件的数据,那就要求咱们在调用Include的方法时传入参数,示例代码如下:

 var filteredBlogs = await _context.Blogs.Include(blog => blog.Posts.Where(post => post.BlogId == 1).OrderByDescending(post => post.Title).Take(5)).ToListAsync();

注:以上方法仅在.net5中支持。所以,efcore也是在一个发展的过程中,随着时间与版本的更新,功能也会渐渐趋于完善。相关内容请参考:https://docs.microsoft.com/zh-cn/ef/core/querying/related-data

7. 改

使用过EF的应该都了解查询的跟踪与非跟踪的概念吧(纳尼?你没听说过,老衲给您指条明路吧:https://docs.microsoft.com/zh-cn/ef/core/querying/tracking)。

通常来讲,更新的流程大概是这样:查询出数据,修改某些字段的值,调用Update方法,然后调用SaveChange方法。看上去毫无破绽,但如果你仔细观察过生成的sql脚本的话,或许你就应该有更好的方法,咱们先来看看示例代码:

var school = await _context.Schools.FirstAsync(x => x.Id > 0);
school.Name = "6666";
_context.Schools.Update(school);
await _context.SaveChangesAsync();

如下图所示的是执行以上代码生成的update的sql语句,我们发现明明代码中只对Name重新赋了值,但生成的脚本却将此记录的所有字段进行了更新,显然这不是我们想要的结果。

其实,如果实体是通过跟踪查询得到的,则可直接调用SaveChage方法,而不用多余调用Update方法,此时,EF内部会自动判断哪些字段进行了更新,从而只生成值改变了的sql语句。

结论:当要更新的实体开启了跟踪,则更新时,无需调用Update方法, 直接调用SaveChange方法,此时之后更新值发生改变的字段。如果先调用Update则SaveChange,则不管实体的字段有没有更新,生成的sql脚本依旧会更新所有的字段,牺牲了性能。假如你的实体不是通过数据库的跟踪查询获取的,则在调用时才需要调用Update方法。


福禄ICH.架构出品

作者:福尔斯

2020年8月

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/308020.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

关于前端性能优化问题,认识网页加载过程和防抖节流

前端性能优化—网页加载过程、性能优化方法、防抖和节流一、网页加载过程1、加载资源的形式2、加载资源的过程3、渲染页面的过程4、关于window.onload 和 DOMContentLoaded二、性能优化1、性能优化原则2、性能优化的方法3、让加载更快4、让渲染更快三、防抖和节流1、防抖 debou…

javax.net.ssl.SSLHandshakeException: No appropriate protocol

一:报错 二:解决 找到jdk 1.8安装目录&#xff0c;找到C:\Program Files\Java\jre里面的lib\security 下面有个java.security将jdk.tls.disabledAlgorithms后面的SSLv3, TLSv1, TLSv1.1都删除掉.&#xff08;大概位置是在700多行&#xff09; 三:上方并未解决的 我的jdk是这…

『软件工程9』结构化系统分析——解决软件“做什么”问题

结构化系统分析——解决软件“做什么”问题一、系统分析的任务和过程1、系统分析的任务2、系统分析的过程&#xff08;1&#xff09;问题识别&#xff08;2&#xff09;分析与综合&#xff08;3&#xff09;编制文档&#xff08;4&#xff09;系统分析评审二、结构化分析方法1、…

.NET5.0 Preview 8 开箱教程

.NET5.0 Preview 8 开箱教程前言首先&#xff0c;看到 .NET5.0 Preview 8 发布后&#xff0c;作为一枚基层应用开发人员&#xff0c;很想要体验一下新版本的魅力&#xff1b;这可能就是程序员对新技术的一种执着吧。其实从官方宣布 .NETCore 将更名为 .NET5 开始&#xff0c;我…

leetcode977. 有序数组的平方(暴力+双指针)

一:题目 二:暴力双指针 1:暴力 class Solution { public:vector<int> sortedSquares(vector<int>& nums) {vector<int> v;for(int num : nums){int temp pow(num,2);v.push_back(temp);} sort(v.begin(),v.end());return v;} };2:双指针 思路:1.利…

『软件工程10』结构化系统分析:数据流图和字典案例分析

结构化系统分析——数据流图和数据字典案例分析一、数据流图案例分析1、案例1&#xff1a;商店业务管理系统2、案例2&#xff1a;学籍管理系统3、案例3&#xff1a;大型企业数据中心二、数据字典案例分析1、案例1&#xff1a;学籍管理系统三、写在最后接 上一篇文章的内容&…

MongoDB最新4.2.7版本三分片集群修改IP实操演练

背景重新组网&#xff0c;需要对现有MongoDB分片集群服务器的IP进行更改&#xff0c;因此也需要对MongoDB分片集群的IP也进行相应的更新&#xff0c;而MongoDB分片集群的IP修改不能单纯的通过配置来进行&#xff0c;需要一番折腾后才能正常更新&#xff0c;这里对整个MongoDB集…

浅谈Web前端安全策略xss和csrf,及又该如何预防?

Web前端安全策略xss和csrf的攻击和防御一、XSS跨站请求攻击1、什么是XSS2、场景模拟3、XSS的攻击类型4、如何防御XSS二、XSRF跨站请求伪造1、什么是csrf2、场景模拟&#xff08;1&#xff09;场景一&#xff08;2&#xff09;场景二3、CSRF的特点4、CSRF攻击方式5、CSRF常见的攻…

leetcode209. 长度最小的子数组(暴力+滑动窗口)

一:题目 二:暴力滑动窗口 1:暴力解法 class Solution { public:int min (int a ,int b){return a < b ? a : b;}int minSubArrayLen(int target, vector<int>& nums) {int minx 100001;for(int i 0; i < nums.size(); i){vector<int> v;int sum nu…

做权限认证,还不了解IdentityServer4?不二话,赶紧拥抱吧,.NET Core官方推荐!...

目前大多数的应用程序或多或少看起来是上图所示这样的&#xff0c;最常见的交互场景有&#xff08;浏览器与Web应用程序、Web应用程序与WebApi通讯、本地应用程序狱WebApi通讯、基于浏览器的应用程序与WebApi 通讯、基本服务器的应用程序与WebApi通讯、WebApi与WebApi通讯&…

leetcode 904:水果成篮(滑动窗口)

一:题目 二:思路 1.用两个篮子装进两个数&#xff0c;后面只能装入这两个相同的数,并统计个数;如果遇到其他数,则重新开始计数&#xff0c; 这里的重新开始计数指的是在去除第一个篮子中所装进的数 2.滑动窗口来做 滑动窗口的起始位置为:数组的起始位置 滑动体为 统计的个数 滑…

真・WPF 按钮拖动和调整大小

真・WPF 按钮拖动和调整大小独立观察员 2020 年 8 月 29 日手头有个 Winform 程序&#xff0c;是使用动态生成按钮&#xff0c;然后拖动、调整大小&#xff0c;以此来记录一些坐标数据&#xff0c;最后保存坐标数据的。在数据量&#xff08;按钮数量&#xff09;比较小的时候是…

『软件工程11』结构化系统设计:解决软件“怎么做”问题

结构化系统设计——解决软件“做什么”问题一、设计的目标和任务1、目标2、任务3、开发阶段的信息流4、软件设计的重要性5、软件设计的技术观点和管理观点二、设计基础1、结构图&#xff08;体系结构图、模块结构图&#xff09;&#xff08;1&#xff09;分析结构图三者间的关系…

map容器中删除一个元素(value)

一:问题描述 我们想要删除map容器中&#xff0c;一个key值对应的vlaue 二&#xff1a;上码 #include<iostream> #include<map> #include<vector> using namespace std; int main(){map<int,int> m;for(int i 0; i < 4; i){m[i] i1;}//正常输出 …

.Net5发布在即,当心技术断层!

就在上周&#xff0c;.NET5的最后一个预览版&#xff0c;.NET5 Preview.8发布了&#xff0c;更新内容只有几个小bug的修复&#xff0c;已完成.NET5正式版的最后准备&#xff0c;.NET5即将正式面世&#xff01;时光荏苒&#xff0c;回首2016年发布.NET Core1.0至今&#xff0c;已…

『软件测试4』耗子尾汁!2021年了,你还不知道这4种白盒测试方法吗?

软件测试——详解白盒测试基本概念&#xff0c;四种白盒测试方法一、白盒测试基本概念1、白盒测试的定义2、白盒测试的测试对象3、白盒测试的原则4、白盒测试的分类二、静态白盒测试1、代码检查法&#xff08;1&#xff09;代码审查的定义&#xff08;2&#xff09;代码审查的目…

leetcode76:最小覆盖字串(滑动窗口)

一&#xff1a;题目 二:思路 思路拿别人的&#xff0c;感觉写的很nice!! 渣渣杰只能膜拜大佬的了 1.滑动窗口的思想&#xff1a; left 指针和 ring 指针&#xff0c;保证两个指针之间的字符串包含所需要的全部字符。 2在保证 1 的前提下&#xff0c; 向右移动 left&#x…

Orleans 知多少 | Orleans 中文文档上线

Orleans 简介Orleans是一个跨平台框架&#xff0c;用于构建健壮&#xff0c;可扩展的分布式应用程序Orleans建立在.NET开发人员生产力的基础上&#xff0c;并将其带入了分布式应用程序的世界&#xff0c;例如云服务。Orleans可从单个本地服务器扩展到云中全局分布的高可用性应用…

『软件工程12』软件工程实践方法——软件测试

软件工程实践方法—— 软件测试一、软件测试概述1、软件测试的目的&#xff08;1&#xff09;从用户和开发者角度&#xff08;2&#xff09;Myers软件测试目的2、软件测试的原则3、软件测试的对象4、测试信息流5、测试与软件开发各阶段的关系二、软件测试用例1、黑盒测试概述2、…

leetcode59:螺旋矩阵||(思路+详解)

一:题目 二&#xff1a;思路 1.我们需要模拟数字的变化过程, 2.模拟填充的过程:(顺时针) 填充上行 从左到右 填充右行 从上到下 填充下行 从右向左 填充左行 从下到上 3.分析我们填充数字的过程&#xff0c;比如n3的时候 上行:1,2 右行:3,4 下行:5,6 左行:7,8 这么分析下来我们…