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);
}
}
The following lines are most likely causing the ObjectDisposedException error:
return await _context.MyTypes.FindAsync(id);
and
return CreatedAtAction("GetMyType", new { id = myType.ID }, myType);
This is because you are relying on this variable:
private readonly MyContext _context;
Since object myType has been attached to that context.
As I mentioned before, it is not a good idea to send context entities for serialization because by the time the serializer has a chance to fire, the context might have been disposed. Use a model (meaning a class in the Models folder) instead and map all the relevant properties from your real entity to it. for instance, you could create a class called MyTypeViewModel that contains only the properties that you need to return:
public class MyTypeViewModel
{
public MyTypeViewModel(MyType obj)
{
Map(obj);
}
public int ID { get; set; }
private void Map(MyType obj)
{
this.ID = obj.ID;
}
}
Then instead of returning the entity, use the view model:
var model = new MyTypeViewModel(myType);
return CreatedAtAction("GetMyType", new { id = myType.ID }, model);
As far as the InvalidOperationException, my educated guess is that since you are not awaiting the SaveChangesAsync method, the serializer is firing while the original operation is still in progress, causing a double hit to the context, resulting in the error.
Using await on the SaveChangesAsync method should fix that, but you still need to stop sending lazy-loaded entities for serialization.
Upon further review, the service itself might also be causing issues since you are passing it a reference to object myType:
_service.Enqueue(myType);
The same two issues may occur if the service is doing something with the object that causes a call to a now-disposed context or at the same time as other asynchronous parts (e.g. serialization) attempt to lazy-load stuff.
Related
I have problem with ef core. I have two services which read data from database. On one page is call first service and on second page is called second service. When i click to button for create a new program i got error. I call it normally from page with inject service. Can anybody help me with it?
Show in application
builder.Services.AddDbContextPool<Context>(options =>
{
options.UseSqlServer(builder.Configuration.GetConnectionString("Connection"));
});
TestService1:
public class TestService1 : ITestService1
{
private readonly Context _context;
private readonly IMapper _mapper;
public TestService1(Context context, IMapper mapper)
{
_kreativgangContext = kreativgangContext;
_mapper = mapper;
}
public virtual async Task<AllProgramViewModel> HandleAsync(AllProgramFilterViewModel filter)
{
var model = new AllProgramViewModel();
var data = _context.Programs.Where(x => (EF.Functions.Like(x.Name ?? "", "%" + filter.Name + "%") || string.IsNullOrEmpty(filter.Name)))
.Select(x => new Core.Models.Program() { ID = x.ID, Name = x.Name, Order = x.Order });
result.Model.TotalCount = await data.CountAsync();
result.Model.Items = data.Select(x => _mapper.Map<AllProgramItemViewModel>(x));
return model;
}
}
public interface ITestService1
{
public Task<AllProgramViewModel> HandleAsync(AllProgramFilterViewModel filter);
}
Test service 2:
public class TestService2 : ITestService2
{
private readonly Context _context;
public TestService2(Context context)
{
_context = context;
}
public virtual async Task<NewProgramViewModel> HandleAsync()
{
var model = new NewProgramViewModel();
List<ProgramOrderViewModel> items = _context.Programs.Select(x => new Core.Models.Program() { Order = x.Order, ID = x.ID })
.Select(x => new ProgramOrderViewModel()
{
ID = x.ID,
Order = x.Order
}).ToList();
return await Task.FromResult(model);
}
}
public interface ITestService2
{
public Task<NewProgramViewModel> HandleAsync();
}
Error:
Error: System.InvalidOperationException: A second operation was started on this context instance before a previous operation completed. This is usually caused by different threads concurrently using the same instance of DbContext. For more information on how to avoid threading issues with DbContext, see https://go.microsoft.com/fwlink/?linkid=2097913.
at Microsoft.EntityFrameworkCore.Infrastructure.Internal.ConcurrencyDetector.EnterCriticalSection()
at Microsoft.EntityFrameworkCore.Query.Internal.SingleQueryingEnumerable`1.Enumerator.MoveNext()
at System.Collections.Generic.List`1..ctor(IEnumerable`1 collection)
at System.Linq.Enumerable.ToList[TSource](IEnumerable`1 source)
at Mitar.Kreativgang.Admin.Handlers.TestService2.HandleAsync() in D:\Programming\Kreativgang\Src\Mitar.Kreativgang.Admin\Handlers\TestService2.cs:line 26
at Mitar.Kreativgang.Admin.Pages.Program.ProgramNew.OnInitializedAsync() in D:\Programming\Kreativgang\Src\Mitar.Kreativgang.Admin\Pages\Program\ProgramNew.razor:line 114
at Microsoft.AspNetCore.Components.ComponentBase.RunInitAndSetParametersAsync()
This is a known and documented pitfall, explained in ASP.NET Core Blazor Server with Entity Framework Core (EFCore). In Blazor Server, the DI scope is the user circuit - essentially the user session. That means that a scoped service like TestService2 or a DbContext will remain in memory for a long time and end up reused by multiple methods and actions.
As the docs explain :
Blazor Server is a stateful app framework. The app maintains an ongoing connection to the server, and the user's state is held in the server's memory in a circuit. One example of user state is data held in dependency injection (DI) service instances that are scoped to the circuit. The unique application model that Blazor Server provides requires a special approach to use Entity Framework Core.
You need to register and use a DbContextFactory (or PooledDbContextFactory) instead of a DbContextPool, and create a new DbContext instance right where it's used.
builder.Services.AddDbContextFactory<ContactContext>(opt =>
opt.UseSqlServer(...));
or
builder.Services.AddPooledDbContextFactory<ContactContext>(opt =>
opt.UseSqlServer(...));
The service constructors should accept the factory instead of a context :
public TestService2(AddDbContextFactory<ContactContext> factory)
{
_factory = factory;
}
public virtual async Task<NewProgramViewModel> HandleAsync()
{
using var context=_factory.CreateContext())
{
...
}
}
Component Scope
To limit a DbContext's scope to a single component it's not enough to just inject the DbContextFactory. The DbContext instance needs to be explicitly disposed when the user navigates away from the component. To do this, the component needs to implement IDisposable. This is explained in the section Scope to the component lifetime
#implements IDisposable
#inject IDbContextFactory<ContactContext> DbFactory
...
#code
{
ContactContext? Context;
public void Dispose()
{
Context?.Dispose();
}
protected override async Task OnInitializedAsync()
{
Context = DbFactory.CreateDbContext();
...
}
}
I am listing data with Blazor server side and MudBlazor.
I have a user list:
UserList.razor
public partial class UserList
{
private async Task<TableData<User>> ServerReload(TableState state)
{
var admTableData = await _userService.GetUsersAsTableDataAsync(state.ToAdmTableState());
return admTableData.ToTableData();
}
}
The service for the user list looks like this:
UserService.cs
public class UserService
{
public UserService(MyDbContext myDbContext)
{
_userRepository = new UserRepository(myDbContext);
}
public Task<AdmTableData<User>> GetUsersAsTableDataAsync(AdmTableState admTableState)
{
var queryable = _userRepository.GetUsersAsQueryable();
if (!string.IsNullOrEmpty(admTableState.SearchString))
{
queryable = queryable.Where(u => u.Name.Contains(admTableState.SearchString, StringComparison.OrdinalIgnoreCase));
}
switch (admTableState.SortLabel)
{
case "Name":
queryable = queryable.OrderByDirection(admTableState.SortDirection, o => o.Name);
break;
}
return PaginationHelper.GetTableDataAsync(queryable, admTableState);
}
}
The pagination helper:
PaginationHelper.cs
public static async Task<AdmTableData<T>> GetTableDataAsync<T>(IQueryable<T> queryable, AdmTableState admTableState)
{
var admTableData = new AdmTableData<T>();
admTableData.TotalItems = await queryable.CountAsync();
admTableData.Items = await queryable.Skip(admTableState.PageNumber * admTableState.PageSize)
.Take(admTableState.PageSize).ToListAsync();
return admTableData;
}
Lastly. I am registering the services in the following way:
Program.cs
builder.Services.AddDbContext<MyDbContext>(options =>
options.UseSqlServer(builder.Configuration.GetConnectionString("myConnectionString")));
builder.Services.AddScoped<IUserService, UserService>();
If I order a column. I get this error:
Error: System.InvalidOperationException: A second operation was started on this context instance before a previous operation completed. This is usually caused by different threads concurrently using the same instance of DbContext. For more information on how to avoid threading issues with DbContext, see https://go.microsoft.com/fwlink/?linkid=2097913.
If I do a search. It never gets the data and it keeps loading:
Loading forever screenshot
You must use IDbContextFactory if you are using Blazor server-side, because you can't use the same dbcontext instance multiple times in multiple threads.
Your dbcontext service is scoped, which means it will create a new instance while the new request to the server, but the Blazor server is a single page application and you have a single request and single dbcontext instance, and if you use the same dbcontext like a normal asp.net core application it will give you this error:
Error: System.InvalidOperationException: A second operation was started on this....
You must create dbcontext instances manually. Register your dbcontext like this:
builder.Services.AddDbContextFactory<MyDbContext>(
options => options.UseSqlServer(builder.Configuration.GetConnectionString("myConnectionString")));
and use it in your code like this:
private readonly IDbContextFactory<MyDbContext> _contextFactory;
public MyController(IDbContextFactory<MyDbContext> contextFactory)
{
_contextFactory = contextFactory;
}
public void DoSomething()
{
using (var context = _contextFactory.CreateDbContext())
{
// ...
}
}
You can read more in DbContext Lifetime, Configuration, and Initialization.
I cannot seem to find an answer to this question.
So in the frontend when the user loads a page we call an API for each item on that page (10 items). So that equals 10 API calls.
Most of the calls work but there are always a few that fail when trying to query the database resulting in the following error:
InvalidOperationException: A second operation started on this
context before a previous operation completed. Any instance members
are not guaranteed to be thread safe.
Now I understand that Entity Framework is not thread safe but I am unsure how to get around this error.
Everywhere where I am using a DBContext it is always injected in using the built in .net core Ioc container.
Here is the DI setup
services.AddScoped<IOmbiContext, OmbiContext>();
services.AddTransient<ISettingsRepository, SettingsJsonRepository>();
All of my repositories are setup in a Transient scope with the Context as Scoped according to this article: https://learn.microsoft.com/en-us/aspnet/core/data/entity-framework-6
Now I have tried changing the context to Transient and it still happens.
How can I avoid this?
More Information
The API Method:
[HttpGet("movie/info/{theMovieDbId}")]
public async Task<SearchMovieViewModel> GetExtraMovieInfo(int theMovieDbId)
{
return await MovieEngine.LookupImdbInformation(theMovieDbId);
}
Which eventually calls the following where the exception is being thrown:
public async Task<RuleResult> Execute(SearchViewModel obj)
{
var item = await PlexContentRepository.Get(obj.CustomId); <-- Here
if (item != null)
{
obj.Available = true;
obj.PlexUrl = item.Url;
obj.Quality = item.Quality;
}
return Success();
}
PlexContentRepository
public PlexContentRepository(IOmbiContext db)
{
Db = db;
}
private IOmbiContext Db { get; }
public async Task<PlexContent> Get(string providerId)
{
return await Db.PlexContent.FirstOrDefaultAsync(x => x.ProviderId == providerId); <-- Here
}
If you use Entity Framework Core usually you do not need to add your Database Context as an additional service
I recommend to setup your DbContext in the Startup.cs as following:
services.AddEntityFrameworkSqlServer()
.AddDbContext<OmbiContext>();
Followed by a Controller class for your API calls taking the DBContext as constructor parameter.
public class ApiController : Controller
{
protected OmbiContext ctx;
public ApiController(OmbiContext dbctx)
{
ctx = dbctx;
}
public async Task<IActionResult> yourAsyncAction()
{
// access ctx here
}
}
Our existing database deployment has a single 'master' and a read-only replica. Using ASP.NET's Web API2 and an IoC container I want to create controller actions whose attribute (or lack there of) indicate which database connection is to be used for that request (See Controller and Services usage below)...
public MyController : ApiController
{
public MyController(IService1 service1, IService2 service2) { ... }
// this action just needs the read only connection
// so no special attribute is present
public Foo GetFoo(int id)
{
var foo = this.service1.GetFoo(id);
this.service2.GetSubFoo(foo);
return foo;
}
// This attribute indicates a readwrite db connection is needed
[ReadWrteNeeded]
public Foo PostFoo(Foo foo)
{
var newFoo = this.service1.CreateFoo(foo);
return newFoo;
}
}
public Service1 : IService1
{
// The dbSession instance injected here will be
// based off of the action invoked for this request
public Service1(IDbSession dbSession) { ... }
public Foo GetFoo(int id)
{
return this.dbSession.Query<Foo>(...);
}
public Foo CreateFoo(Foo newFoo)
{
this.dbSession.Insert<Foo>(newFoo);
return newFoo;
}
}
I know how to setup my IoC (structuremap or Autofac) to handle per request IDbSession instances.
However, I'm not sure how I would go about making the type of IDbSession instance for the request to key off the indicator attribute (or lack there of) on the matching controller's action. I assume I will need to create an ActionFilter that will look for the indicator attribute and with that information identify, or create, the correct type of IDbSession (read-only or read-write). But how do I make sure that the created IDbSession's lifecycle is managed by the container? You don't inject instances into the container at runtime, that would be silly. I know Filters are created once at startup (making them singleton-ish) so I can't inject a value into the Filter's ctor.
I thought about creating an IDbSessionFactory that would have 'CreateReadOnlyDbSession' and 'CreateReadWriteDbSession' interfaces, but don't I need the IoC container (and its framework) to create the instance otherwise it can't manage its lifecycle (call dispose when the http request is complete).
Thoughts?
PS During development, I have just been creating a ReadWrite connection for every action, but I really want to avoid that long-term. I could also split out the Services methods into separate read-only and read-write classes, but I'd like to avoid that as well as placing GetFoo and WriteFoo in two different Service implementations just seems a bit wonky.
UPDATE:
I started to use Steven's suggestion of making a DbSessionProxy. That worked, but I was really looking for a pure IoC solution. Having to use HttpContext and/or (in my case) Request.Properties just felt a bit dirty to me. So, if I had to get dirty, I might as well go all the way, right?
For IoC I used Structuremap and WebApi.Structuremap. The latter package sets up a nested container per Http Request plus it allows you to inject the current HttpRequestMessage into a Service (this is important). Here's what I did...
IoC Container Setup:
For<IDbSession>().Use(() => DbSession.ReadOnly()).Named("ReadOnly");
For<IDbSession>().Use(() => DbSession.ReadWrite()).Named("ReadWrite");
For<ISampleService>().Use<SampleService>();
DbAccessAttribute (ActionFilter):
public class DbAccessAttribute : ActionFilterAttribute
{
private readonly DbSessionType dbType;
public DbAccessAttribute(DbSessionType dbType)
{
this.dbType = dbType;
}
public override bool AllowMultiple => false;
public override void OnActionExecuting(HttpActionContext actionContext)
{
var container = (IContainer)actionContext.GetService<IContainer>();
var dbSession = this.dbType == DbSessionType.ReadOnly ?
container.GetInstance<IDbSession>("ReadOnly") :
container.GetInstance<IDbSession>("ReadWrite");
// if this is a ReadWrite HttpRequest start an Request long
// database transaction
if (this.dbType == DbSessionType.ReadWrite)
{
dbSession.Begin();
}
actionContext.Request.Properties["DbSession"] = dbSession;
}
public override void OnActionExecuted(HttpActionExecutedContext actionExecutedContext)
{
var dbSession = (IDbSession)actionExecutedContext.Request.Properties["DbSession"];
if (this.dbType == DbSessionType.ReadWrite)
{
// if we are responding with 'success' commit otherwise rollback
if (actionExecutedContext.Response != null &&
actionExecutedContext.Response.IsSuccessStatusCode &&
actionExecutedContext.Exception == null)
{
dbSession.Commit();
}
else
{
dbSession.Rollback();
}
}
}
}
Updated Service1:
public class Service1: IService1
{
private readonly HttpRequestMessage request;
private IDbSession dbSession;
public SampleService(HttpRequestMessage request)
{
// WARNING: Never attempt to access request.Properties[Constants.RequestProperty.DbSession]
// in the ctor, it won't be set yet.
this.request = request;
}
private IDbSession Db => (IDbSession)request.Properties["DbSession"];
public Foo GetFoo(int id)
{
return this.Db.Query<Foo>(...);
}
public Foo CreateFoo(Foo newFoo)
{
this.Db.Insert<Foo>(newFoo);
return newFoo;
}
}
I assume I will need to create an ActionFilter that will look for the indicator attribute and with that information identify, or create, the correct type of IDbSession (read-only or read-write).
With your current design, I would say an ActionFilter is the way to go. I do think however that a different design would serve you better, which is one where business operations are more explicitly modelled behind a generic abstraction, since you can in that case place the attribute in the business operation, and when you explicitly separate read operations from write operations (CQS/CQRS), you might not even need this attribute at all. But I'll consider this out of scope of your question right now, so that means an ActionFilter is the the way to go for you.
But how do I make sure that the created IDbSession's lifecycle is managed by the container?
The trick is let the ActionFilter store information about which database to use in a request-global value. This allows you to create a proxy implementation for IDbSession that is able to switch between a readable and writable implementation internally, based on this setting.
For instance:
public class ReadWriteSwitchableDbSessionProxy : IDbSession
{
private readonly IDbSession reader;
private readonly IDbSession writer;
public ReadWriteSwitchableDbSessionProxy(
IDbSession reader, IDbSession writer) { ... }
// Session operations
public IQueryable<T> Set<T>() => this.CurrentSession.Set<T>();
private IDbSession CurrentSession
{
get
{
var write = (bool)HttpContext.Current.Items["WritableSession"];
return write ? this.writer : this.reader;
}
}
}
I'm struggling with Entityframework in a MVC 4 app, making use of Unity for Dependency injection and Automapper for automapping object to DTO. I run from one issue to the other, EF is sometimes returning old data, so I think my design is not good enough.
What do I have:
To configure Unity I have in my Application_Start:
var UnityContainer = UnityConfig.GetConfiguredContainer();
Mapper.Initialize(cfg => cfg.ConstructServicesUsing(type => UnityContainer.Resolve(type)));
UnityContainer.Resolve<AutoMapperConfig>().MapAutoMapper();
...
In UnityConfig.RegisterTypes:
container.RegisterType<IMyContext, MyContext>(new ContainerControlledLifetimeManager())
...
My respositories use constructor depencency injection:
public class MSSQLTenantRepository : IDalTenantRepository
{
private readonly IMyContext _Db;
public MSSQLTenantRepository(IMyContext db)
{
Db = db;
}
...
And my controller use constructor dependency injection too:
public class TenantController : Controller
{
private readonly ITenantRepository _TenantRepository;
public TenantController(ITenantRepository tenantRepository,
{
_TenantRepository = tenantRepository;
}
...
Automapper config:
public class AutoMapperConfig
{
private readonly ITenantRepository _TenantRepository;
public AutoMapperConfig(ITenantRepository tenantRepository)
{
_TenantRepository = tenantRepository;
}
...
Issues:
I sometimes get old data, from the first request.
When I manually update the data in de SQL server, EF's returning object don't reflect the changes
When I tried different options I also got error about multiple context (due to Automapper)
My questions:
What is best practice using Unity, MVC4, EF 6, repositories and Automapper?
Where to put the code (e.g. in global.asax.c or in UnitiConfig.cs of UnityWebApiActivator?
Do I need to explicit dispose the dbcontext, and if so: Where to do this?
There is a lot said about this subject, but nothing covers all.
container.RegisterType<IMyContext, MyContext>(
new ContainerControlledLifetimeManager())
This is rather bad, it makes a singleton out of your context. This way not only multiple requests share the same context and you risk concurrency issues but also the memory consumption of such shared context grows without control.
Rather, you would like to have a "per-request" life time, where a new context is established for each separate request:
http://www.wiktorzychla.com/2013/03/unity-and-http-per-request-lifetime.html
public class PerRequestLifetimeManager : LifetimeManager
{
private readonly object key = new object();
public override object GetValue()
{
if (HttpContext.Current != null &&
HttpContext.Current.Items.Contains(key))
return HttpContext.Current.Items[key];
else
return null;
}
public override void RemoveValue()
{
if (HttpContext.Current != null)
HttpContext.Current.Items.Remove(key);
}
public override void SetValue(object newValue)
{
if (HttpContext.Current != null)
HttpContext.Current.Items[key] = newValue;
}
}
and
container.RegisterType<IMyContext, MyContext>(new PerRequestLifetimeManager())
I am not sure what your AutoMapperConfig class does and why a repository is injected into it. This is a possible another lifetime issue but I need a clarification on that.
I figured it out with some help of Wiktor.
First: I must just a PerRequestLifeTimeManager (as stated by Wiktor Zychla, thank you for that), which is available in de Unity for MVC bootstrapper.
Second: The line:
UnityContainer.Resolve<AutoMapperConfig>().MapAutoMapper();
must be in Application_BeginRequest (Globas.asax.cs). I've put it in Application_Start, so this was only resolved once at startup. An incoming request was creating a new context, so it differs from the one that Automapper uses. When putting it in BeginRequest the resolve is done on every request, with the same context as the repositories.