《Entity Framework Core in Action》--- 读书随记(8)
Part 2 Entity Framework in depth
《Entity Framework Core in Action》
-- SECOND EDITIONAuthor: JON P SMITH
如果需要电子书的小伙伴,可以留下邮箱,看到了会发送的
11 Going deeper into the DbContext
11.1 Overview of the DbContext class’s properties
本章重点介绍从 EFCore 的 DbContext 类继承的公共属性的方法和数据。这些属性提供的信息或方法使您能够更好地管理实体类及其到数据库的映射:
11.2 Understanding how EF Core tracks changes
EF Core 使用一个名为 State 的属性,该属性附加到所有被追踪的实体上。State 属性包含有关在调用应用程序的 DbContext 方法 SaveChanges 时希望对该实体发生什么的信息
State 属性是 EntityState 类型的枚举,通常由 EF Core 内部的变更跟踪特性设置,在本节中,您将探索可以设置 State 的所有方式
11.3 Looking at commands that change an entity’s State
11.3.1 The Add command: Inserting a new row into the database
Add/AddRange 方法通过将给定实体的 State 设置为 Add 来在数据库中创建新实体。第3.2节介绍 Add 方法,第6.2.2节详细介绍了如何逐步添加具有关系的实体类。总结一下
- 实体的状态设置为“ Added ”
- Add 方法查看与添加的实体链接的所有实体
- 如果当前不是tracked关系,则跟踪该关系,并将其状态设置为“Added”
- 如果tracked关系,则使用其当前 State,除非需要更改/设置外键,在这种情况下,其 State 被设置为 Modified
11.3.2 The Remove method: Deleting a row from the database
如果被移除的实体具有任何加载/跟踪的关系,则每个关系实体的 State 值将为下列值之一
- State == Deleted -- 典型的依赖关系
- State == Modifie -- 外键可为空的可选依赖关系
- State == Unchanged -- 删除链接到主体类的依赖实体类的结果。删除依赖实体类时,主类键/外键中没有任何更改
11.3.3 Modifying an entity class by changing the data in that entity class
默认情况下,当调用 SaveChanges/SaveChangesAsync 时,EF Code 执行一个名为 ChangeTracker.DetectChanges 的方法,该方法将当前实体的数据与该实体的跟踪快照进行比较。如果任何属性、 backing fields 属性或阴影属性不同,实体的状态设置为 Modified,属性、 backing fields 属性或阴影属性设置为 IsModified
11.3.4 Modifying an entity class by calling the Update method
Update 方法告诉 EF Core 更新该实体中的所有属性/列,方法是将给定实体的 State 设置为 Modified ,并将所有非关系属性(包括任何外键)上的 IsModified 属性设置为 true。因此,将更新数据库中的行的所有列
如果使用 Update 调用的实体类型具有加载关系,Update 方法将递归地查看每个相关实体类并设置其状态。在相关实体类上设置 State 的规则取决于关系实体的主键是否由数据库生成并设置
- 数据库生成的键,而不是默认值ーー在这种情况下,EF Core 将假定关系实体已经在数据库中,如果需要设置外键,则将 State 设置为 Modified ; 否则,State 将 Unchanged
- 不是数据库生成的键,或者键是默认值ーー在这种情况下,EF Core 将假定关系实体是新的,并将其状态设置为 Add
11.3.5 The Attach method: Start tracking an existing untracked entity class
如果您有一个具有现有有效数据的实体类并希望跟踪它,那么 Attach 和 AttachRange 方法非常有用。附加实体之后,将跟踪该实体,并且 EF Core 假定其内容与当前数据库状态匹配
11.3.6 Setting the State of an entity directly
另一种设置实体状态的方法是手动将其设置为您想要的任何状态。当一个实体有许多关系时,这种对实体状态的直接设置非常有用,您需要明确地决定希望每个关系具有哪种状态
context.Entry(myEntity).State = EntityState.Added;
// or
var entity = new MyEntity();
context.Entry(entity).Property("MyString").IsModified = true;
11.3.7 TrackGraph: Handling disconnected updates with relationships
如果您有一个具有关系的未跟踪实体,并且需要为每个实体设置正确的状态,则 TrackGraph 方法非常有用。TrackGraph 方法将遍历实体中的所有关系链接,调用您为它找到的每个实体提供的操作。如果您有一组来自断开连接的情况(比如,通过某种形式的序列化)的链接实体,并且您只想更改已加载的部分数据,那么此方法非常有用
TrackGraph 遍历作为其第一个参数提供的实体以及通过遍历其导航属性可到达的任何实体。遍历是递归的,因此任何发现的实体的导航属性也将被扫描。作为第二个参数提供的 Action 方法针对每个发现的未跟踪(State = = Detached)实体进行调用,并且可以设置应该跟踪每个实体的 State。如果没有设置被访问实体的状态,那么该实体将保持在分离状态(也就是说,该实体没有被 EF Core 跟踪)。此外,TrackGraph 将忽略它访问的任何当前被跟踪的实体
11.4 SaveChanges and its use of ChangeTracker.DetectChanges
11.4.2 What to do if ChangeTracker.DetectChanges is taking too long
在某些应用程序中,可能加载了大量被跟踪的实体。例如,在执行数学建模或构建人工智能应用程序时,在内存中保存大量数据可能是达到所需性能水平的唯一方法
问题是,如果您有大量被跟踪的实体实例,并且/或者您的实体中有大量的数据。在这种情况下,调用 SaveChanges/SaveChangesAsync 可能会变慢。如果要保存大量数据,那么速度缓慢很可能是由数据库访问造成的。但是如果只保存少量数据,那么任何减速都可能是由于 ChangeTracker.DetectChanges 将每个实体类实例与其匹配的跟踪快照进行比较所花费的时间
EF Core 提供了一些替代 ChangeTracker.DetectChanges 的方法,可以用另一种方法来检测更改。这些特性的工作原理是检测实体类中数据的单独更新,从而剔除未更改数据的任何比较。例如,用普通的 ChangeTracker.DetectChanges 方法保存100,000个没有更改的微型实体需要350毫秒,而通过类检测更改的方法需要2毫秒就能检测到相同的数据
有四种方法可以替换 ChangeTracker.DetectChanges; 每种方法都有不同的特性和要实现的工作级别
总的来说,代理更改跟踪特性更容易编码,但是需要更改所有实体类以使用代理更改跟踪。但是,如果您发现现有应用程序中存在 SaveChanges 性能问题,那么更改所有实体类的工作量可能太大。出于这个原因,我关注第一种方法,INotifyPropertyChanged,它很容易添加到一些有问题的实体类中,而最后一种方法,代理更改/更改跟踪,它更容易,但需要您在整个应用程序中使用它
FIRST APPROACH: INOTIFYPROPERTYCHANGED
下面显示了一个名为 NotifyEntity 的实体类,它继承了上图所示的 NotificationEntity。每当非集合属性发生更改时,必须调用 SetWithNotify 方法。对于集合,当导航集合属性发生更改时,必须使用 ObserverableCollection 来引发事件
在定义了使用 INotifyPropertyChanged 接口的实体类之后,必须将该实体类的跟踪策略配置为 ChangedNotification 。这个配置告诉 EF Core 不要通过 ChangeTracker.DetectChanges 检测更改,因为它将通过 INotifyPropertyChanged 事件得到任何更改的通知
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder
.Entity<NotifyEntity>()
.HasChangeTrackingStrategy(ChangeTrackingStrategy.ChangedNotifications);
}
LAST APPROACH: PROXY CHANGE TRACKING
这些变更跟踪事件被添加到带有 virtual 属性的延迟加载代理方法中
要使用这种方法,您需要做五件事:
- 将所有实体类更改为具有 virtual 属性
- 对导航集合属性使用Observable集合类型
- 将创建实体类的新实例的代码更改为使用 CreateProxy < TEntity > 方法
- NuGet library Microsoft.EntityFrameworkCore.Proxies
- 在生成应用程序的 DbContext 选项时添加 UseChangeTrackingProxy 方法
var optionsBuilder = new DbContextOptionsBuilder<EfCoreContext>();
optionsBuilder
.UseChangeTrackingProxies()
.UseSqlServer(connection);
var options = optionsBuilder.Options;
using (var context = new EfCoreContext(options))
11.4.3 Using the entities’ State within the SaveChanges method
现在,您将使用 SaveChanges/SaveChangesAsync 中的 State 数据来做一些有趣的事情。以下是检测数据库中将要更改的内容的一些可能用途
- 自动向实体添加额外信息ーー例如,添加或更新实体的时间
- 每次更改特定实体类型时都将历史审计跟踪写入数据库
- 添加安全检查以查看是否允许当前用户更新该特定实体类型
下面提供了一个可以添加到任何实体类的接口。这定义了在添加或更新实体时需要填充的属性,以及可用于将属性设置为正确值的方法
11.4.4 Catching entity class’s State changes via events
EF Core 2.1为 EF Core 增加了两个事件: ChangeTracker.Tracked,当一个实体首次被跟踪时触发; ChangeTracker.StateChanged,当一个已经被跟踪的实体的状态发生变化时触发。这个特性提供了类似于调用 ChangeTracker.Entry ()的效果,但是在某些事情发生变化时产生一个事件。ChangeTracker 事件对于记录更改或在特定实体类型的状态更改时触发操作等特性非常有用
11.4.5 Triggering events when SaveChanges/SaveChangesAsync is called
EF Core 5引入了 SavingChanges、 SavedChanges 和 SaveChangesFail 事件,这些事件分别在数据保存到数据库之前、数据成功保存到数据库之后以及保存到数据库失败时调用。这些事件允许您利用 SaveChanges 和 SaveChangesAsync 方法中正在发生的事情。您可以使用这些事件来记录写入数据库的内容,或者在 SaveChanges 或 SaveChangesAsync 内部出现某个异常时通知某人
11.5 Using SQL commands in an EF Core application
EF Core 的 SQL 命令旨在检测 SQL 注入攻击ーー在这种攻击中,恶意用户用一些 SQL 命令替换主键值,这些 SQL 命令从数据库中提取额外的数据。EF Core 提供了两种类型的 SQL 命令
- 以Raw结尾的方法,例如FromSqlRaw。在这些命令中,您可以提供单独的参数,并检查这些参数
- 以插值结尾的方法,例如 FromSqlInterpolated。提供给这些方法的字符串参数使用了 C # 6的字符串插值和字符串中的参数。EF Core 可以检查内插字符串类型中的每个参数
如果在命令之外构建一个内插字符串ーー比如 var badSQL = $“ SELECT... ... WHERE BookId = { myKey }”ーー然后在 FromSqlRaw (badSQL)等命令中使用它,EF Core 就无法检查 SQL 注入攻击。应该将 FromSqlRaw 与参数一起使用,或者将 FromSqlInterpolated 与字符串插值中嵌入的参数一起使用
- FromSqlRaw/FromSqlInterpolated,查询语句
- ExecuteSqlRaw/ExecuteSqlInterpolated,非查询语句
- AsSqlQuery Fluent API,映射一个entity到SQL 查询
- Reload/ReloadAsync,用于刷新已被 ExecuteSql... 方法更改的 EFCore 加载的实体
- GetDbConnection,提供用于直接访问数据库的低级数据库访问库
11.5.1 FromSqlRaw/FromSqlInterpolated: Using SQL in an EF Core query
有几条关于 SQL 查询的规则:
- SQL 查询必须返回实体类型的所有属性的数据(但是有一种方法可以绕过这条规则)
- 结果集中的列名必须与属性映射到的列名匹配
- SQL 查询不能包含相关数据,但是可以添加 Include 方法来加载相关的导航属性
您可以在 SQL 命令之后添加其他 EF Core 命令,例如 Include、 Where 和 OrderBy
11.5.2 ExecuteSqlRaw/ExecuteSqlInterpolated: Executing a nonquery command
11.5.3 AsSqlQuery Fluent API method: Mapping entity classes to queries
EF Core 5提供了一种通过 AsSqlQuery Fluent API 方法将实体类映射到 SQL 查询的方法。这个特性允许您将 SQL 代码隐藏在应用程序的 DbContext 配置中,开发人员可以在查询中使用这个 DbSet < T > 属性,就好像它是映射到实体的普通实体类一样。当然,它是一个只读实体类,但是如果需要读/写版本,请参阅下面的说明。
11.5.4 Reload: Used after ExecuteSql commands
如果有一个实体加载(跟踪) ,并且使用 ExecuteSqlRaw/ExecuteSqlInterpolated 方法更改数据库上的数据,则所跟踪的实体已过期。这种情况可能会在以后引起问题,因为 EF Core 不知道值已经更改。为了解决这个问题,EF Core 有一个名为 Relload/ReloadAsync 的方法,它通过重读数据库来更新实体
var entity = context.Books.Single(x => x.Title == "Quantum Networking");
var uniqueString = Guid.NewGuid().ToString();
context.Database.ExecuteSqlRaw(
"UPDATE Books " +
"SET Description = {0} " +
"WHERE BookId = {1}",
uniqueString, entity.BookId);
context.Entry(entity).Reload();
11.5.5 GetDbConnection: Running your own SQL commands
当 EF Core 不能提供您想要的查询特性时,您需要回到另一个可以提供查询特性的数据库访问方法。一些低级的数据库库需要编写更多的代码,但是提供了对数据库的更直接的访问,因此您几乎可以做任何需要做的事情。通常,这些底层数据库库是特定于数据库服务器的
在本节中,您将使用一个名为 Dapper 的 NuGet 库。Dapper 是 NET 的一个简单的对象映射器,有时也被称为 microORM。Dapper 简单但快速。它使用 ADO.NET 库访问数据库,并向类属性添加列的自动复制
下面使用 Dapper 将特定的列读入一个名为 RawsqlDto 的非实体类,该类具有名为 BookId、 Title 和 AverageVotes 的属性,因此您只能加载所需的列
11.7 Dynamically changing the DbContext’s connection string
EFCore5使得在应用程序的 DbContext 实例中更改连接字符串变得更加容易。现在它提供了一个名为 SetConnectionString 的方法,该方法允许您随时更改连接字符串,以便您可以随时更改正在访问的数据库
我通常使用此特性根据登录的用户、用户所在位置等选择不同的数据库。这个过程被称为数据库分片,它提供了更好的性能,因为用户的数据分布在多个数据库上。它还可以通过将一组用户的所有数据放在一个数据库中来增加一些安全性
EFCore5做了另一个重要更改: 当您首次创建应用程序的 DbContext 时,连接字符串可以为 null。(在 EFCore5之前,连接字符串不能为空。)在需要访问数据库之前,连接字符串可以为空。这个特性很有用,因为在启动时没有租户信息,所以连接字符串将为空。然而,在 EF Core 5更改后,您的 EF Core 配置代码可以运行而不需要连接字符串
11.8 Handling database connection problems
对于关系数据库服务器,尤其是云服务器,数据库访问可能会失败,因为连接超时或出现某些暂时性错误。EF Core 有一个执行策略特性,允许您定义超时发生时应该发生什么、允许多少次超时等等。提供执行策略可以降低应用程序由于连接问题或暂时性内部错误而失败的可能性
SQLServer 数据库提供程序包括专门针对 SQLServer (包括 SQLAzure)的执行策略。它知道可以重试的异常类型,并且对于最大重试、重试之间的延迟等具有合理的默认值
optionsBuilder.UseSqlServer(connection,
option => option.EnableRetryOnFailure());
11.8.1 Handling database transactions with EF Core’s execution strategy
由于执行策略的工作方式,您需要对使用数据库事务的任何代码进行调整,在该数据库事务中,您可以在一个独立的事务中对 SaveChanges 进行多次调用。执行策略的工作原理是,如果发生暂时性故障,则回滚整个事务,然后重播事务中的每个操作; 每个查询和对 SaveChanges 的每个调用都作为一个单元重试。对于要重试的事务中的所有操作,执行策略必须控制事务代码
下面显示了添加的 SQLServerEnableRetryOnFalse 执行策略以及对事务使用的执行策略(粗体)。事务代码的编写方式是,如果需要重试,则从一开始就再次运行整个事务。
11.8.2 Altering or writing your own execution strategy
在某些情况下,您可能需要更改数据库的执行策略。如果数据库提供程序(如 SQLServer)有现有的执行策略,则可以更改一些选项,如重试次数或要重试的 SQL 错误
如果希望编写自己的执行策略,则需要实现一个继承接口 IExectionStrategy 的类。我建议您将 EF Core 内部类 SqlServerExectionStrategy 作为模板
在编写了自己的执行策略类之后,可以通过在选项中使用 ExecuteStrategy 方法将其配置到数据库中
var connection = this.GetUniqueDatabaseConnectionString();
var optionsBuilder = new DbContextOptionsBuilder<Chapter09DbContext>();
optionsBuilder.UseSqlServer(connection,
options => options.ExecutionStrategy(
p => new MyExecutionStrategy()));
using (var context = new Chapter09DbContext(optionsBuilder.Options))
{
... etc.
}
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 微软正式发布.NET 10 Preview 1:开启下一代开发框架新篇章
· 没有源码,如何修改代码逻辑?
· PowerShell开发游戏 · 打蜜蜂
· 在鹅厂做java开发是什么体验
· WPF到Web的无缝过渡:英雄联盟客户端的OpenSilver迁移实战