使用EF时MySQL抛出DataReader异常的原因及解决方案
你遇到的这个问题其实挺常见的,毕竟SQL Server和MySQL的EF驱动在细节实现上有差异。之前在SQL Server上正常的代码到MySQL出问题,核心原因是MySQL的EF驱动默认没有启用多活动结果集(MARS),而SQL Server要么默认支持,要么你之前的连接字符串已经配置了这个选项。
先看看你遇到的具体错误:
MySql.Data.MySqlClient.MySqlException: There is already an open DataReader associated with this Connection which must be closed first.
你的代码逻辑是遍历context.Devices(这时候EF会打开一个DataReader来逐条读取设备数据),然后在循环里又去访问device.Monitors(延迟加载,会尝试用同一个连接打开另一个DataReader)——MySQL驱动默认不允许同一个连接同时有两个活跃的DataReader,所以就抛出了这个异常。
接下来给你几个可行的解决/规避方案,按需选择:
方案1:在连接字符串中启用MARS
这是最省心的方案,适合你项目里大量使用延迟加载的场景。只需要在你的MySQL连接字符串里加上MultipleActiveResultSets=True即可,比如:
server=你的服务器地址;database=你的库名;uid=用户名;pwd=密码;MultipleActiveResultSets=True;
不过要注意,你用的MySqlClient 6.9.11版本是支持MARS的,不用太担心兼容性问题。
方案2:提前加载所有设备到内存
把context.Devices先转换成内存集合,这样遍历的是内存里的数据,不会保持DataReader打开。修改后的代码如下:
using (var context = new ApplicationDbContext()) { // 先一次性把所有设备加载到内存,关闭DataReader var devices = context.Devices.ToList(); foreach (var device in devices) { var monitors = device.Monitors.Where(m => m.Enabled == true).ToList(); if (monitors.Count > 0) { // 处理采样数据的逻辑 } } }
方案3:显式加载关联的Monitor数据
在循环里先显式加载当前设备的Monitor,再进行筛选,这样也能避免同时打开多个DataReader:
using (var context = new ApplicationDbContext()) { foreach (var device in context.Devices) { // 显式加载当前设备的所有Monitor到内存 context.Entry(device).Collection(d => d.Monitors).Load(); var monitors = device.Monitors.Where(m => m.Enabled == true).ToList(); if (monitors.Count > 0) { // 处理采样数据的逻辑 } } }
方案4:一次性查询所有需要的数据
用LINQ的Include结合筛选,一次性把带启用状态Monitor的设备都查出来,减少数据库查询次数,也从根源避免DataReader冲突:
using (var context = new ApplicationDbContext()) { var deviceWithEnabledMonitors = context.Devices .Include(d => d.Monitors.Where(m => m.Enabled == true)) .ToList(); foreach (var device in deviceWithEnabledMonitors) { var monitors = device.Monitors; // 已经在内存中,直接使用 if (monitors.Count > 0) { // 处理采样数据的逻辑 } } }
至于为什么SQL Server上没问题,是因为SQL Server的ADO.NET驱动默认支持MARS,或者你之前的连接字符串已经包含了MultipleActiveResultSets=True,所以同一个连接可以同时处理多个DataReader请求。而MySQL驱动默认关闭了这个功能,才会出现这种差异。
这些方案都能解决你的问题,选一个最适合你项目现有代码结构的就行,不用太担心这会成为使用MySQL的障碍,只是需要适配一下驱动的特性而已。
内容的提问来源于stack exchange,提问作者Psyfun

