EF第一次加载较慢的原因以及解决方法

EF第一次加载较慢的原因以及解决方法(汇)
孤的卡布奇诺

C#中 EF 性能优化

https://www.cnblogs.com/chenwolong/p/7531955.html

EF使用AsNoTracking(),无跟踪查询技术(查询出来的数据不可以修改,如果你做了修改,你会发现修改并不成功)

using (var context = new DBContext())
            {
                var blogs = context.Student.AsNoTracking().ToList();    //查询结果是数据库实体
            }
using (var context = new DBContext()){
    context.ChangeTracker.QueryTrackingBehavior = QueryTrackingBehavior.NoTracking;
    var stu= context.Student.ToList();
}

 

EF 去掉缓存 获取最新数据库内容

weixin_34148508

用AsNoTracking()去掉EF 缓存,获取数据库最新内容。

用法:163941_sChm_2494395.png

 

转载于:https://my.oschina.net/u/2494395/blog/614668


前言:
    这个问题没有遇到过,但是面试当中很可能会被问到,当然也不主要是为了应对面试,学到知识才是王道

为什么会慢:

  • 在应用程序中定义的每个上下文,其首次使用时,JUST-INTIME编译器:Entity Framework都会根据数据库中的信息在内存中生成一个映射视图(mapping views),这个操作非常耗时。
  • 定义的每一个上下文都会受此困扰
  • Code First第一次启动会对比程序中的Model与数据库表(database initializer ),生成Model与数据库的映射视图
  • EF从6开始安装.net Framework默认不会安装EF。因此EF程序集就没有生成本地镜像,这样每次程序启动,EF的代码都会通过just-in-time (JIT) compiler把MSIL中间代码编译成本机能识别的本地代码(存在程序运行的进程的内存中),当程序进程被终止它将回收(例如:iis程序池回收,程序池默认是按需触发运行的,没人访问它就不启动了)。由于EF框架还是比较大的,EF6文件大小到4-5M了,所以每次启动都要重写编译本地代码有比较明显的性能影响。
  • MVC的程序第一次访问比较慢的的问题由于第一次是要处理视图文件.cshtml(生成为.cs文件)、加载引用的dll程序文件和初始化程序池等等

解决一EF暖机操作
在应用程序初始化时一次性触发所有的DbContext进行mapping views的生成操作——调用StorageMappingItemCollection的GenerateViews()方法

using (var dbcontext = new CnblogsDbContext()){
    var objectContext = ((IObjectContextAdapter)dbcontext).ObjectContext;
    var mappingCollection = (StorageMappingItemCollection)objectContext.MetadataWorkspace.GetItemCollection(DataSpace.CSSpace);
    mappingCollection.GenerateViews(new List<EdmSchemaError>());
}

对于ASP.NET应用程序 ,可以将上面的代码放在Application_Start或者PreApplicationStartMethod中执行。

来,给Entity Framework热热身

 

多种方案,挺详细的:优化EF Code First第一次请求速度

解决二application Initialization
    iis8内置功能,安装Application Initialization Module for IIS 7.5,
http://pan.baidu.com/s/1c091WxM

用Ngen安装生成EF的本地镜像
   Ngen创建本机映像native-code并将其安装到本机映像缓存中,可通过内存映射被多个进程共享

本机影像生成器(Ngen.exe)工具使用实践
NGen是个什么东西?

禁用第一次ef查询对表__MigrationHistory的问题
使用了ef的Code first会在第一次ef查询的时候会对__MigrationHistory访问,是为了检查数据库和model是否匹配,以保证ef能正常运行。通过监测会先执行下面的sql:

SELECT 
[GroupBy1].[A1] AS [C1]
FROM ( SELECT 
    COUNT(1) AS [A1]
    FROM [dbo].[__MigrationHistory] AS [Extent1]
)  AS [GroupBy1]
GO

SELECT TOP (1) 
[Extent1].[Id] AS [Id], 
[Extent1].[ModelHash] AS [ModelHash]
FROM [dbo].[EdmMetadata] AS [Extent1]
ORDER BY [Extent1].[Id] DESC
GO

这段sql语句其实中只是在开发的时候有用,发布到生产环境,可以把这个给禁用了以提高性能。解决办法:
Application_Start加代码

Database.SetInitializer<上下文>(null);
 

后语:
    上面的东西是汇集四海八荒的精髓而成的,不过正确与否以目前本菜的水平和还无法评价,感觉很是不错,谢谢大牛们的分享(尊重原著),感谢大家的阅读o(^▽^)o

   再推荐一篇:关于EF第一次加载慢或过一段时间不访问时再次访问加载慢问题的总结
————————————————
版权声明:本文为CSDN博主「孤的卡布奇诺」的原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/ma15732625261/article/details/72541184/

<think>好的,我现在需要帮用户解决LINQ中Select加子查询的性能问题。用户提到处理100条数据,每条数据有两个子查询,耗时6秒,即使在数据量少的情况下。这显然性能有问题,得找出优化方法。 首先,用户可能使用的是LINQ to Objects或者LINQ to Entities,如果是后者,可能需要考虑生成的SQL语句是否高效。但用户提到数据量少,可能是在内存中处理,所以优化重点可能在减少重复查询和延迟加载上。 子查询在Select里会导致每个元素都执行一次查询,这样如果有100条数据,每个数据两个子查询,就是200次查询。如果每次查询都访问数据库,那延迟会很高,即使数据量少,多次往返也会累积时间。 所以第一个优化思路应该是把子查询的数据提前加载到内存,避免N+1查询问题。比如使用Include或者显式加载相关数据,把需要的子查询结果一次性取出来,然后在内存中进行处理。 其次,可以考虑将子查询转换为Join操作,通过连接表来一次性获取所需数据,减少查询次数。例如,使用GroupJoin来处理一对多的关系,这样可以在一个查询中获取所有相关数据,而不是逐个查询。 另外,检查是否使用了延迟执行(Deferred Execution),导致每次访问时都重新执行查询。使用ToList()或ToArray()将结果物化,可以避免重复查询。 还有,匿名类的使用可能会影响性能,尤其是在嵌套查询时。改用元组或者具体类来存储中间结果,可能会提升效率。 用户可能没有意识到在循环中执行查询的开销,所以需要引导他们将子查询提前批量处理。例如,先收集所有需要的主键,然后一次性查询所有相关数据,再在内存中匹配,这样可以减少数据库的访问次数。 此外,分析生成的SQL语句也是很重要的。如果使用EF Core,可以通过日志查看实际执行的SQL,检查是否有不必要的复杂查询或索引缺失的问题。添加合适的索引可以大幅提升查询速度。 还有可能的问题是查询中使用了不当的操作,比如在子查询里用了AsEnumerable()或者ToList(),导致从数据库拉取大量数据到内存,再进行过滤,这反而更慢。应该保持查询在数据库端执行,直到最后一步才物化结果。 最后,如果所有优化都尝试过,问题仍未解决,可能需要考虑使用Dapper等更底层的ORM工具,或者对关键部分进行手写SQL,以进一步优化性能。</think>以下是针对 LINQ `Select` + 子查询性能问题的优化策略,结合代码示例和原理分析: --- ### **问题定位核心原因** 当在 `Select` 中嵌套子查询时,**每条数据都会触发独立的子查询**(即 N+1 问题)。即使数据量小,频繁的查询操作(尤其是涉及数据库时)会导致性能急剧下降。 --- ### **优化方案及代码示例** #### **1. 预先加载所有子查询数据(消除N+1查询)** **原理**:通过 `Join` 或 `GroupJoin` **一次性加载所有关联数据**,避免循环中逐条查询。 ```csharp // 假设主数据是 orders,需要查询每个订单的客户信息和订单详情 var orderIds = orders.Select(o => o.Id).ToList(); // 一次性加载所有关联数据(替代子查询) var details = dbContext.OrderDetails .Where(d => orderIds.Contains(d.OrderId)) .GroupBy(d => d.OrderId) .ToDictionary(g => g.Key, g => g.ToList()); var customers = dbContext.Customers .Where(c => orderIds.Contains(c.OrderId)) .ToLookup(c => c.OrderId); // 内存中组合数据(零额外查询) var result = orders.Select(o => new { Order = o, Details = details.TryGetValue(o.Id, out var d) ? d : new List<OrderDetail>(), Customer = customers[o.Id].FirstOrDefault() }).ToList(); ``` #### **2. 使用 `SelectMany` 扁平化查询(替代嵌套查询)** **原理**:将嵌套结构转换为**平铺结构**,利用 SQL 的 JOIN 特性优化。 ```csharp // 低效写法(每条数据触发子查询) var badQuery = orders.Select(o => new { Order = o, Details = dbContext.OrderDetails.Where(d => d.OrderId == o.Id).ToList() }); // 优化后(单次查询完成) var goodQuery = dbContext.Orders .SelectMany(o => dbContext.OrderDetails.Where(d => d.OrderId == o.Id), (o, d) => new { Order = o, Detail = d }) .GroupBy(x => x.Order) .Select(g => new { Order = g.Key, Details = g.Select(x => x.Detail).ToList() }); ``` #### **3. 强制立即执行查询(避免重复解析)** **原理**:通过 `ToList()`/`ToArray()` **提前物化中间结果**,减少重复计算。 ```csharp // 低效:多次执行子查询 var unoptimized = orders.Select(o => new { Total = dbContext.Payments.Where(p => p.OrderId == o.Id).Sum(p => p.Amount) }); // 优化:一次性加载支付数据 var payments = dbContext.Payments .Where(p => orders.Select(o => o.Id).Contains(p.OrderId)) .GroupBy(p => p.OrderId) .ToDictionary(g => g.Key, g => g.Sum(p => p.Amount)); var optimized = orders.Select(o => new { Total = payments.TryGetValue(o.Id, out var sum) ? sum : 0 }); ``` #### **4. 数据库端优化(针对EF Core)** ```csharp // 使用 AsNoTracking 减少开销 var orders = dbContext.Orders.AsNoTracking().Where(...).ToList(); // 确保 SQL 有索引支持 // 检查 OrderId 是否在 OrderDetails 表中建立索引 ``` --- ### **性能对比场景** | 方法 | 查询次数 (100条数据) | 耗时估算 | |---------------------|----------------------|----------| | 原始嵌套子查询 | 100 * 2 = 200 | 6s | | 预加载+内存处理 | 2 | 0.2s | | SelectMany 优化 | 1 | 0.1s | --- ### **进阶技巧** 1. **表达式缓存** 对复杂查询条件使用 `CompiledQuery`(EF6)或局部函数(EF Core): ```csharp private static readonly Func<MyDbContext, int, IEnumerable<Detail>> _getDetails = EF.CompileQuery((MyDbContext db, int orderId) => db.OrderDetails.Where(d => d.OrderId == orderId)); ``` 2. **批量操作扩展** 使用 [EntityFramework Plus](https://entityframework-plus.net/) 的 `Future` 方法合并查询: ```csharp var q1 = db.Orders.Where(...).Future(); var q2 = db.Details.Where(...).Future(); var data = q1.ToList(); // 单次执行合并 q1 和 q2 ``` --- ### **排查工具** 1. 使用 **EF Core 的 `LogTo` 方法** 输出实际生成的 SQL 2. 通过 **SQL Server Profiler** 监测查询频率 3. 使用 **Benchmark.NET** 对比不同方法的性能差异 --- **最终建议**:优先采用预加载+内存计算的模式,其次考虑数据库层面的索引优化。如果数据规模继续增大,可引入缓存机制(如 Redis 缓存常用子查询结果)。
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值