严重问题

客户端求值

  • 如where条件包含的GetValueOrDefault()不能被翻译成sql语句
  • 不规范代码段例子
  1. public async Task<List<Person>> GetPersonsAsync()
  2. {
  3. var results = await _context.Person
  4. .Where(p => p.State.GetValueOrDefault() == 1)
  5. .ToListAsync()
  6. return results;
  7. }

客户端与服务器评估

作为一般规则,Entity Framework Core 会尝试尽可能全面地评估服务器上的查询。 EF Core 将查询的一部分转换为可在客户端评估的参数。 系统将查询的其余部分(及生成的参数)提供给数据库提供程序,以确定要在服务器上评估的等效数据库查询。 EF Core 支持在顶级投影中进行部分客户端评估(基本上为最后一次调用 Select())。 如果查询中的顶级投影无法转换为服务器,EF Core 将从服务器中提取任何所需的数据,并在客户端上评估查询的其余部分。 如果 EF Core 在顶级投影之外的任何位置检测到不能转换为服务器的表达式,则会引发运行时异常。 请参阅查询工作原理,了解 EF Core 如何确定哪些表达式无法转换为服务器。

在 3.0 版之前,Entity Framework Core 支持在查询中的任何位置进行客户端评估。

顶级投影中的客户端评估

在下面的示例中,一个辅助方法用于标准化从 SQL Server 数据库中返回的博客的 URL。 由于 SQL Server 提供程序不了解此方法的实现方式,因此无法将其转换为 SQL。 查询的所有其余部分是在数据库中评估的,但通过此方法传递返回的 URL 却是在客户端上完成。

  1. var blogs = context.Blogs
  2. .OrderByDescending(blog => blog.Rating)
  3. .Select(blog => new
  4. {
  5. Id = blog.BlogId,
  6. Url = StandardizeUrl(blog.Url)
  7. })
  8. .ToList();
  1. public static string StandardizeUrl(string url)
  2. {
  3. url = url.ToLower();
  4. if (!url.StartsWith("http://"))
  5. {
  6. url = string.Concat("http://", url);
  7. }
  8. return url;
  9. }

不支持的客户端评估

尽管客户端评估非常有用,但有时会减弱性能。 请看以下查询,其中的 where 筛选器现已使用辅助方法。 由于数据库中不能应用筛选器,因此需要将所有数据提取到内存中,以便在客户端上应用筛选器。 根据服务器上的筛选器和数据量,客户端评估可能会减弱性能。 因此 Entity Framework Core 会阻止此类客户端评估,并引发运行时异常。

  1. var blogs = context.Blogs
  2. .Where(blog => StandardizeUrl(blog.Url).Contains("dotnet"))
  3. .ToList();

显式客户端评估

在某些情况下,可能需要以显式方式强制进行客户端评估,如下所示

由于数据量小,因此在进行客户端评估时才不会大幅减弱性能。

所用的 LINQ 运算符不会进行任何服务器端转换。

在这种情况下,通过调用 AsEnumerable 或 ToList 等方法(若为异步,则调用 AsAsyncEnumerable 或 ToListAsync),以显式方式选择进行客户端评估。 使用 AsEnumerable 将对结果进行流式传输,但使用 ToList 将通过创建列表来进行缓冲,因此也会占用额外的内存。 但如果枚举多次,则将结果存储到列表中可以带来更大的帮助,因为只有一个对数据库的查询。 根据具体的使用情况,你应该评估哪种方法更适合。

  1. var blogs = context.Blogs
  2. .AsEnumerable()
  3. .Where(blog => StandardizeUrl(blog.Url).Contains("dotnet"))
  4. .ToList();

客户端评估中潜在的内存泄漏

由于查询转换和编译的开销高昂,因此 EF Core 会缓存已编译的查询计划。 缓存的委托在对顶级投影进行客户端评估时可能会使用客户端代码。 EF Core 为树型结构中客户端评估的部分生成参数,并通过替换参数值重用查询计划。 但表达式树中的某些常数无法转换为参数。 如果缓存的委托包含此类常数,则无法将这些对象垃圾回收,因为它们仍被引用。 如果此类对象包含 DbContext 或其中的其他服务,则会导致应用的内存使用量逐渐增多。 此行为通常是内存泄漏的标志。 只要遇到的常数为不能使用当前数据库提供程序映射的类型,EF Core 就会引发异常。 常见原因及其解决方案如下所示:

使用实例方法:在客户端投影中使用实例方法时,表达式树包含实例的常数。 如果你的方法不使用该实例中的任何数据,请考虑将该方法设为静态方法。 如果需要方法主体中的实例数据,则将特定数据作为实参传递给方法。

将常数实参传递给方法:这种情况通常是由于在客户端方法的实参中使用 this 引起的。 请考虑将实参拆分为多个标量实参,可由数据库提供程序进行映射。

其他常数:如果在任何其他情况下都出现常数,则可以评估在处理过程中是否需要该常数。 如果必须具有常数,或者如果无法使用上述情况中的解决方案,则创建本地变量来存储值,并在查询中使用局部变量。 EF Core 会将局部变量转换为形参。

建议解决

无用追踪

  • 无须追踪的数据没有加AsNoTracking

跟踪与非跟踪查询

跟踪行为决定了 Entity Framework Core 是否将有关实体实例的信息保留在其更改跟踪器中。 如果已跟踪某个实体,则该实体中检测到的任何更改都会在 SaveChanges() 期间永久保存到数据库。 EF Core 还将修复跟踪查询结果中的实体与更改跟踪器中的实体之间的导航属性。

从不跟踪无键实体类型。 无论在何处提到实体类型,它都是指定义了键的实体类型。

跟踪查询

返回实体类型的查询是默认会被跟踪的。 这表示可以更改这些实体实例,然后通过 SaveChanges() 持久化这些更改。 在以下示例中,将检测到对博客评分所做的更改,并在 SaveChanges() 期间将这些更改持久化到数据库中。

  1. var blog = context.Blogs.SingleOrDefault(b => b.BlogId == 1);
  2. blog.Rating = 5;
  3. context.SaveChanges();

非跟踪查询

在只读方案中使用结果时,非跟踪查询十分有用。 可以更快速地执行非跟踪查询,因为无需设置更改跟踪信息。 如果不需要更新从数据库中检索到的实体,则应使用非跟踪查询。 可以将单个查询替换为非跟踪查询。

  1. var blogs = context.Blogs
  2. .AsNoTracking()
  3. .ToList();

还可以在上下文实例级别更改默认跟踪行为:

  1. context.ChangeTracker.QueryTrackingBehavior = QueryTrackingBehavior.NoTracking;
  2. var blogs = context.Blogs.ToList();

标识解析

由于跟踪查询使用更改跟踪器,因此 EF Core 将在跟踪查询中执行标识解析。 当具体化实体时,如果 EF Core 已被跟踪,则会从更改跟踪器返回相同的实体实例。 如果结果中多次包含相同的实体,则每次会返回相同的实例。 非跟踪查询不会使用更改跟踪器,也不会执行标识解析。 因此会返回实体的新实例,即使结果中多次包含相同的实体也是如此。 此行为与 EF Core 3.0 之前的版本中的行为有所不同,请参阅早期版本。

跟踪和自定义投影

即使查询的结果类型不是实体类型,默认情况下 EF Core 也会跟踪结果中包含的实体类型。 在以下返回匿名类型的查询中,结果集中的 Blog 实例会被跟踪。

  1. var blog = context.Blogs
  2. .Select(b =>
  3. new
  4. {
  5. Blog = b,
  6. PostCount = b.Posts.Count()
  7. });

如果结果集包含来自 LINQ 组合的实体类型,EF Core 将跟踪它们。

  1. var blog = context.Blogs
  2. .Select(b =>
  3. new
  4. {
  5. Blog = b,
  6. Post = b.Posts.OrderBy(p => p.Rating).LastOrDefault()
  7. });

如果结果集不包含任何实体类型,则不会执行跟踪。 在以下查询中,我们返回匿名类型(具有实体中的某些值,但没有实际实体类型的实例)。 查询中没有任何被跟踪的实体。

  1. var blog = context.Blogs
  2. .Select(b =>
  3. new
  4. {
  5. Id = b.BlogId,
  6. Url = b.Url
  7. });

EF Core 支持执行顶级投影中的客户端评估。 如果 EF Core 具体化实体实例以进行客户端评估,则会跟踪该实体实例。 此处,由于我们要将 blog 实体传递到客户端方法 StandardizeURL,因此 EF Core 也会跟踪博客实例。

  1. var blogs = context.Blogs
  2. .OrderByDescending(blog => blog.Rating)
  3. .Select(blog => new
  4. {
  5. Id = blog.BlogId,
  6. Url = StandardizeUrl(blog)
  7. })
  8. .ToList();
  1. public static string StandardizeUrl(Blog blog)
  2. {
  3. var url = blog.Url.ToLower();
  4. if (!url.StartsWith("http://"))
  5. {
  6. url = string.Concat("http://", url);
  7. }
  8. return url;
  9. }

EF Core 不会跟踪结果中包含的无键实体实例。 但 EF Core 会根据上述规则跟踪带有键的实体类型的所有其他实例。

在 EF Core 3.0 之前,某些上述规则的工作方式有所不同。 有关详细信息,请参阅早期版本。

没有使用异步方法

  • 没有优先使用异步方法
  • 不规范代码段例子
  1. public async Task<int> AddPersons(IEnumerable<Person> persons)
  2. {
  3. this._context.Person.AddRange(persons);
  4. return await this._context.SaveChangesAsync();
  5. }

异步查询

当在数据库中执行查询时,异步查询可避免阻止线程。 异步查询对于在胖客户端应用程序中保持响应式 UI 非常重要。 异步查询还可以增加 Web 应用程序中的吞吐量,即通过释放线程,以处理 Web 应用程序中的其他请求。 有关详细信息,请参阅使用 C# 异步编程。

EF Core 不支持在同一上下文实例上运行多个并行操作。 应始终等待操作完成,然后再开始下一个操作。 这通常是通过在每个异步操作上使用 await 关键字完成的。

Entity Framework Core 提供一组类似于 LINQ 方法的异步扩展方法,用于执行查询并返回结果。 示例包括 ToListAsync()、ToArrayAsync()、SingleAsync()。 某些 LINQ 运算符(如 Where(...) 或 OrderBy(...))没有对应的异步版本,因为这些方法仅用于构建 LINQ 表达式树,而不会导致在数据库中执行查询。

EF Core 异步扩展方法在 Microsoft.EntityFrameworkCore 命名空间中定义 。 必须导入此命名空间才能使这些方法可用。

  1. public async Task<List<Blog>> GetBlogsAsync()
  2. {
  3. using (var context = new BloggingContext())
  4. {
  5. return await context.Blogs.ToListAsync();
  6. }
  7. }

事务滥用

  • 没必要使用事务的场景使用事务
  • 不规范代码段例子
  1. public async Task<bool> UpdatePersonInfo(List<Person> persons, List<Address> addresses)
  2. {
  3. using (var transaction = _dbContext.Database.BeginTransaction(IsolationLevel.ReadCommitted))
  4. {
  5. try
  6. {
  7. _dbContext.Person.UpdateRange(persons);
  8. await _dbContext.SaveChangesAsync();
  9. _dbContext.Address.UpdateRange(addresses);
  10. await _dbContext.SaveChangesAsync();
  11. transaction.Commit();
  12. return true;
  13. }
  14. catch (Exception ex)
  15. {
  16. transaction.Rollback();
  17. throw new InternalServerErrorException($"更新失败,ErrorMessage:{ex.Message}\r\nInnerException:{ex.InnerException}", ex);
  18. }
  19. }
  20. }

使用事务

事务允许以原子方式处理多个数据库操作。 如果已提交事务,则所有操作都会成功应用到数据库。 如果已回滚事务,则所有操作都不会应用到数据库。

默认事务行为

默认情况下,如果数据库提供程序支持事务,则会在单次调用 SaveChanges() 时将所有更改都将应用到事务中。 如果其中有任何更改失败,则会回滚事务且所有更改都不会应用到数据库。 这意味着,SaveChanges() 可保证要么完全成功,要么在出现错误时不修改数据库。

对于大多数应用程序,此默认行为已足够。 除非应用程序确有需求,否则不应手动控制事务。

控制事务

可以使用 DbContext.Database API 开始、提交和回滚事务。 以下示例显示了在单个事务中执行的两个 SaveChanges() 操作以及 一个LINQ 查询。

并非所有数据库提供程序都支持事务。 调用事务 API 时,某些提供程序可能会引发异常或不执行任何操作。

规范参考

  1. 数据追踪参考规范: https://docs.microsoft.com/zh-cn/ef/core/querying/tracking
  2. 客户端求值参考规范:https://docs.microsoft.com/zh-cn/ef/core/querying/client-eval
  3. 异步查询参考规范:https://docs.microsoft.com/zh-cn/ef/core/querying/async
  4. 加载相关数据参考规范:https://docs.microsoft.com/zh-cn/ef/core/querying/related-data
  5. 事务使用参考规范:https://docs.microsoft.com/zh-cn/ef/core/saving/transactions

EntityFrameworkCore 开发实践问题及规范的更多相关文章

  1. Angular开发实践(一):环境准备及框架搭建

    引言 在工作中引入Angular框架将近一年了,在这一年中不断的踩坑和填坑,当然也学习和积累了很多的知识,包括MVVM框架.前后端分离.前端工程化.SPA优化等等.因此想通过Angular开发实践这系 ...

  2. 《疯狂前端开发讲义jQuery+Angular+Bootstrap前端开发实践》学习笔记

    <疯狂前端开发讲义jQuery+Angular+Bootstrap前端开发实践>学习笔记 二〇一九年二月十三日星期三2时28分54秒 前提:本书适合有初步HTML.CSS.JavaScri ...

  3. 前端开发工程师 - 05.产品前端架构 - 协作流程 & 接口设计 & 版本管理 & 技术选型 &开发实践

    05.产品前端架构 第1章--协作流程 WEB系统 角色定义 协作流程 职责说明 第2章--接口设计 概述 接口规范 规范应用 本地开发 第3章--版本管理 见 Java开发工程师(Web方向) - ...

  4. UniEAP V4 开发实践说明文档

    一.开发环境搭建 1. 前期准备 Java jdk1.6 ,Oralce数据库,plsql客户端,tomcat6.0,开发样例数据库脚本,unieap脚本,unieap工程,unieap worksh ...

  5. Git工程开发实践(四)——Git分支管理策略

    A successful Git branching model https://nvie.com/posts/a-successful-git-branching-model/ Git工程开发实践( ...

  6. 《JavaScript设计模式与开发实践》整理

    最近在研读一本书<JavaScript设计模式与开发实践>,进阶用的. 一.高阶函数 高阶函数是指至少满足下列条件之一的函数. 1. 函数可以作为参数被传递. 2. 函数可以作为返回值输出 ...

  7. Android游戏开发实践(1)之NDK与JNI开发03

    Android游戏开发实践(1)之NDK与JNI开发03 前面已经分享了两篇有关Android平台NDK与JNI开发相关的内容.以下列举前面两篇的链接地址,感兴趣的可以再回顾下.那么,这篇继续这个小专 ...

  8. TFS 2015 敏捷开发实践 – 在Kanban上运行一个Sprint

    前言:在 上一篇 TFS2015敏捷开发实践 中,我们给大家介绍了TFS2015中看板的基本使用和功能,这一篇中我们来看一个具体的场景,如何使用看板来运行一个sprint.Sprint是Scrum对迭 ...

  9. Android游戏开发实践(1)之NDK与JNI开发01

    Android游戏开发实践(1)之NDK与JNI开发01 NDK是Native Developement Kit的缩写,顾名思义,NDK是Google提供的一套原生Java代码与本地C/C++代码&q ...

随机推荐

  1. leetcode 反转链表部分节点

    反转从位置 m 到 n 的链表.请使用一趟扫描完成反转. 说明:1 ≤ m ≤ n ≤ 链表长度. 示例: 输入: 1->2->3->4->5->NULL, m = 2, ...

  2. springMVC 异常

    springMVC  异常 0.依赖(不只是本次案例所需) <?xml version="1.0" encoding="UTF-8"?> <p ...

  3. test for OCr

  4. 🧑🏻‍💻数据库简介及Mac平台环境搭建🧑🏻‍💻

    数据库 存储数据的演变过程 如果没有使用数据库,我们自己存放文件,数据格式是千差万别的,完全取决于我们自己,例如: """ # 张三 zhangsan|123|read ...

  5. kali系统安装google拼音

    1.设置多线程下载 /bin/bash -c "$(curl -sL https://git.io/vokNn)" 2.打开终端,输入下面的命令 apt-fast install ...

  6. 利用java从docx文档中提取文本内容

    利用java从docx文档中提取文本内容 使用Apache的第三方jar包,地址为https://poi.apache.org/ docx文档内容如图: 目录结构: 每个文件夹的名称为日期加上来源,例 ...

  7. Hexo博客框架攻略

    前言 前天无意在b站看到up主CodeSheep上传的博客搭建教程,引起了我这个有需求但苦于没学过什么博客框架的小白的兴趣.于是花了两天时间终于终于把自己的博客搭建好了,踩了无数的坑,走偏了无数的路, ...

  8. 关于UDP的检验和计算(附代码)

    关于UDP的检验和计算(附代码) 在下午的学习过程中https://www.cnblogs.com/roccoshi/p/13032356.html 有一张图讲述了UDP的校验方法, 如下: 老师只粗 ...

  9. 将反向传播讲解的深入透彻的神一样的文章(numpy实现人工神经网络)

    为了完成机器学习课的项目,规定不许调tensorflow,pytorch这些包.可是要手工实现一个可训练的神经网络是非常困难的一件事,难点无他,就在于反向传播的实现.这不,我在网上发现了这篇文章.怎么 ...

  10. laravel查询常用的方式含义.

    find($id) 传值并返回一个模型.如果不存在匹配的模型,则返回null.findOrFail($id) 传值并返回一个模型.如果不存在匹配的模型, 它会抛出异常.first() 返回在数据库中找 ...