C# 实体框架 - 优化/最佳实践

时间:2021-01-26 18:12:47

标签: c# mysql entity-framework .net-framework-4.8

我有一个实体框架数据库,其结构如下:

enter image description here

“Lots”是零件的命名集合

“零件”是单独的部分

“站”是零件的结果

“工具”是站的结果

“测量”是工具的结果

对于给定的批次,大约。 53,000 个零件在我的测试用例中有 6 个关联的行,总行数为 318,001(+ 1 很多)

我的问题是如何通过这种类型的数据关联来提高实体框架的性能。

删除批次(在 MySQL 中使用 CASCADE 删除)大约需要。 12 秒。 提取批次数据需要大约。 2分钟

我正在利用以下内容提取批次信息:

this.Context.Database.CommandTimeout = 2700;

Lot foundLot = EntitiesContext.Lots.Where(x => x.ID == lotID).FirstOrDefault();

this.Context.Database.CommandTimeout = 180;

我正在使用 Entity Framework Code First 中的迁移

 Database.SetInitializer(new MigrateDatabaseToLatestVersion<GenInspContext, Migrations.Configuration>());

当我创建我的表时,我将 cascadeDelete 设置为 true

.ForeignKey("dbo.Lots", t => t.LotID, cascadeDelete: true)

我更关心提取数据所需的时间,而不是删除数据的 12 秒。

1 个答案:

答案 0 :(得分:0)

开发人员在使用实体框架时遇到的第一个也是可以说是最大的性能障碍是延迟加载。这是确保应用程序正常工作而不是抛出 NullReferenceException 的一项很棒的功能,但如果您不考虑它,它绝对是性能杀手。

声明如下:

Lot foundLot = EntitiesContext.Lots.Where(x => x.ID == lotID).FirstOrDefault();

...看起来很无辜,它们完全无害提供您只需要来自 Lot 记录的信息,而不是任何其他相关信息实体的信息。这包括执行诸如序列化批次以发送到网络客户端之类的操作。

默认情况下,每当 EF 加载包含对其他实体的 virtual 引用的实体时,除非您明确禁用延迟加载代理,否则 EF 将创建将跟踪这些相关实体的实体的代理。如果引用不是预先加载的并且代码尝试访问它,则实体将检查它的 DbContext 是否仍然可用,并发出一个新查询来获取所接触的实体。

将此行为与序列化程序相结合,从性能的角度来看,这会导致各种地狱。例如,如果一个 Lot 有一个 Parts 集合,每个 Parts 都有一个 Stations 集合,我们加载 1 个 Lot,我们得到:

SELECT * FROM Lot WHERE LotId = 1;

够无辜了。但是,如果我们的序列化程序或代码触及 Parts 集合,则会运行以下查询:

SELECT * FROM Parts WHERE LotId = 1;

现在加载了 100 个零件......不过,没有什么可真正关心的。然而,对象模型嵌套得越多,它就会变得越糟糕。在序列化程序的情况下,它将开始迭代每个部分,这涉及到 Stations 集合。现在大便开始撞击风扇了:

SELECT * FROM Stations WHERE PartId = 1;
SELECT * FROM Stations WHERE PartId = 2;
SELECT * FROM Stations WHERE PartId = 3;
SELECT * FROM Stations WHERE PartId = 4;
SELECT * FROM Stations WHERE PartId = 5;
....
SELECT * FROM Stations WHERE PartId = 100;

这样做(而不是JOIN将零件放在工作站上)是因为迭代涉及每个零件的工作站集合。这会导致对 每个 部分的 Stations 查询。如果每个部分有1-5个工位。随着这些站被序列化,每个站的引用又会导致更多的查询,依此类推。您可以在应用程序运行时使用分析器观察 SQL 垃圾邮件的激增。

快速解决方法是利用预先加载。这会将初始查询中的 SELECT 垃圾邮件替换为 JOIN。这可以使加载数据比依赖延迟加载更快。与等待几分钟让 EF 组合并启动数万个查询相比,具有各种内部和外部联接的一个查询可能需要几秒钟的时间。但是,这里的考虑是您仍在从服务器加载潜在的垃圾数据。 EF 必须编写查询,将其发送到 DB 以执行,DB Server 需要为所有结果分配空间,然后通过线路将它们传输回应用服务器,然后应用服务器必须为这些结果分配内存结果图,可能将其序列化到客户端。这也会随着应用程序的发展和新关系添加到现有实体中而潜伏在您的应用程序中。在图中的某处添加了一个新关系,并且在您知道它之前,在完全未触及的区域(例如搜索)中出现了看似无关的性能问题,因为序列化程序现在正在拾取并加载那些未预先加载的实体。 (因为应用的这些区域不需要显示该信息。)

更好的解决方案是在读取要针对客户端计算或序列化到客户端的数据时利用投影,并保存加载实体及其相关详细信息(如有必要)以用于更新操作等。使用 Select 或 Automapper 的 ProjectTo 方法进行投影允许您编写查询以填充安全的 POCO 以进行序列化或匿名类型以检查和使用,从而导致远、远、更快的查询和更小的有效负载数据通过线路发送。这样,查询只加载它需要的信息,并且根据新关系对数据模型的更改永远不会污染现有查询。那些现有的查询可能不会注意到新表,只有在实际需要考虑新数据时才会更改。随着时间的推移发生变化的地雷效应是我永远向网络客户端发送实体的最大论据之一。 (除了浪费时间/有效负载发送客户端不需要查看的数据,以及如果您接受来自客户端的实体返回的潜在安全问题。)