Ссылка на 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;
}
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);
}
Ссылка:
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 молча переключил выполнение запроса на оценку клиента (ненавидят это), а затем включил защиту для выполнения нескольких асинхронных операций в одном и том же контексте.