【问题标题】:Reference to an ITVF raises a "second operation started on this context before a previous operation completed" exception对 ITVF 的引用会引发“在前一个操作完成之前在此上下文上启动的第二个操作”异常
【发布时间】: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 参考时会发生这种情况?我该如何解决这个问题?

代码

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})");

}

FooViewModelViewModel:

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);

}

参考:

【问题讨论】:

    标签: c# linq asp.net-core asp.net-core-2.1 entity-framework-core-2.1


    【解决方案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&lt;&gt; 方法,并将其定义为modelBuilder.Query&lt;UnitRepairStatus&gt;("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 限制的解决方法。
    猜你喜欢
    • 2017-03-14
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2022-01-01
    • 2020-01-22
    • 1970-01-01
    • 1970-01-01
    • 2018-11-07
    相关资源
    最近更新 更多