一文学会使用entityframeworkcore(代码片段)

JimCarter JimCarter     2023-01-06     802

关键词:

本文基于发稿时EF Core的最新版本5.0.

1. 操作篇

快速开始
如何根据实体类生成模型
实体关系的配置
值转换器(Value Conversion)
数据库架构调整与数据迁移

2. 原理篇

更改跟踪原理
数据查询原理
数据保存原理

3. 优化篇

日志、指标与拦截器
性能优化

4. 常见问题

4.1 怎么查看生成的sql

使用LogTo

protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)

    optionsBuilder
        .UseSqlServer(@"Server=(localdb)\\mssqllocaldb;Database=Blogging;Integrated Security=True")
        .LogTo(Console.WriteLine, LogLevel.Information);

4.2 怎么直接执行sql

context.Database.ExecuteSqlRaw("UPDATE [Employees] SET [Salary] = [Salary] + 1000");

//防止sql注入方式四,直接构造DbParameter
var user = new SqlParameter("user", "johndoe");
var blogs = context.Blogs
    .FromSqlRaw("EXECUTE dbo.GetMostPopularBlogsForUser @user", user)
    .ToList();

详见【原理篇-数据查询原理】

4.3 怎么执行事务

using var context = new BloggingContext();
using var transaction = context.Database.BeginTransaction();
try

    context.Blogs.Add(new Blog  Url = "http://blogs.msdn.com/dotnet" );
    context.SaveChanges();

    context.Blogs.Add(new Blog  Url = "http://blogs.msdn.com/visualstudio" );
    context.SaveChanges();

    var blogs = context.Blogs
        .OrderBy(b => b.Url)
        .ToList();

    // Commit transaction if all commands succeed, transaction will auto-rollback
    // when disposed if either commands fails
    transaction.Commit();

catch (Exception)

    // TODO: Handle failure

事务失败会自动回滚,不需要手动操作。事务的更多用法,详见【原理篇-数据保存原理】

4.4 怎么使用数据库锁

4.4.1 乐观锁

实体上新增一个属性,标记为Timestamp:

[Timestamp]
public byte[] versionget;set;

然后把SaveChanges用try-catch包裹起来 ,当有并发冲突时会抛出DbUpdateConcurrencyException异常,然后进行处理 即可。

详见【原理篇-数据保存原理3.2节】

4.4.2 悲观锁

查询之后就锁住,禁止其他查询和修改,容易死锁,性能低。efcore不支持,可以借助ADO.NET的事务实现:

static void Main(string[] args)
        
           using (SqlConnection conn = new SqlConnection(connstr))
            
                conn.Open();
                using (var tx = conn.BeginTransaction())
                
                    try
                    
                        using (var selectCmd = conn.CreateCommand())
                        
                            selectCmd.Transaction = tx;
                            //xlock:排它锁,ROWLOCK行锁
                            selectCmd.CommandText = "select * from T_Girls with(xlock,ROWLOCK) where id=1";
                            using (var reader = selectCmd.ExecuteReader())
                            
                                if (!reader.Read())
                                
                                    Console.WriteLine("没有id为1的女孩");
                                    return;
                                
                                string bf = null;
                                if (!reader.IsDBNull(reader.GetOrdinal("BF")))
                                
                                    bf = reader.GetString(reader.GetOrdinal("BF"));
                                
                                if (!string.IsNullOrEmpty(bf))//已经有男朋友
                                
                                    if (bf == myname)
                                    
                                        Console.WriteLine("早已经是我的人了");
                                    
                                    else
                                    
                                        Console.WriteLine("早已经被" + bf + "抢走了");
                                    
                                    Console.ReadKey();
                                    return;
                                
                                //如果bf==null,则继续向下抢
                            
                            Console.WriteLine("查询完成,开始update");
                            using (var updateCmd = conn.CreateCommand())
                            
                                updateCmd.Transaction = tx;
                                updateCmd.CommandText = "Update T_Girls set BF=@bf where id=1";
                                updateCmd.Parameters.Add(new SqlParameter("@bf", myname));
                                updateCmd.ExecuteNonQuery();
                            
                            Console.WriteLine("结束Update");
                            Console.WriteLine("按任意键结束事务");
                            Console.ReadKey();
                        
                        //事务提交之前你一直占有这行数据
                        tx.Commit();
                    
                    catch (Exception ex)
                    
                        Console.WriteLine(ex);
                        tx.Rollback();
                    
                
            
            Console.ReadKey();
        

4.5 导航属性的类型

如果是集合则可以为ICollection<T>List<T>HashSet<T>

4.6 主外键的默认命名规则

  1. 主键命名规则:符合IdIDClassnameIdClassnameID这些规则的属性自动认为是主键
  2. 外键规则:<导航属性名称><导航属性对应实体的主键属性名><导航属性对应实体的主键属性名>

4.7 EF Core怎么知道SaveChanges时哪些内容要提交到数据库?

通过Change tracking实现的。当数据从数据库读取出来之后,efcore会创建数据的快照,调用保存时会与快照进行对比。详见【原理篇-更改跟踪原理】。

4.8 禁止自动给主键赋值

[DatabaseGenerated(DatabaseGeneratedOption.None)]
public int BlogId  get; set; 

5. 一些“坑”

“坑”不一定是真坑,所以加了引号。

5.1 跟踪查询引起的问题

ef默认使用的是跟踪查询,会把查询出来的实体实例信息记录在跟踪器中。当下次查询返回了一个跟踪器中已记录(主键相同)的实体时,则会把记录的这个实体作为查询结果直接返回(虽然还是会执行一遍sql),而且不会用数据库中的值覆盖现有实体的值。所以以下代码可能会出乎你的意料:

var blog = context.Blogs.Single(b => b.Name == "fish");
blog.Url = "aa";
var b = context.Blogs.Single(b => b.Name == "fish");
Console.WriteLine(b.Url);

此时blog的url还是aa,虽然生成的sql可以看到明显有两次查询:

info: 2021/6/2 15:40:01.856 RelationalEventId.CommandExecuted[20101] (Microsoft.EntityFrameworkCore.Database.Command)
      Executed DbCommand (4ms) [Parameters=[], CommandType='Text', CommandTimeout='30']
      SELECT TOP(2) [b].[Id], [b].[Name], [b].[Url]
      FROM [Blogs] AS [b]
      WHERE [b].[Name] = N'fish'
info: 2021/6/2 15:40:01.862 RelationalEventId.CommandExecuted[20101] (Microsoft.EntityFrameworkCore.Database.Command)
      Executed DbCommand (0ms) [Parameters=[], CommandType='Text', CommandTimeout='30']
      SELECT TOP(2) [b].[Id], [b].[Name], [b].[Url]
      FROM [Blogs] AS [b]
      WHERE [b].[Name] = N'fish'

针对where的一个操作:

var orders = context.Orders.Where(o => o.Id > 1000).ToList();
var filtered = context.Customers.Include(c => c.Orders.Where(o => o.Id > 5000)).ToList();

此时的查询结果:order的id大于1000的数据,而不是大于5000的数据。因为在跟踪查询的情况下,filter里的导航属性会被认为已经加载完成。

因为这个问题的存在,所以有时候你会发现明明自己没有调用Include,却也把子实体的数据给加载过来了。

以上问题产生的原因是:efcore只会维护同一实体的一个状态,如果同一个实体既是modified又是deleted,那么保存时就会出错(上面的原理篇-更改跟踪原理做了详细解释)。这个问题可以通过使用非跟踪查询AsNoTracking解决。

var blog = context.Blogs.Single(b => b.Name == "fish");
blog.Url = "aa";
var b = context.Blogs.AsNoTracking().Single(b => b.Name == "fish");
Console.WriteLine(b.Url);

一文学会使用bazel构建c++项目(代码片段)

文章目录什么是Bazel?简介特点使用流程构建流程基本概念WORKSPACE(工作区)PACKAGE(包)TARGET(目标)LABEL(标签)BUILD(构建文件)DEPENDENCY(依赖项)实战环境搭建安装Baz 查看详情

一文学会使用bazel构建c++项目(代码片段)

文章目录什么是Bazel?简介特点使用流程构建流程基本概念WORKSPACE(工作区)PACKAGE(包)TARGET(目标)LABEL(标签)BUILD(构建文件)DEPENDENCY(依赖项)实战环境搭建安装Baz 查看详情

一文学会使用bazel构建c++项目(代码片段)

文章目录什么是Bazel?简介特点使用流程构建流程基本概念WORKSPACE(工作区)PACKAGE(包)TARGET(目标)LABEL(标签)BUILD(构建文件)DEPENDENCY(依赖项)实战环境搭建安装Baz 查看详情

一文学会-fiddler抓包快速实战(代码片段)

文章目录一、Fiddler介绍与安装Fiddler介绍Fiddler下载与安装二、WEB端抓包开启/关闭抓包初次配置验证是否可以正常抓包Filter请求过滤_过滤&筛选页签启用Filter只显示指定的请求只显示内网的Hosts[使用频率较少]只显示外网的Hosts[... 查看详情

一文学会open5gs和ueransim安装及使用(代码片段)

1环境准备VM配置(2台)①操作系统:Ubuntu18.04.6server(注意server版安装需要断网安装)②CPU:4③Memory:2G④网卡2块:一块使用NAT模式用于访问外网(同时用于WebUI访问),另一块用于VM间的通信࿰... 查看详情

pycharm用着卡还收费!何不试试vscode!一文学会vscode使用python(代码片段)

一、前言刚开始学Python的小伙伴可能会觉得每次写Python打开Cmd或者idle有点烦躁,没有代码补全也没有格式提示等。所以直接上手了Pycharm。但pycharm专业版还收费,而且这软件还挺占内存!电脑配置不高的小伙伴就会感... 查看详情

springcloudconfig一文学会(代码片段)

目录1、简介2、正文2.1SpringCloudConfig+手动刷新2.2SpringCloudConfig+Git+WebHook实现自动刷新2.3SpringCloudConfig+Eureka2.4SpringCloudBus多端刷新1、简介传统配置的痛点:在以前的项目中,我们通过配置文件、操作系统变量、Jav... 查看详情

(转)一文学会用tensorflow搭建神经网络

 一文学会用Tensorflow搭建神经网络 本文转自:http://www.jianshu.com/p/e112012a4b2d字数2259 阅读3168 评论8 喜欢11cs224d-Day6:快速入门Tensorflow本文是学习这个视频课程系列的笔记,课程链接是youtube上的,讲的很好,浅显易... 查看详情

一文学会express(代码片段)

文章目录Express安装基本使用中间件编写中间件客户端发送请求的方式解析body数据解析JSON格式解析urlencoded格式什么是x-www-form-urlencoded格式?urlencoded方法解析form-data格式解析非文件类型数据上传文件日志处理解析params和query动... 查看详情

一文学会calico及k8s网络策略入门

参考技术Acalico网络策略提供了一系列的策略能力,其中包括策略的顺序/优先级,拒绝规则和其它更灵活的匹配规则。kubernetes网络策略仅应用于Pod,而Calico网络策略可以应用于多种类型的终端,比如pods,VMs和主机接口等。而且,... 查看详情

一文带你学会使用yolo及opencv完成图像及视频流目标检测(上)|附源码(代码片段)

计算机视觉领域中,目标检测一直是工业应用上比较热门且成熟的应用领域,比如人脸识别、行人检测等,国内的旷视科技、商汤科技等公司在该领域占据行业领先地位。相对于图像分类任务而言,目标检测会更加复杂一些,不... 查看详情

一文学会k8s多master集群+keepalived高可用实战

参考技术AApiserver是kubernetes集群交互的入口,封装了核心对象的增删改查操作,提供了RESTFul风格的API接口,通过etcd来实现持久化并维护对象的一致性。所以在整个K8S集群中,Apiserver服务至关重要,一旦宕机,整个K8S平台将无法... 查看详情

一文学会calico网络自定义

参考技术ACalico支持多个容器网络选项,用于可伸缩性、网络性能和与现有基础设施的互操作性。不同的网络实现更适合不同的环境。Calico提供了几种不需要封装的基于IP路由的网络实现。如果您的部署需要封装,Calico提供覆盖网... 查看详情

一文学会springmvc表单标签(代码片段)

...专栏:国学周更-心性养成之路🥭本文内容:一文学会SpringMVC表单标签文章目录form标签input标签password标签checkbox标签checkboxes标签radiobutton与radiobuttons标签select与option/options标签  使用SpringMVC提供的表单标签可以让JSP... 查看详情

一文教你学会需求分析与管理

大家好,我是孙叫兽,本期给大家分享需求分析与管理,在项目开始或者需求整理时,这方面的知识显得尤为重要,常言道:良好的开始是成功的一半!1.需求的定义产品需求是在一定的时期,一定的场景中,无论是心理上还是... 查看详情

一文教你学会需求分析与管理

大家好,我是孙叫兽,本期给大家分享需求分析与管理,在项目开始或者需求整理时,这方面的知识显得尤为重要,常言道:良好的开始是成功的一半!1.需求的定义产品需求是在一定的时期,一定的场景中,无论是心理上还是... 查看详情

一文学会使用bazel构建c++项目(代码片段)

文章目录什么是Bazel?简介特点使用流程构建流程基本概念WORKSPACE(工作区)PACKAGE(包)TARGET(目标)LABEL(标签)BUILD(构建文件)DEPENDENCY(依赖项)实战环境搭建安装Bazel安装... 查看详情

python字体反爬之乐居字体反爬,一文看懂,一文学会(代码片段)

📢📢📢📢📢📢💗你正在阅读【梦想橡皮擦】的博客👍阅读完毕,可以点点小手赞一下🌻发现错误,直接评论区中指正吧📆橡皮擦的第672篇原创博客从订购之日起,案例5年... 查看详情