Linq-to-SQL 迁移到 EF Core,已经有一个与此命令关联的打开的 DataReader

问题描述 投票:0回答:1

我正在将一些旧版 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]
c# sql-server entity-framework linq-to-sql
1个回答
0
投票

与非 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
进行操作。

© www.soinside.com 2019 - 2024. All rights reserved.