Blazor Server中EF Core异步查询报错:读取器关闭时调用CheckDataIsReady无效
Blazor Server中EF Core异步查询异常:Invalid attempt to call CheckDataIsReady when reader is closed
异常信息
fail: Microsoft.EntityFrameworkCore.Query[10100] An exception occurred while iterating over the results of a query for context type 'DBR.Web.Context.DBRContext'. System.InvalidOperationException: Invalid attempt to call CheckDataIsReady when reader is closed. at Microsoft.Data.SqlClient.SqlDataReader.CheckDataIsReady(Int32 columnIndex, Boolean allowPartiallyReadColumn, Boolean permitAsync, String methodName) at Microsoft.Data.SqlClient.SqlDataReader.TryReadColumn(Int32 i, Boolean setTimeout, Boolean allowPartiallyReadColumn) at Microsoft.Data.SqlClient.SqlDataReader.ReadColumn(Int32 i, Boolean setTimeout, Boolean allowPartiallyReadColumn) at Microsoft.Data.SqlClient.SqlDataReader.GetGuid(Int32 i) at lambda_method340(Closure , QueryContext , DbDataReader , ResultContext , SplitQueryResultCoordinator ) at Microsoft.EntityFrameworkCore.Query.Internal.SplitQueryingEnumerable`1.AsyncEnumerator.MoveNextAsync() System.InvalidOperationException: Invalid attempt to call CheckDataIsReady when reader is closed. at Microsoft.Data.SqlClient.SqlDataReader.CheckDataIsReady(Int32 columnIndex, Boolean allowPartiallyReadColumn, Boolean permitAsync, String methodName) at Microsoft.Data.SqlClient.SqlDataReader.TryReadColumn(Int32 i, Boolean setTimeout, Boolean allowPartiallyReadColumn) at Microsoft.Data.SqlClient.SqlDataReader.ReadColumn(Int32 i, Boolean setTimeout, Boolean allowPartiallyReadColumn) at Microsoft.Data.SqlClient.SqlDataReader.GetGuid(Int32 i) at lambda_method340(Closure , QueryContext , DbDataReader , ResultContext , SplitQueryResultCoordinator ) at Microsoft.EntityFrameworkCore.Query.Internal.SplitQueryingEnumerable`1.AsyncEnumerator.MoveNextAsync()
问题描述
在ASP.NET Core(Blazor Server)应用中调用GetByIdAsync方法获取对象时触发上述异常。将EF仓库中的.ToListAsync()改为.ToList()后程序可正常运行,但需要保留异步实现,求异常原因及解决方案。
相关代码实现
UI代码
protected override async Task OnInitializedAsync() { if (CreateOrEdit is not "rediger" && CreateOrEdit is not "opret") { errorMessage = "Der er ingenting at se her. :(", isInvalidPath = true; return; } if (!string.IsNullOrWhiteSpace(WorkshopId)) { Func<IQueryable<Workshop>, IIncludableQueryable<Workshop, object>> workshopIncludes = workshop => workshop.Include(x => x.Address!); ResponseDTO<WorkshopDTO> loadedWorkshop = await WorkshopService.GetByIdAsync(Guid.Parse(WorkshopId), isTrackingDisabled: false, includeProperties: workshopIncludes); if (loadedWorkshop.Success) { if (CreateOrEdit is "rediger") { workshopToEdit = loadedWorkshop.Content!; } else { workshopInputModel.Name = loadedWorkshop.Content!.Name; workshopInputModel.PhoneNumber = loadedWorkshop.Content!.PhoneNumber; workshopInputModel.AddressInputModel!.Street = loadedWorkshop.Content!.Address!.Street; workshopInputModel.AddressInputModel!.City = loadedWorkshop.Content!.Address.City; workshopInputModel.AddressInputModel!.PostCode = loadedWorkshop.Content!.Address.PostCode; } } } }
GetByIdAsync服务代码
public async Task<ResponseDTO<TOUT>> GetByIdAsync(Guid entityId, bool isTrackingDisabled = true, Func<IQueryable<T>, IIncludableQueryable<T, object>>? includeProperties = null, Expression<Func<T, T>>? selector = null, CancellationToken cancellationToken = default) { ResponseDTO<TOUT> responseDTO = await repository.GetByIdAsync(entityId, isTrackingDisabled, includeProperties, selector, cancellationToken); return responseDTO; }
GetByIdAsync EF仓库代码(原问题代码)
public async Task<ResponseDTO<TOUT>> GetByIdAsync(Guid id, bool isTrackingDisabled = true, Func<IQueryable<T>, IIncludableQueryable<T, object>>? includeProperties = null, Expression<Func<T, T>>? selector = null, CancellationToken cancellationToken = default) { try { T requestedEntity = (await table.QueryHelper(selector: selector, includeProperties: includeProperties, isTrackingDisabled: isTrackingDisabled).ToListAsync(cancellationToken)).Find(entity => (Guid)entity.GetType().GetProperty("Id")!.GetValue(entity)! == id) ?? throw new ArgumentNullException(null); TOUT requestedEntityDTO = mapper.Map<TOUT>(requestedEntity); return new ResponseDTO<TOUT> { Success = true, Content = requestedEntityDTO }; } catch (ArgumentNullException ex) { return new ResponseDTO<TOUT> { Success = false, ErrorMessage = $"Din anmodning blev ikke fundet - prøv igen.", ExceptionMessage = ex.Message, InnerExceptionMessage = ex.InnerException?.Message, ErrorType = ErrorType.EntityNotFound }; } catch (OperationCanceledException ex) { return new ResponseDTO<TOUT> { Success = false, ErrorMessage = "Din anmodning blev annulleret.", ExceptionMessage = ex.Message, InnerExceptionMessage = ex.InnerException?.Message, ErrorType = ErrorType.CancellationTokenRequested }; } catch (Exception ex) { return new ResponseDTO<TOUT> { Success = false, ErrorMessage = "Ubehandlet serverfejl. Kontakt server administrator.", ExceptionMessage = ex.Message, InnerExceptionMessage = ex.InnerException?.Message, ErrorType = ErrorType.Unhandled }; } }
QueryHelper扩展方法
public static IQueryable<T> QueryHelper<T>(this DbSet<T> table, bool isTrackingDisabled = true, Func<IQueryable<T>, IIncludableQueryable<T, object>>? includeProperties = null, Expression<Func<T, bool>>? filter = null, Expression<Func<T, T>>? selector = null, Func<IQueryable<T>, IOrderedQueryable<T>>? orderBy = null) where T : class { IQueryable<T> query = table.AsSplitQuery(); if (isTrackingDisabled) { query = query.AsNoTracking(); } if (filter is not null) { query = query.Where(filter); } if (includeProperties is not null) { query = includeProperties(query); } if (selector is not null) { query = query.Select(selector); } if (orderBy is not null) { query = orderBy(query); } return query; }
Workshop领域模型
public class Workshop : BaseEntity { [StringLength(50)] public string Name { get; set; } = null!; public Guid AddressId { get; set; } [ForeignKey(nameof(AddressId))] public Address? Address { get; set; } [StringLength(15)] public string PhoneNumber { get; set; } = null!; public ICollection<Member>? Members { get; set; } public ICollection<Case>? Cases { get; set; } public ICollection<Specialization>? Specializations { get; set; } }
Address领域模型
public class Address : BaseEntity { [StringLength(50)] public string Street { get; set; } = null!; [StringLength(25)] public string City { get; set; } = null!; [Required] public short? PostCode { get; set; } }
Program.cs中的DBRContext配置
builder.Services.AddDbContext<DBRContext>(options => options.UseSqlServer(builder.Configuration.GetConnectionString("DBRContext") ?? throw new InvalidOperationException("Connectionstring 'DBRContext' was not found."), options => options.UseQuerySplittingBehavior(QuerySplittingBehavior.SplitQuery)));
异常原因
- 全表加载+内存筛选的错误逻辑
仓库的GetByIdAsync先通过ToListAsync()加载整张表到内存,再用Find()在内存中筛选ID。这种做法不仅性能低下(全表扫描),更关键的是结合Split Query时,异步场景下会导致SQLDataReader提前关闭。 - Split Query异步读取的资源冲突
全局配置了QuerySplittingBehavior.SplitQuery,且QueryHelper默认启用AsSplitQuery()。Split Query会为主实体和关联实体分别执行SQL查询,异步读取时,若上下文在读取完成前被释放(或异步操作的资源管理时序问题),会触发“reader is closed”异常。同步ToList()是阻塞读取,资源能被正确释放,所以无报错。
解决方案
核心修复:数据库层面过滤ID
将“全表加载+内存筛选”改为直接在数据库层面用Where过滤ID,再用FirstOrDefaultAsync()获取单条数据,从根源解决性能和异步资源问题。
修改后的EF仓库GetByIdAsync代码:
public async Task<ResponseDTO<TOUT>> GetByIdAsync(Guid id, bool isTrackingDisabled = true, Func<IQueryable<T>, IIncludableQueryable<T, object>>? includeProperties = null, Expression<Func<T, T>>? selector = null, CancellationToken cancellationToken = default) { try { // 直接在数据库层面过滤ID,避免全表加载 var query = table.QueryHelper(selector: selector, includeProperties: includeProperties, isTrackingDisabled: isTrackingDisabled) .Where(entity => EF.Property<Guid>(entity, "Id") == id); T requestedEntity = await query.FirstOrDefaultAsync(cancellationToken) ?? throw new ArgumentNullException(null); TOUT requestedEntityDTO = mapper.Map<TOUT>(requestedEntity); return new ResponseDTO<TOUT> { Success = true, Content = requestedEntityDTO }; } catch (ArgumentNullException ex) { return new ResponseDTO<TOUT> { Success = false, ErrorMessage = $"Din anmodning blev ikke fundet - prøv igen.", ExceptionMessage = ex.Message, InnerExceptionMessage = ex.InnerException?.Message, ErrorType = ErrorType.EntityNotFound }; } catch (OperationCanceledException ex) { return new ResponseDTO<TOUT> { Success = false, ErrorMessage = "Din anmodning blev annulleret.", ExceptionMessage = ex.Message, InnerExceptionMessage = ex.InnerException?.Message, ErrorType = ErrorType.CancellationTokenRequested }; } catch (Exception ex) { return new ResponseDTO<TOUT> { Success = false, ErrorMessage = "Ubehandlet serverfejl. Kontakt server administrator.", ExceptionMessage = ex.Message, InnerExceptionMessage = ex.InnerException?.Message, ErrorType = ErrorType.Unhandled }; } }
其他优化建议
- 增强QueryHelper通用性:可以给QueryHelper添加
Guid? id参数,直接内置ID过滤逻辑,避免重复代码。 - 验证上下文生命周期:确保DBRContext在Blazor Server中配置为Scoped(默认配置已满足),避免上下文提前回收。
内容的提问来源于stack exchange,提问作者DM-98
相关产品推荐
相关产品推荐

