同时执行两个数据库调用(使用两个 DbContext)

Execute Two Database Calls (with Two DbContexts) Simultaneously

我需要从两个不同的 DbContext(针对 Oracle 数据库的两个不同模式)获取结果来填充页面。我想同时执行两个数据库查询(只读,无写操作),并在它们都完成时 return 结果。麻烦的是,我是平行世界里的连载小子,我不知道杰克 Async/Await/TPL 等等……

我有一个控制器 Action 基本上是这样的:

public Task<IActionResult> Foo(MyViewModel vm)
{
  if (!ModelState.IsValid) return Task.Run(()=> (IActionResult)View(vm));

  var filter = new FilterObject(vm);
  var firstTask = _firstContext.FilterItems(filter); // returns Task<IQueryable<Items>>
  var secondTask = _secondContext.FilterItems(filter); // returns Task<IQueryable<Items>> 

  vm.Result.Clear();
  vm.Results.AddRange(firstTask.Result);
  vm.Results.AddRange(secondTask.Result);

  return Task.Run(()=> (IActionResult)View("Index", vm));
}

我的 DbContext 调用过滤器来完成它的工作:

public class FirstContext : DbContext
{
  public DbQuery<Items> Items{get;set;}

  public Task<IQueryable<Items>> FilterItems(FilterObject filter)
  {
     filter.ApplyTo(Items.AsQueryable());
  }
}

... 并且我的 FilterObject 修改了 IQueryable:

public class FilterObject
{
  public Task<IQueryable<Items>> ApplyTo(IQueryable<Items> items)
  {
    return Task.Run(()=> items
      .Where(item => item.Property1.Contains(this.Property1))
      .Where(item => item.Other == this.OtherString)
       // more Where clauses ad nauseum; you get the idea
  }
}

我这样做是否接近正确?据我了解,对数据库的调用直到 AddRange 方法才会真正执行,因为 Task.Result 是一个 IQueryable - 所以这对我来说似乎是错误的: AddRange() 方法将同步执行,而不是异步执行,对吗?我所做的只是构建两个查询,而不是执行它们。我猜我需要 return 来自 DbContext 的 List<Item>,这将导致每个查询实际执行......但这是我需要做的唯一改变,以便这些调用发生在同时,互不阻挡?

一如既往,非常感谢任何指导。

Task.Run 会将任务移至另一个线程,这并不是真正必要的。使用 asyncawait,这将 运行 同一线程上的所有内容,但允许在等待结果时在同一线程上完成其他工作。例如,在触发第一个查询后,它可以在等待第一个查询完成时启动第二个查询。

好像FilterItemsApplyTo只是在修改查询,并没有实际执行结果,所以我不明白为什么他们需要return一个Task 完全没有。在构建查询后,您可以使用 ToListAsync() 实际执行查询。

例如:

public async Task<IActionResult> Foo(MyViewModel vm)
{
  if (!ModelState.IsValid) return Task.Run(()=> (IActionResult)View(vm));

  var filter = new FilterObject(vm);

  //This will execute the queries
  var firstTask = _firstContext.FilterItems(filter).ToListAsync();
  var secondTask = _secondContext.FilterItems(filter).ToListAsync();

  vm.Result.Clear();
  vm.Results.AddRange(await firstTask); //add the first set when they're available
  vm.Results.AddRange(await secondTask); //add the second set when they're available

  return View("Index", vm);
}

您在 FilterItems 中没有 return,但我认为它应该在那里:

public class FirstContext : DbContext
{
  public DbQuery<Items> Items{get;set;}

  public IQueryable<Items> FilterItems(FilterObject filter)
  {
     return filter.ApplyTo(Items.AsQueryable());
  }
}
public class FilterObject
{
  public IQueryable<Items> ApplyTo(IQueryable<Items> items)
  {
    return items
      .Where(item => item.Property1.Contains(this.Property1))
      .Where(item => item.Other == this.OtherString)
       // more Where clauses ad nauseum; you get the idea
  }
}

从您拥有的 IQueryables 中删除所有 Task 工作。那一层太多了。把事情简单化。你有一个查询。那将 return 和 IQueryable<>。然后你使用ToListAsync异步获取结果:

public async Task<IActionResult> Foo(MyViewModel vm)
{
  if (!ModelState.IsValid) return View(vm);

  var filter = new FilterObject(vm);
  var firstTask = _firstContext.YourQueryable.ToListAsync();
  var secondTask = _secondContext.YourQueryable.ToListAsync();    

  var firstResult = await firstTask;
  var secondResult = await secondTask;

  vm.Result.Clear();
  vm.Results.AddRange(firstResult);
  vm.Results.AddRange(secondResult);

  return View("Index", vm);
}