I have a hosted service that processes objects PUT/POST via an API endpoint, that is, as soon as a new entity is given or an existing one is edited, (a) the hosted service starts processing it (a long running process), and (b) the received/modified object returned (as a JSON object) to the API caller.
When PUT/POST an entity, I see run-time errors here and there (e.g., at object JSON serializer) complaining for different issues, such as:
ObjectDisposedException: Cannot access a disposed object. A common cause of this error is disposing a context that was resolved from dependency injection and then later trying to use the same context instance elsewhere in your application. This may occur if you are calling Dispose() on the context, or wrapping the context in a using statement. If you are using dependency injection, you should let the dependency injection container take care of disposing context instances.
or:
InvalidOperationException: A second operation started on this context before a previous operation completed. This is usually caused by different threads using the same instance of DbContext.
Initially I was using a database context pool, but according to this, it seems the pooling has known issues with hosted services. Therefore, I switched to regular AddDbContext
; however, neither that has solved the problem.
This is how I define the database context and the hosted service:
public class Startup
{
public void ConfigureServices(IServiceCollection services)
{
services.AddCustomDbContext(Configuration);
// This is the hosted service:
services.AddHostedService<MyHostedService>();
}
}
public static class CustomExtensionMethods
{
public static IServiceCollection AddCustomDbContext(
this IServiceCollection services,
IConfiguration configuration)
{
services.AddDbContext<MyContext>(
options =>
{
options
.UseLazyLoadingProxies(true)
.UseSqlServer(
configuration.GetConnectionString("DefaultConnection"),
sqlServerOptionsAction: sqlOptions => { sqlOptions.MigrationsAssembly(typeof(Startup).GetTypeInfo().Assembly.GetName().Name); });
});
return services;
}
}
and I access the database context in hosted service as the following (as recommended here):
using(var scope = Services.CreateScope())
{
var context = scope.ServiceProvider.GetRequiredService<MyContext>();
}
Edit 1
As mentioned, the errors happen all around the code; however, since I mentioned the errors occurring on the serializer, I am sharing the serializer code in the following:
public class MyJsonConverter : JsonConverter
{
private readonly Dictionary<string, string> _propertyMappings;
public MyJsonConverter()
{
_propertyMappings = new Dictionary<string, string>
{
{"id", nameof(MyType.ID)},
{"name", nameof(MyType.Name)}
};
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
JObject obj = new JObject();
Type type = value.GetType();
foreach (PropertyInfo prop in type.GetProperties())
{
if (prop.CanRead)
{
// The above linked errors happen here.
object propVal = prop.GetValue(value, null);
if (propVal != null)
obj.Add(prop.Name, JToken.FromObject(propVal, serializer));
}
}
obj.WriteTo(writer);
}
}
Update 2
An example API endpoint is as the following:
[Route("api/v1/[controller]")]
[ApiController]
public class MyTypeController : ControllerBase
{
private readonly MyContext _context;
private MyHostedService _service;
public MyTypeController (
MyContext context,
MyHostedService service)
{
_context = context;
_service = service
}
[HttpGet("{id}")]
public async Task<ActionResult<IEnumerable<MyType>>> GetMyType(int id)
{
return await _context.MyTypes.FindAsync(id);
}
[HttpPost]
public async Task<ActionResult<MyType>> PostMyType(MyType myType)
{
myType.Status = State.Queued;
_context.MyTypes.Add(myType);
_context.MyTypes.SaveChangesAsync().ConfigureAwait(false);
// the object is queued in the hosted service for execution.
_service.Enqueue(myType);
return CreatedAtAction("GetMyType", new { id = myType.ID }, myType);
}
}