Guga Todua
Guga Todua

Reputation: 522

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

I'm trying to call a method from IActionResult using Task.Run. This is the Exception I am getting.

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.

Initialization of EntityContext instance:

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

Calling method from controller:

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

Method I am calling:

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

Upvotes: 0

Views: 11822

Answers (2)

Edward
Edward

Reputation: 29976

For your issue, this is caused by that you are reference a scoped service EntityContext from another thread. For EntityContext, it will be disposed when the request returned from Controller.

As the suggestion from Chris, you may call t1.Wait(); to complete the t1 task before the request return back to client. By calling t1.Wait();, the EntityContext _context will not be disposed and then you won't get any error.

For another option, you may try pass IServiceProvider to create a new EntityContext instead of referencing the existing EntityContext which is created by Controller

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

Upvotes: 1

Chris Pratt
Chris Pratt

Reputation: 239260

Task.Run will start a new thread, and unless you await it, the existing thread where the action is running will keep going, eventually returning and taking the context with it, which your method running in the new thread depends on. If you do await it, then there's no point in running in a separate thread; you're just consuming additional resources for no good reason.

In short, you should not be using Task.Run for this at all. It's not the same as "running the background". Instead, you should schedule the email to be sent on different process or at the very least an IHostedService. You can use QueuedBackgroundService. There's an implementation available at https://learn.microsoft.com/en-us/aspnet/core/fundamentals/host/hosted-services?view=aspnetcore-2.2&tabs=visual-studio#queued-background-tasks.

Upvotes: 1

Related Questions