对 ITVF 的引用会引发“在前一个操作完成之前在此上下文上启动的第二个操作”异常
Posted
技术标签:
【中文标题】对 ITVF 的引用会引发“在前一个操作完成之前在此上下文上启动的第二个操作”异常【英文标题】:Reference to an ITVF raises a "second operation started on this context before a previous operation completed" exception 【发布时间】:2018-10-25 16:58:54 【问题描述】:我正在尝试在 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();
查询运行时会产生错误:
InvalidOperationException:在此上下文中启动了第二个操作 在之前的操作完成之前。任何实例成员都不是 保证是线程安全的。
提及代码:
System.Linq.AsyncEnumerable.Aggregate_(IAsyncEnumerable 源,TAccumulate 种子, Func 累加器、Func resultSelector、CancellationToken cancelToken) MyApplication.Controllers.VehiclesController.Foo() 在 VehiclesController.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 参考时会发生这种情况?我该如何解决这个问题?
代码
UnitRepairStatus
ITVF:
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;
MyDatabaseDbContext
DbContext:
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
ViewModel:
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;
VehiclesController
Foo
方法:
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);
参考:
Use a Inline Table-Valued Functions with Linq and Entity Framework【问题讨论】:
【参考方案1】:对不起,我的错。您之前问题的答案中的技术适用于使用常量/变量参数调用 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 默默地将查询执行切换到客户端评估(讨厌这样),然后在同一个上下文中执行多个异步操作时受到保护。
【讨论】:
使用没有参数的 TVF 有什么价值,而不仅仅是一个视图?如果没有,我可以消除IQueryable<>
方法,并将其定义为modelBuilder.Query<UnitRepairStatus>("vUnitRepairStatus")
。似乎您的代码的其余部分将按原样工作。
“原始方法的问题在于 EF Core 默默地将查询执行切换到客户端评估(讨厌那个),然后在同一个上下文中执行多个异步操作时受到保护。”当我使用第二个上下文(与另一个数据库的连接)时,我没有收到错误——可能是因为它没有达到操作限制。这也解释了为什么输出显示重复调用该函数的数据库。
查询已按预期翻译:SELECT [v].[ID], [v].[VehicleNumber], [r].[InRepair] FROM [Vehicles] AS [v] INNER JOIN ( SELECT * FROM UnitRepairStatus() ) AS [r] ON [v].[VehicleNumber] = [r].[UnitNumber] ORDER BY [VehicleNumber]
@craig 确实,没有参数它可以映射到视图。仍然使用函数更灵活 - 它允许您定义和传递一些其他(非相关)参数,并且在将来的某个时候,我希望将添加对 TVF(设置)返回函数的支持(类似于对 [数据库标量函数映射](docs.microsoft.com/en-us/ef/core/what-is-new/… 已在 v2.0 中添加)。上述技术只是当前 EF Core 限制的解决方法。以上是关于对 ITVF 的引用会引发“在前一个操作完成之前在此上下文上启动的第二个操作”异常的主要内容,如果未能解决你的问题,请参考以下文章
Blazor:在前一个操作完成之前在此上下文上启动了第二个操作
UserManager 错误“在前一个操作完成之前在此上下文上启动了第二个操作”
Entity Framework Core:在前一个操作完成之前在此上下文上启动了第二个操作
asp.net core 在前一个操作完成之前在此上下文上启动了第二个操作