对ITVF的引用引发了"second operation started on this context before a previous operation completed"例外



我正在尝试在Linq查询中引用内联表值函数(ITVF):

var results = await (
from v in _context.Vehicles
from r in _context.UnitRepairStatus(v.VehicleNumber) <-- ITVF reference
orderby v.VehicleNumber
select new FooViewModel { 
ID = v.ID, 
VehicleNumber = v.VehicleNumber,
InRepair = Convert.ToBoolean(r.InRepair) <-- ITFV field
}
).ToListAsync();

当查询运行时,它会生成一个错误:

无效操作异常:在此上下文上启动了第二个操作 在上一个操作完成之前。任何实例成员都不是 保证线程安全。

提到代码:

System.Linq.AsyncEnumerable.Aggregate_(IAsyncEnumerable Source, TAccumulate seed, 函数累加器、函数结果选择器、取消令牌取消令牌) MyApplication.Controllers.VehiclesController.Foo() in 车辆控制器.cs

var results = await (

如果我删除 ITFV 引用,查询将按预期工作

var results = await (
from v in _context.Vehicles
orderby v.VehicleNumber
select new FooViewModel { 
ID = v.ID, 
VehicleNumber = v.VehicleNumber,
InRepair = False <-- dummy value
}
).ToListAsync();

为什么在添加 ITVF 引用时会发生这种情况? 我该如何解决此问题?

法典

UnitRepairStatusITVF:

CREATE FUNCTION dbo.UnitRepairStatus(
@unit_number varchar(18)
)
RETURNS TABLE
AS
RETURN
SELECT  h.InRepair
-- connects to a second database on same server
--  shouldn't be an issue, but mentioning it in case it might be
FROM    Schema2..Unit u
INNER JOIN Schema2..History h on u.ID = h.UnitID
WHERE   u.UnitNumber = @unit_number

UnitRepairStatus型号:

public class UnitRepairStatus
{
public string UnitNumber { get; set; }
public int? InRepair { get; set; }
}

MyDatabaseDbContextDbContext:

public class MyDatabaseDbContext : DbContext
{
public MyDatabaseDbContext(DbContextOptions<MyDatabaseDbContext> options) : base(options) {}
...
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
...
modelBuilder.Query<UnitRepairStatus>();
}
public IQueryable<UnitRepairStatus> UnitRepairStatus(string unitNumber) =>
Query<UnitRepairStatus>().FromSql($"SELECT * FROM UnitRepairStatus({unitNumber})");
}

FooViewModel视图模型:

public class FooViewModel
{
public int ID { get; set; }
public string VehicleNumber { get; set; }
public bool InRepair { get; set; }
}

VehiclesController构造函数:

public VehiclesController(
ILogger<VehiclesController> logger, 
MyDatabaseDbContext context
)
{
_logger = logger;
_context = context;
}

VehiclesControllerFoo方法:

public async Task<IActionResult> Foo()
{
List<FooViewModel> model = null;
try
{
var results = await (  <-- line referenced in error message
from v in _context.Vehicles
from r in _context.UnitRepairStatus(v.VehicleNumber)
orderby v.VehicleNumber
select new FooViewModel { 
ID = v.ID, 
VehicleNumber = v.VehicleNumber,
InRepair = Convert.ToBoolean(r.InRepair)
}
).ToListAsync();
}
catch (Exception e)
{
_logger.LogError(e.Message);
throw;
}
return View(model);
}

参考:

  • 将内联表值函数与 Linq 和实体框架结合使用

对不起,我的错。从您上一个问题的答案中得出的技术适用于使用常量/变量参数调用 ITVF,但不适用于像您的情况(和我的错误示例)那样的相关子查询。

解决方案是删除 ITVF 参数并扩展结果以包含该列(有效地将其转换为无参数视图):

CREATE FUNCTION dbo.UnitRepairStatus()
RETURNS TABLE
AS
RETURN
SELECT u.UnitNumber, h.InRepair
FROM    Schema2.Unit u
INNER JOIN Schema2.History h on u.ID = h.UnitID

同时从上下文方法中删除参数

EF Core 2.x:

public IQueryable<UnitRepairStatus> UnitRepairStatus() =>
Query<UnitRepairStatus>().FromSql("SELECT * FROM UnitRepairStatus()");

EF Core 3.x:

public IQueryable<UnitRepairStatus> UnitRepairStatus() =>
Set<UnitRepairStatus>().FromSqlRaw("SELECT * FROM UnitRepairStatus()");

并将 LINQ 查询更改为使用联接:

var results = await (
from v in _context.Vehicles
join r in _context.UnitRepairStatus() on v.VehicleNumber equals r.UnitNumber // <---
orderby v.VehicleNumber
select new FooViewModel { 
ID = v.ID, 
VehicleNumber = v.VehicleNumber,
InRepair = Convert.ToBoolean(r.InRepair)
}
).ToListAsync();

现在它应该转换并执行服务器端,并成功地在客户端实现。

原始方法的问题在于,EF Core 以静默方式将查询执行切换到客户端评估(讨厌那个),然后达到对在同一上下文上执行多个异步操作的保护。

相关内容

最新更新