【问题标题】:Blazor wait for ef core to finish requestBlazor 等待 ef 核心完成请求
【发布时间】:2021-03-26 12:21:20
【问题描述】:

所以现在我得到了一个

Error: System.InvalidOperationException: A second operation was started on this context before a previous operation completed.

因为 blazor 似乎不尊重当前的请求。

我正在做的是这样的:

FirstComponent.razor

@inject IService _service; // abstracted service that calls EF

<layout> // layout stuff here
  <SecondComponent /> // second blazor component
</layout>

@code {

  protected override async Task OnInitializeAsync()
  {
     var getSomething = await _service.OnInitializedAsync();
  }

}

SecondComponent.razor

@inject IService _service; // abstracted service that calls EF

@code {

  protected override async Task OnInitializedAsync()
  {
     var getSomething = await _service.GetSomething();
  }

}

所以我将我的实体拆分为多个子组件进行编辑。现在我有了一个调用所有这些子组件的“父”组件。


编辑 1

我的IService 看起来像这样。

public interface IService
{
  public Task<Something> GetSomething();
}

internal class Service : IService
{
  private readonly SomethingRepository _repo;

  public Service(SomethingRepository repo)
  {
     _repo = repo;
  }

  public async Task<Something> GetSomething() => _repo.GetAsync();
}

internal SomethingRepository
{
  private readonly AppDbContext _context;

  public SomethingRepository(AppDbContext context)
  {
    _context = context;
  }

  public async Task<Something> GetAsync() => ...;//implementation of whatever
}

我正在使用 AddDbContext 将我的 AppDbContext 添加到服务集合中,并使用 AddScoped 添加我的服务和存储库

【问题讨论】:

  • 听起来您设置 EF 上下文的方式存在设计缺陷。问题出在您的服务上,这不是 Blazor 的错。您一次不能将同一个上下文实例用于多个操作。正确的依赖注入将确保每个请求都有自己的实例。
  • 也就是说,目前阅读 docs blazor 不支持范围,就像您的 API 控制器或 MVC 控制器一样。所以我的问题是,如何更新 blazor 以确保我的组件仅在另一个完成调用后才调用数据库
  • 请展示您的服务。
  • 我会编辑我的答案,但我有一个非常大的项目,所以我会尝试抽象它,但不会遗漏核心功能。
  • 如果服务在请求之间使用相同的上下文实例,这将是一个问题。 This is the preferred way 为 di 系统提供上下文。

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


【解决方案1】:

对于 Blazor Server 应用,不应将现有的 DI 生命周期用于 DbContext。而是为每个请求创建一个新请求,或者将其限定为一个组件。每:

EF Core 为 ASP.NET Core 应用提供 AddDbContext 扩展 默认情况下将上下文注册为范围服务。在 Blazor 服务器中 应用程序,范围服务注册可能会出现问题,因为 实例在用户电路中的组件之间共享。 DbContext 不是线程安全的,也不是为并发使用而设计的。这 由于以下原因,现有的生命周期是不合适的:

  • Singleton 在应用程序的所有用户之间共享状态并导致 不适当的同时使用。
  • Scoped(默认)在同一用户的组件之间产生了类似的问题。
  • 瞬态导致每个请求产生一个新实例;但由于组件可以长期存在,这会导致上下文比可能的寿命更长 有意的。

以下建议旨在提供一致的 在 Blazor 服务器应用中使用 EF Core 的方法。

  • 默认情况下,考虑每个操作使用一个上下文。 . . .
  • 使用标志来防止多个并发操作:。 . .
  • 对于利用 EF Core 的更改跟踪或并发控制的长期操作,请将上下文范围限定为 组件。

ASP.NET Core Blazor Server with Entity Framework Core (EFCore)

【讨论】:

  • 感谢您的回答。就我而言,这对于瞬态范围来说不是问题。我没有运行完整的 blazor 服务器应用程序。我们正在从 MVC 迁移到 blazor,其中一些页面已被 blazor 功能替换。我在某些特定页面上实例化了一个组件,因此当用户离开页面时生命周期结束。
  • 连答案都没有……奇怪的是这里的投票方式
【解决方案2】:

当您使用 async/await 调用服务但不使用标志来停止子组件的呈现时,通常会发生这种情况。这应该可以解决您的大部分“已开始第二次操作...”问题。

<layout>
@if(_loading)
{
 <span> Loading .... </span>
}
else{
 <ChildComponent/>
}
</layout>


@code
{
  bool _loading;
  protected override async Task OnInitializedAsync(){
     _loading = true;
     var data = _service.getSomething();
     _loading = false;
  }
}

【讨论】:

    【解决方案3】:

    将此添加到您的连接字符串“MultipleActiveResultSets=true”

    【讨论】:

      【解决方案4】:

      所以使用 Henk Holterman 提供的工厂模式对我来说不是一个解决方案。见:https://docs.microsoft.com/en-us/ef/core/dbcontext-configuration/#using-a-dbcontext-factory-eg-for-blazor

      这确实有效地将所有东西的生命周期从 Scoped 更改为 Transient。包括数据库上下文。

      services.AddDbContext<AppDbContext>(..options.., ServiceLifetime.Transient);
      
      services.AddTransient<IService, Service>();
      services.AddTransient<SomethingRepository>();
      

      【讨论】:

      • 但是 Transient 服务不是 Disposed 的,是吗?您现在依靠 GC 进行清理,这将无法很好地扩展。
      • 建议您不要使用 Transient DI 生命周期,因为它可能会被注入到更长生命周期的服务中。你总是处置你的 DbContext。如果您手动创建它,则使用 using 块,并且如果您将其作用域/注入到某个其他组件,则该组件应该是可处置的,并在处置时负责处置。你永远不应该依赖 GC。但默认情况下,DbContext 不保存打开的数据库连接,因此它不像直接泄漏 SqlConnection 那样可怕。
      猜你喜欢
      • 1970-01-01
      • 2011-07-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2020-07-22
      • 2020-06-08
      相关资源
      最近更新 更多