Managing DbContext lifetime in ASP.NET Core SignalR - c#

I have implemented an ASP.Core SignalR Application.
A shared hub class is invoking a signal to all its client on every 10 seconds
from class SharedHub (this class is not inherited from Hub it has logic to get IHubContext to invoke)
public void Tick(){
var time = _context.table.time;
invoke('tick', time.tick);
}
Also in the same class once a new connection established a method called to update database
public void UpdateSocketConnection(int connectionId){
var connection =_context.connection;
connection.id = connectionId;
_context.saveChanges();
}
Problem with this implementation is if the connection is currently calling Tick() method and also a client connected the same time. _context throws an error saying:
_context in use.
(I'll update exact error message once I reproduce).
What I have done ?
I have implemented a factory method to get a new instance of _context on top of every method
public void Tick(){
var time = factory.GetContext().time;
invoke('tick', time.tick);
}
public void UpdateSocketConnection(int connectionId){
var context = Factory.getContext();
var connection =context.connection;
connection.id = connectionId;
context .saveChanges();
}
This actually solved the problem. But it seems not the right thing to do. I am not sure of the performance when getting a new context every time on top of every method. this seems bad practice.
I want to know what are the possible implementation for this scenario.

In the first approach DbContext is shared between operations at the same time and it cause error and unexpected result. To avoid create and dispose DbContext every time in the second approach, DbContextPooling can help performance.
A pool of reusable instances can be created. Instead of disposing an instance, it returns to the pool and resets the instance to its default state. So instead of creating a new instance every time, the code will first check if there is an instance available in the pool or not.
You can enable DbContextPooling in Configure method in startup class:
services.AddDbContextPool<YourContext>(options => options.UseSqlServer(connection));
The default pool size value is 128. Read this article for more info.

Related

Entity Framework query throws 'async error' after many requests

In my project using .NET framework 4.6.1, EF 6.1.4 and IdentityServer3, I set the following DbContext:
public class ValueContext : DbContext
{
public IValueContext(bool lazyLoadingEnabled = false) : base("MyConnectionString")
{
Database.SetInitializer<IValueContext>(null);
Configuration.LazyLoadingEnabled = lazyLoadingEnabled;
}
public DbSet<NetworkUser> NetworkUser { get; set; }
public DbSet<User> User { get; set; }
[...]
And my Entity model User:
[Table("shared.tb_usuarios")]
public class NetworkUser
{
[Column("id")]
[Key()]
public int Id { get; set; }
[Required]
[StringLength(255)]
[Column("email")]
public string Email { get; set; }
[...]
public virtual Office Office { get; set; }
[...]
So far I think its all good.
Then I set this following query in my UserRepository (using DI)
protected readonly ValueContext Db;
public RepositoryBase(ValueContext db)
{
Db = db;
}
public async Task<ImobUser> GetUser(string email)
{
//sometimes I get some error here
return await Db.User.AsNoTracking()
.Include(im => im.Office)
.Include(off => off.Office.Agency)
.Where(u => u.Email == email &&
u.Office.Agency.Active)
.FirstOrDefaultAsync();
}
And everything runs well, until it starts to get many sequential requests, then I begin to get these type of errors, randomly in any function that uses my ValueContext as data source:
System.NotSupportedException: 'A second operation started on this context before a previous asynchronous operation completed. Use 'await' to ensure that any asynchronous operations have completed before calling another method on this context. Any instance members are not guaranteed to be thread safe.'
This is my last hope, as I tried a bunch of different things. Some of them work, and some dont, like:
Convert dbContext to use DI: no difference.
Use context lifetime to run the queries: works, but isnt the solution I want.
Remove asyncronous from requests: works, but also I feel is not the correct way to do.
What Im doing wrong?
EDIT 1
This is how I set up DI in Startup.cs:
private void AddAuth()
{
Builder.Map("/identity", app =>
{
var factory = new IdentityServerServiceFactory()
{
//here I implemented the IdentityServer services to work
ClientStore = new Registration<IClientStore>(typeof(ClientStore)),
[...]
};
AddDependencyInjector(factory);
}
[...]
}
private void AddDependencyInjector(IdentityServerServiceFactory factory)
{
//here I inject all the services I need, as my DbContext
factory.Register(new Registration<ValueContext>(typeof(ValueContext)));
[...]
}
And this is how my UserService is working:
public class UserService : IUserService
{
[Service injection goes here]
//this is a identityServer method using my dbContext implementation on UserRepository
public async Task AuthenticateLocalAsync(LocalAuthenticationContext context)
{
SystemType clientId;
Enum.TryParse(context.SignInMessage.ClientId, true, out clientId);
switch (clientId)
{
case 2:
result = await _userService.GetUser(context.UserName);
break;
case 3:
//also using async/await correctly
result = await _userService.Authenticate(context.UserName, context.Password);
break;
default:
result = false;
break;
}
if (result)
context.AuthenticateResult = new AuthenticateResult(context.UserName, context.UserName);
}
Update - After code posted
When using ASP.Net DI and IdentityServer DI together, we have to be careful to make sure that both the IdentityServer and the underlying DbContext are scoped to the OWIN request context, we do that by Injecting the DbContext into the IdentityServer context. this answer has some useful background: https://stackoverflow.com/a/42586456/1690217
I suspect all you need to do is resolve the DbContext, instead of explicitly instantiating it:
private void AddDependencyInjector(IdentityServerServiceFactory factory)
{
//here I inject all the services I need, as my DbContext
factory.Register(new Registration<ValueContext>(resolver => new ValueContext()));
[...]
}
Supporting dicussion, largely irrelevant now...
With EF it is important to make sure that there are no concurrent queries against the same DbContext instance at the same time. Even though you have specified AsNoTracking() for this endpoint there is no indication that this endpoint is actually the culprit. The reason for synchronicity is so that the context can manage the original state, there are many internals that are simply not designed for multiple concurrent queries, including the way the database connection and transactions are managed.
(under the hood the DbContext will pool and re-use connections to the database if they are available, but ADO.Net does this for us, it happens at a lower level and so is NOT an argument for maintaining a singleton DbContext)
As a safety precaution, the context will actively block any attempts to re-query while an existing query is still pending.
EF implements the Unit-Of-Work pattern, you are only expected to maintain the same context for the current operation and should dispose of it when you are done. It can be perfectly acceptable to instantiate a DbContext scoped for a single method, you could instantiate multiple contexts if you so need them.
There is some anecdotal advice floating around the web based on previous versions of EF that suggest there is a heavy initialization sequence when you create the context and so they encourage the singleton use of the EF context. This advice worked in non-async environments like WinForms apps, but it was never good advice for entity framework.
When using EF in a HTTP based service architecture, the correct pattern is to create a new context for each HTTP request and not try to maintain the context or state between requests. You can manually do this in each method if you want to, however DI can help to minimise the plumbing code, just make sure that the HTTP request gets a new instance, and not a shared or recycled one.
Because most client-side programming can create multiple concurrent HTTP requests (this of a web site, how many concurrent requests might go to the same server for a single page load) it is a frivolous exercise to synchronise the incoming requests, or introduce a blocking pattern to ensure that the requests to the DbContext are synchronous or queued.
The overheads to creating a new context instance are expected to be minimal and the DbContext is expected to be used in this way especially for HTTP service implementations, so don't try to fight the EF runtime, work with it.
Repositories and EF
When you are using a repository pattern over the top of EF... (IMO an antipattern itself) it is important that each new instance of the repository gets its own unique instance of the DbContext. Your repo should function the same if you instead created the DbContext instance from scratch inside the Repo init logic. The only reason to pass in the context is to have DI or another common routine to pre-create the DbContext instance for you.
Once the DbContext instance is passed into the Repo, we lose the ability to maintain synchronicity of the queries against it, this is an easy pain point that should be avoided.
No amount of await or using synchronous methods on the DbContext will help you if multiple repos are trying to service requests at the same time against the same DbContext.

Why is DBContext is disposed after putting it in IMemoryCache (.NET Core / EF Core)

I'm trying to put a subset of db-data in an IMemoryCache, but the 2nd time I call the application, I get an error:
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.
Object name: 'WebDbContext'.
My Code snippet:
public class ArticleRepository : IArticleRepository
{
private readonly WebDbContext _WebDbContext;
private readonly IMemoryCache _cache;
public ArticleRepository(WebDbContext WebDbContext, IMemoryCache cache)
{
_WebDbContext = WebDbContext;
_cache = cache;
}
public IQueryable<Articles> WebshopArticles
{
get
{
return _cache.GetOrCreate("WebshopArticles", entry =>
{
entry.AbsoluteExpirationRelativeToNow = TimeSpan.FromHours(1);
return _WebDbContext.Article.Include(s => s.Details);
});
}
}
public IQueryable<Articles> GetArticles(string category)
{
return WebshopArticles.FirstOrDefault(s => s.Category == Category);
}
}
It looks like DBContext is disposed after the first time I put it in cache. How can i handle this?
You're using dependency injection to get an instance of your WebDbContext through your constructor. ASP.NET Core does this by initiating a WebDbContext object for you and injecting it into the constructor call when it creates an instance of your repository class.
But that WebDbContext object is only available for the life of the current HTTP request. Once that HTTP request is complete, ASP.NET Core gets rid of it. That's why you see it disposed.
Update: I see what you're doing. The problem is here:
return _WebDbContext.Article.Include(s => s.Details);
That does not cache the data. That caches the query (IQueryable). The query doesn't get executed until you enumerate that (loop through it). This is refered to as "lazy loading". So your GetArticles actually performs the query again each time it's called.
The first time you use it (in the same HTTP request you cached it), it works. But when you use it the second time, the context is disposed and the query can't be executed.
You need to force it to execute the query right away. An easy way is to call ToList():
return _WebDbContext.Article.Include(s => s.Details).ToList();
You'll need to change the property type to IEnumerable too.

AsyncLocal with ASP.NET Core Controller/ServiceProviderScope

It seems like the execution context is not kept until Dispose is called on elements resolved in the controller scope. This is probably due to the fact that asp.net core has to jump between native and managed code and resets the execution context at each jump. Seems like the correct context is not restored any more before the scope is disposed.
The following demonstrates the issue - simply put this in the default asp.net core sample project and register TestRepo as a transient dependency.
When calling GET api/values/ we set the value for the current task to 5 in a static AsyncLocal at the start of the call. That value flows as expected through awaits without any problem. But when the controller and its dependencies are disposed after the call the AsyncLocal context is already reset.
[Route("api/[controller]")]
public class ValuesController : Controller
{
private readonly TestRepo _testRepo;
public ValuesController(TestRepo testRepo) => _testRepo = testRepo;
[HttpGet()]
public async Task<IActionResult> Get()
{
_testRepo.SetValue(5);
await Task.Delay(100);
var val = _testRepo.GetValue(); // val here has correctly 5.
return Ok();
}
}
public class TestRepo : IDisposable
{
private static readonly AsyncLocal<int?> _asyncLocal = new AsyncLocal<int?>();
public int? GetValue() => _asyncLocal.Value;
public void SetValue(int x) => _asyncLocal.Value = x;
public void Foo() => SetValue(5);
public void Dispose()
{
if (GetValue() == null)
{
throw new InvalidOperationException(); //GetValue() should be 5 here :(
}
}
}
Is this intentional? And if yes is there any workaround around this problem?
The behavior you are seeing is an unfortunate quirk in the way that ASP.NET Core works. It's unclear to me why Microsoft choose this behavior, but it seems copied from the way Web API worked, which has the exact behavior. Disposing is obviously done at the end of the request, but for some reason the asynchronous context is already cleared before that point, making it impossible to run the complete request in a single asynchronous context.
You've basically got two options:
Instead of using ambient state to share state, flow state through the object graph instead of using ambient state. In other words, make TestRepo Scoped, and store value in a private field.
Move the operation that uses that value to an earlier stage in the request. For instance, you can define some middleware that wraps a request and invokes that operation at the end. At that stage, the asynchronous context will still exist.
Some DI containers actually apply this second technique. Simple Injector, for instance, uses scoping that is based on ambient state, using AsyncLocal<T> under the covers. When integrated in ASP.NET Core, it will wrap the request in a piece of middleware that applies this scope. This means that any Scoped component, resolved from Simple Injector, will be disposed before the ASP.NET Core pipeline disposes its services, and this happens while the asynchronous context is still available.

Unable to cast object of type 'System.Data.ProviderBase.DbConnectionClosedConnecting' to type 'System.Data.SqlClient.SqlInternalConnectionTds

I am getting the following error on the first db access after the application starts - "Unable to cast object of type 'System.Data.ProviderBase.DbConnectionClosedConnecting' to type 'System.Data.SqlClient.SqlInternalConnectionTds"
The error only thrown once, at the first method tries to read data from the database, after the application starts.
Re-calling the same method for the 2nd time and further, everything works fine.
Using .net core 1.1 with Entity Framework
I recently had this same exception in an ASP.NET Core 2 app with EF Core. In my case, the root cause was a problem with the scopes my dependency-injected DbContext. I had a controller and a service both using an injected DbContext. The service was a singleton, like this:
public class TestService{
public TestService(FooDbContext db)
{
this.db = db;
}
}
public class FooController{
public FooController(FooDbContext db, TestService testService)
{
this.testService = testService;
this.db = db;
}
}
public class Startup{
public void ConfigureServices(IServiceCollection services){
//...
services.AddDbContext<FooDbContext>(options =>
options
.UseSqlServer(Configuration.GetConnectionString("FooDbContext"))
);
services.AddSingleton<TestService>();
}
}
So the controller would use it's instance, and then if the singleton service also tried to use it's own instance, then it would give the error above about 90% of the time. I'm a little fuzzy on why this would be an issue, or be intermittent, but it became pretty clear in debugging that EF was reusing some underlying resources. I didn't dig into EF code debugging, but I suspect the controller instance was closed, and the service instance reused the connection, expecting it to be open. In reading, others suggested MultipleActiveResultSet=true in the connection string would fix, but this did not resolve the issue in my case. In my case, the fix was to change the service to Transient in Startup.cs, which was acceptable in this case, and possibly better:
services.AddTransient<TestService>();

Is correct to use transaction inside a Hub on SignalR

I am learning SignalR and I have some doubts about it.
First: Does the Hub works as static object? This question moves to the next question.
Second: Is right to start a transaction inside a method inside the hub?
I want to use the SignalR to send and save information in real time. For example, I want to create a chat, and, when the server receive the message, it saves in the database.
My question is about the method that receive the message will be in memory forever (while the webapp is running).
My concern is about the transaction/connection. Does the transaction will be always active?
For example:
public void Send(string name, string message)
{
Message m = new Message() { n = name, m = message};
using(Entities db = new Entities()
{
db.Messages.Add(m);
db.Save();
}
// Call the addNewMessageToPage method to update clients.
Clients.All.addNewMessageToPage(name, message);
}
I am using EntityFramework 6 and SignalR 2.
Hub instances are created for each request. You can read more about hub instance lifetime here.
EF is creating a transaction each time it needs to save changes. However the transactions created by EF are committed/rolled back and disposed once saving changes is completed and transactions do not leak outside the SaveChanges call. You are also disposing your context (which is good) so you do not leak transactions or connections. (I actually don't hub instance lifetime is relevant at all in your case since you don't try to store anything in class variables).

Categories

Resources