Microsoft.EntityFrameworkCore.dll 中发生类型 'System.InvalidOperationException' 的异常,但未在用户代码中处理

An exception of type 'System.InvalidOperationException' occurred in Microsoft.EntityFrameworkCore.dll but was not handled in user code

我正在尝试使用 Task.Run 从 IActionResult 调用一个方法。这是我得到的异常。

An exception of type 'System.InvalidOperationException' occurred in Microsoft.EntityFrameworkCore.dll but was not handled in user code: 'An exception was thrown while attempting to evaluate a LINQ query parameter expression. To show additional information call EnableSensitiveDataLogging() when overriding DbContext.OnConfiguring.' Inner exceptions found, see $exception in variables window for more details. Innermost exception System.NullReferenceException : Object reference not set to an instance of an object.

EntityContext 实例的初始化:

private readonly EntityContext _context; 
public ApiController (
  UserManager<User> userManager,
  SignInManager<User> signInManager,
  ILoggerFactory loggerFactory,
  IMemoryCache memoryCache,
  EntityContext context,
  IRepository repository,
  Context session,
  IEmailService emailService,
  IHostingEnvironment environment,
  IHttpContextAccessor contextAccessor, ViewRender view, IStringLocalizer<SharedResources> localizer) : base (userManager, signInManager, loggerFactory, memoryCache, context, repository, session, contextAccessor) {
  //_view = view;
  _emailService = emailService;
  this.environment = environment;
  _localizer = localizer;
  this._context = context;


}

Startup.cs

 services.AddEntityFrameworkNpgsql ()
    .AddDbContext<EntityContext> (
      options => options.UseNpgsql (connectionString)
    );

从控制器调用方法:

if(updated){
      Task t1 = Task.Run(()=>SendEmailAsync(entity,true,responsible,_context));
    }else{
      Task t1 = Task.Run(()=>SendEmailAsync(entity,false,responsible,_context));
    }

我调用的方法:

public void SendEmailAsync (Activity entity, bool updated, User responsible, EntityContext ctx) {

  List<string> emailList = new List<string> ();

  var mail = new MailComposer (_emailService, environment, _localizer);

  if (responsible.IsSubscriber) {
    emailList.Add (responsible.Email);
  }
  if (entity.Participants.Count > 0) {
    foreach (var item in entity.Participants) {
      var p = ctx.Users.Where(c=>c.Id==item.Participant.Id).FirstOrDefault(); //This is where I am getting an exception.
      if (p.IsSubscriber) {
        emailList.Add (p.Email);
      }

    }
  }
  if (emailList.Count != 0) {
    var emailArray = emailList.ToArray ();
    if (updated) {
      mail.SendActivityUpdate (entity, emailArray);
    } else {
      mail.SendActivityCreated (entity, emailArray);
    }
  }
}

Task.Run 将启动一个新线程,除非您等待它,否则操作 运行 的现有线程将继续运行,最终返回并获取上下文,您的方法运行在新线程依赖。如果您 do await 它,那么 运行 在单独的线程中就没有意义了;你只是无缘无故地消耗了额外的资源。

简而言之,您根本不应该为此使用 Task.Run。它与 "running the background" 不同。相反,您应该安排电子邮件在不同的进程或至少 IHostedService 上发送。您可以使用 QueuedBackgroundService。在 https://docs.microsoft.com/en-us/aspnet/core/fundamentals/host/hosted-services?view=aspnetcore-2.2&tabs=visual-studio#queued-background-tasks.

有可用的实现

对于您的问题,这是因为您从另一个线程引用了范围服务 EntityContext。对于 EntityContext,它将在控制器发出 return 请求时处理。

根据 Chris 的建议,您可以在请求 return 返回客户端之前调用 t1.Wait(); 来完成 t1 任务。通过调用 t1.Wait();EntityContext _context 将不会被释放,这样您就不会收到任何错误。

对于另一种选择,您可以尝试通过 IServiceProvider 创建一个新的 EntityContext 而不是引用由 Controller [=20= 创建的现有 EntityContext ]

public class HomeController : Controller
{        
    private readonly ApplicationDbContext _context;        
    private readonly IServiceProvider _serviceProvider;
    private readonly ILogger<HomeController> _logger;
    public HomeController(ApplicationDbContext context            
        , IServiceProvider serviceProvider
        , ILogger<HomeController> logger)
    {
        _context = context;            
        _serviceProvider = serviceProvider;
        _logger = logger;
    }
    public IActionResult TestTask()
    {
        Task t1 = Task.Run(() => SendEmailAsync(_serviceProvider));
        //t1.Wait();
        return Ok();
    }
    private void SendEmailAsync(IServiceProvider serviceProvider)
    {            
        var context = _serviceProvider.CreateScope().ServiceProvider.GetRequiredService<ApplicationDbContext>();
        var result = context.Student.ToList();
        _logger.LogInformation(JsonConvert.SerializeObject(result));
    }        
}