Ссылка на ITVF вызывает исключение "вторая операция началась в этом контексте до завершения предыдущей операции"

Я пытаюсь сослаться на встроенную табличную функцию (ITVF) в запросе Linq:

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 source, TAccumulate seed, аккумулятор Func, Func resultSelector, CancellationToken cancellationToken) 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;
}

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

}

Ссылка:

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

Также удалите параметр из метода контекста:

public IQueryable<UnitRepairStatus> UnitRepairStatus() =>
    Query<UnitRepairStatus>().FromSql("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 молча переключил выполнение запроса на оценку клиента (ненавидят это), а затем включил защиту для выполнения нескольких асинхронных операций в одном и том же контексте.

Другие вопросы по тегам