我正在将一些旧版 Linq-to-SQL 代码/查询迁移到 .NET 7/EF Core。一些以前在 L2S 中工作的代码现在抛出异常:
已经有一个打开的 DataReader 与此连接关联,必须先将其关闭。
我进行了深入研究,看到了启用 MARS 的建议(但首先要认真考虑),并尝试了一些
.Include
的语句,但没有帮助。
我有一个直接的 Profile(父)-> HistoryData(子)表关系。其中
HistoryData.hispKey
是 Profile.pKey
的外键。
CREATE TABLE [dbo].[Group]
(
[gKey] [int] IDENTITY(1,1) NOT NULL,
[gName] [nvarchar](255) NOT NULL,
....
)
CREATE TABLE [dbo].[Profile]
(
[pKey] [int] IDENTITY(1,1) NOT NULL,
[pgKey] [int] NOT NULL,
[pAuthID] [nvarchar](255) NOT NULL,
[pDateUpdated] [datetime2](7) NOT NULL,
[pDateCreated] [datetime2](7) NOT NULL,
[pUpdatedBy] [varchar](255) NOT NULL,
[pCreatedBy] [varchar](255) NOT NULL
[pProfile] [ntext] NOT NULL,
[pProfileXml] [xml] NOT NULL
)
CREATE TABLE [dbo].[HistoryData]
(
[hisKey] [int] IDENTITY(1,1) NOT NULL,
[hisgKey] [int] NOT NULL,
[hispKey] [int] NOT NULL,
[hisType] [varchar](50) NOT NULL,
[hisIndex] [varchar](255) NOT NULL,
[hisDateUpdated] [datetime2](7) NOT NULL,
[hisDateCreated] [datetime2](7) NOT NULL,
[hisUpdatedBy] [varchar](255) NOT NULL,
[hisCreatedBy] [varchar](255) NOT NULL,
[hisData] [ntext] NOT NULL,
[hisDataXml] [xml] NOT NULL
)
然后,我有以下与 Linq-to-SQL 中的投影语句相同的投影语句(显然除了
DbContext
与 DataContext
的使用):
var profiles = dataContexts.xDS.Profiles
.Where(p => p.GroupKey == gKey)
.Where(p => options.AuthIdsToExport.Length == 0 || options.AuthIdsToExport.Contains(p.AuthID))
.OrderBy(p => p.AuthID)
.Select(p => new XmlProfileRow {
AuthId = p.AuthID,
Data = p.Data,
DataXml = p.ProfileXml,
DateCreated = p.DateCreated,
DateUpdated = p.DateUpdated
});
var historyDatas = dataContexts.xDS.HistoryDatas
// .Include( h => h.Profile ) // This did not help
.Where(h => h.GroupKey == gKey )
.Where(h => options.AuthIdsToExport.Length == 0 || options.AuthIdsToExport.Contains(h.Profile!.AuthID))
.OrderBy(h => h.Profile!.AuthID)
.ThenBy(h => h.Type)
.ThenBy(h => h.Index)
.Select(h => new XmlHistoryRow {
AuthId = h.Profile!.AuthID,
DateCreated = h.DateCreated,
DateUpdated = h.DateUpdated,
Type = h.Type,
Index = h.Index,
Data = h.Data,
DataXml = h.DataXml
});
使用这两个查询最初是通过
IEnumerator
对象编写的,我不确定 EF Core 的“拆分查询”是否有帮助,或者类似 Dappers 的“多重查询”语句支持,但我绝对不能拥有所有将数据加载到内存中,我需要流式传输“配置文件”的结果,并为每一行流式传输当前“相关”行的“历史数据”的结果。
目的是不必为每个配置文件的
HistoryData
行发出新查询。
演示问题的简化代码是:
var profileEnumerator = profiles.GetEnumerator();
var historyEnumerator = historyDatas.GetEnumerator();
var currentHistory = historyEnumerator.MoveNext();
var currentProfile= profileEnumerator.MoveNext();
最终调用,抛出异常,调用堆栈为:
在 Microsoft.Data.SqlClient.SqlInternalConnectionTds.ValidateConnectionForExecute(SqlCommand 命令)
在 Microsoft.Data.SqlClient.SqlCommand.ValidateCommand(布尔 isAsync,字符串方法)
在 Microsoft.Data.SqlClient.SqlCommand.RunExecuteReader(CommandBehavior cmdBehavior、RunBehavior runBehavior、布尔 returnStream、TaskCompletionSource'1 完成、Int32 超时、任务&任务、布尔&usedCache、布尔 asyncWrite、布尔 inRetry、字符串方法)
在 Microsoft.Data.SqlClient.SqlCommand.ExecuteReader(CommandBehavior 行为)
在 Microsoft.Data.SqlClient.SqlCommand.ExecuteDbDataReader(CommandBehavior 行为)
在 Microsoft.EntityFrameworkCore.Storage.RelationalCommand.ExecuteReader(RelationalCommandParameterObject 参数对象)
在 Microsoft.EntityFrameworkCore.Query.Internal.SingleQueryingEnumerable'1.Enumerator.InitializeReader(Enumerator 枚举器)
在 Microsoft.EntityFrameworkCore.Query.Internal.SingleQueryingEnumerable'1.Enumerator.<>c.b__21_0(DbContext _,Enumerator 枚举器)
在 Microsoft.EntityFrameworkCore.SqlServer.Storage.Internal.SqlServerExecutionStrategy.Execute[TState,TResult](TState 状态,Func'3 操作,Func'3 verifySucceeded)
在 Microsoft.EntityFrameworkCore.Query.Internal.SingleQueryingEnumerable'1.Enumerator.MoveNext()
在 UserQuery.ExportDataAsync(String groupName, DataOptions options, DataContexts dataContexts),第 154 行
在 EF Core 中工作时,适当的解决方案是什么?
仅供参考,直到异常点为止显示的生成的 SQL 如下(看起来是正确的):
info: 10/21/2024 10:30:59.384 RelationalEventId.CommandExecuted[20101] (Microsoft.EntityFrameworkCore.Database.Command)
Executed DbCommand (1ms) [Parameters=[@__gKey_0='1009' (Nullable = true)], CommandType='Text', CommandTimeout='30']
SELECT [p].[pAuthID] AS [AuthId], [h].[hisDateCreated] AS [DateCreated], [h].[hisDateUpdated] AS [DateUpdated], [h].[hisType] AS [Type], [h].[hisIndex] AS [Index], [h].[hisData] AS [Data], [h].[hisDataXml] AS [DataXml]
FROM [HistoryData] AS [h]
LEFT JOIN [Profile] AS [p] ON [h].[hispKey] = [p].[pKey]
WHERE [h].[hisgKey] = @__gKey_0
ORDER BY [p].[pAuthID], [h].[hisType], [h].[hisIndex]
fail: 10/21/2024 10:30:59.385 RelationalEventId.CommandError[20102] (Microsoft.EntityFrameworkCore.Database.Command)
Failed executing DbCommand (0ms) [Parameters=[@__gKey_0='1009' (Nullable = true)], CommandType='Text', CommandTimeout='30']
SELECT [p].[pAuthID] AS [AuthId], [p].[pProfile] AS [Data], [p].[pProfileXml] AS [DataXml], [p].[pDateCreated] AS [DateCreated], [p].[pDateUpdated] AS [DateUpdated]
FROM [Profile] AS [p]
WHERE [p].[pgKey] = @__gKey_0
ORDER BY [p].[pAuthID]
与非 ORM 或模仿 SQL 的实现相比,EF 的一个关键区别在于它旨在自动映射关系和解析连接等。使用 EF,您可以通过导航属性映射关系,然后使用该对象模型来访问数据。如果您的配置文件包含历史数据记录,那么配置文件实体将具有:
public virtual ICollection<HistoryData> HistoryDatas { get; } = [];
为关系定义了
.HasMany(x => x.HistoryDatas).WithOne(x => x.Profile)
,假设历史数据具有返回其配置文件的导航引用。 (可选)
这会将您的查询更改为:
var profiles = dataContexts.xDS.Profiles
.Where(p => p.GroupKey == gKey)
.Where(p => options.AuthIdsToExport.Length == 0
|| options.AuthIdsToExport.Contains(p.AuthID))
.OrderBy(p => p.AuthID)
.Select(p => new
{
ProfileXmlRow = new XmlProfileRow
{
AuthId = p.AuthID,
Data = p.Data,
DataXml = p.ProfileXml,
DateCreated = p.DateCreated,
DateUpdated = p.DateUpdated
},
HistoryXmlRows = p.HistoryDatas
.OrderBy(h => h.Profile!.AuthID)
.ThenBy(h => h.Type)
.ThenBy(h => h.Index)
.Select(h => new XmlHistoryRow
{
AuthId = p.AuthID,
DateCreated = h.DateCreated,
DateUpdated = h.DateUpdated,
Type = h.Type,
Index = h.Index,
Data = h.Data,
DataXml = h.DataXml
}).ToList()
}).ToList();
这将为每个 ProfileXml 及其关联的 HistoryDataXml 返回匿名类型的结构。
您的方法可能存在的一个问题可能是查询的迭代。您的查询仍然是 IQueryable 并且不会具体化到内存中,因此如果某些内容获取枚举器并开始并行迭代这些枚举器,那么这可能会解释异常。 EF DbContext 不是线程安全的,因此需要阻止读取并按顺序完整执行。您的代码可能只需在两个查询中附加
ToList()
(或等待 ToListAsync()
)即可工作,以便您的枚举器针对内存中的物化集而不是 IQueryable
进行操作。