I'm using EF Core with .NET Core 5 and have a database connection that is dependency injected into my different controllers. Here's how the database context is created via ConfigureServices:
public void ConfigureServices(IServiceCollection services)
{
services.AddDbContext<MyDbContext>(opt =>
{
opt.UseMySql(Settings.Instance.SQLConnectionString, ServerVersion.AutoDetect(Settings.Instance.SQLConnectionString), b =>
{
b.UseNewtonsoftJson();
})
});
}
This works great, but I've realized this code runs for every API method that gets called, even those that don't need a database context. For example, this controller has an empty constructor and no database context usage, but still calls UseMySql:
[Produces("application/json")]
[Route("client")]
public class SimpleClientController : Controller
{
[HttpPost("GetTime")]
public IActionResult GetTime([FromBody] GetTimeRequest request)
{
// return the current UTC server time
return Json(new GetTimeResponse()
{
Time = DateTime.UtcNow
});
}
}
In general this isn't a big deal, but some issues were highlighted during the recent us-east-2 AWS outage, which showed that methods that do not rely on the database were blocked by the lack of database connection. My redis/dynamodb methods do not suffer the same fate if there is a redis or dynamodb outage, as they are handled via a singleton service and only used lazily by methods that require them.
Is there a way to do something similar with EF Core and the database context? Ideally we only initialize EF/the database if the controller has to use the context.
Looks like my issue was actually with the automatic version detection, which is creating a connection to MySQL every single time to detect the version. I've now cached the version and it seems to have fixed the issue.
Related
I have a transient EF DB context & a transient service.
I have a controller with an action using session service to update the session, and then use session service to query back the updated session.
However, even I use transient, when I query back the updated session, the session is still the old value but not the updated one. Can someone answer why?
My expected behavior is if DBContext & SessionService are transient, get after my updated session should return a new updated value instead of old value. Because DBContext should be disposed after the update.
Startup.cs
public void ConfigureServices(IServiceCollection services)
{
string connectionString = Configuration.GetConnectionString("ASPState");
services.AddDbContext<ASPStateContext>(options =>
options.UseSqlServer(connectionString, builder=> {
builder.EnableRetryOnFailure(5, TimeSpan.FromSeconds(10), null);
}),ServiceLifetime.Transient);
services.AddTransient<ISessionService, SessionService>();
services.AddControllers();
}
SessionService.cs - using DI
private ASPStateContext aspStateContext;
public SessionService(ASPStateContext dbContext)
{
aspStateContext = dbContext;
}
public async Task<IActionResult> UpdateSession(string id) {
await this.sessionService.UpdateSession(id);
var session = this.sessionService.GetSession(id); // return the value before update
}
ASPStateContext.cs
public partial class ASPStateContext : DbContext
{
public ASPStateContext()
{
}
I think there may be a misunderstanding about how the Transient scope works. A transient dependency is instantiated every time it is injected. Within your controller, a transient and a scoped dependency behave the same way. The difference emerges when another service depends on the transient service; that other service will receive a new instance of the transient service.
Therefore, your controller will use the same SessionService (and the same EF context) for an entire request.
To ensure that Entity Framework is retrieving the latest value from the database, use the AsNoTracking query extension (documentation here). This will completely bypass EF caching and query the underlying database.
Also, make sure that your EF call to save changes after updating the session data is awaited. Otherwise, your SELECT statement may execute before the UPDATE statement is applied.
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.
I've got a Blazor server app using MVVM with Entity Framework. I've got a scenario where the user can change the search criteria very quickly resulting in a second async call to EF before the first call is complete causing the following exception from EF: "There is already an open DataReader associated with this Connection which must be closed first". I can get around it by creating scope for the DbContext with IServiceScopeFactory but it got me thinking that if I need this work around I might not have things set up in the best way possible.
My view model is scoped which gets a reference to my model (also scoped) via constructor injection, and the model gets a reference to the EF context (transient) also via constructor injection. Given that I'm using Blazor server, scoped vs transient isn't really going matter in my scenario because there's only one request for the object instances. So my question is this: Am I appropriately preventing a second async call to EF by using IServiceScopeFactory or is there a better way?
Thanks in advance for your thoughts.
In a normal Blazor componenent, I'd just use a bool flag, something like the following. I'd rather eat my keyboard than go back to MVVM/MVC, so this is the best I can offer you:
#inject MyDataService DBS
<input #bind="SearchString"/><button #onclick="DoSearch">Search</button>
#code{
string SearchString {get; set;} = "";
bool IsSearching;
async Task DoSearch (){
if (!IsSearching){
IsSearching = true;
var results = await DBS.DoDataBaseSearch(SearchString);
IsSearching = false;
}
}
}
Microsoft's best practice for dealing with this situation is to use DbContextFactory.
ASP.NET Core Blazor Server with Entity Framework Core
Pre-.NET 5 you have to implement IDbContextFactory but the code is in the documentation if you switch the version drop down in the link to 3.1. .NET 5 and later it's implemented for you.
Startup.cs
services.AddDbContextFactory<MyContextClass>(options =>
{
options.UseSqlServer(Configuration["MyConnectionString"]);
}, ServiceLifetime.Transient);
Data access class constructor:
public MyDataAccessClass(IDbContextFactory<MyContextClass> contextFactory)
{
_contextFactory = contextFactory;
}
Data access method:
using var context = _contextFactory.CreateDbContext();
Then use the context like you normally would. This is a great solution for controlling the lifespan of the DbContext instance in Blazor server given the persistent connection.
How would I go about setting and accessing application-wide variables in ASP.NET Core 2.0?
Details:
I have a variable, let's call it CompanyName, which resides in the database and is used on literally every page. I don't want to hit the database every time I need to display the CompanyName. 100 years ago, I would have set Application["CompanyName']=CompanyName but I understand that this is not the way to do things in .NET Core. What would be the alternative?
A lot has progressed in the last 100 years. Some time ago, I believe in ASP.NET 1.0, the Application object in ASP classic was superseded with caching (although the Application object was left in for backward compatibility with ASP classic).
AspNetCore has replaced the caching mechanism of ASP.NET and made it DI-friendly, but it is still very similar to how the state of things was in ASP.NET. The main difference is that you now need to inject it instead of using the static HttpContext.Current.Cache property.
Register the cache at startup...
using Microsoft.AspNetCore.Builder;
using Microsoft.Extensions.DependencyInjection;
public class Startup
{
public void ConfigureServices(IServiceCollection services)
{
services.AddMemoryCache();
services.AddMvc();
}
public void Configure(IApplicationBuilder app)
{
app.UseMvcWithDefaultRoute();
}
}
And you can inject it like...
public class HomeController : Controller
{
private IMemoryCache _cache;
public HomeController(IMemoryCache memoryCache)
{
_cache = memoryCache;
}
public IActionResult Index()
{
string companyName = _cache[CacheKeys.CompanyName] as string;
return View();
}
Then to make it work application wide, you can use a filter or middleware combined with some sort of cache refresh pattern:
Attempt to get the value from the cache
If the attempt fails
Lookup the data from the database
Repopulate the cache
Return the value
public string GetCompanyName()
{
string result;
// Look for cache key.
if (!_cache.TryGetValue(CacheKeys.CompanyName, out result))
{
// Key not in cache, so get data.
result = // Lookup data from db
// Set cache options.
var cacheEntryOptions = new MemoryCacheEntryOptions()
// Keep in cache for this time, reset time if accessed.
.SetSlidingExpiration(TimeSpan.FromMinutes(60));
// Save data in cache.
_cache.Set(CacheKeys.CompanyName, result, cacheEntryOptions);
}
return result;
}
Of course, you could clean that up and make a service with strongly typed properties as a wrapper around your cache that is injected into controllers, but that is the general idea.
Note also there is a distributed cache in case you want to share data between web servers.
You could alternatively use a static method or a statically registered class instance, but do note if hosting on IIS that the static will go out of scope every time the application pool recycles. So, to make that work, you would need to ensure your data is re-populated using a similar refresh pattern.
The primary difference is that with caching there are timeout settings which can be used to optimize how long the data should be stored in the cache (either a hard time limit or a sliding expiration).
You could create a Singleton-class called ApplicationWideSettings. Give that class public Properties. Initialize all the values you need one time and then use them by accesing the only instance of your class via:
ApplicationWideSettings.Instance.PropertyName;
Just make sure the namespace of the ApplicationWideSettings-class is referenced when you want to access it.
I prefer this over global/static settings because you have one class to save all your globally available data.
If you are unsure what a Singleton is I can just suggest you look into this article from Jon Skeet:
C# In Depth: Implementing the Singleton Pattern in C#
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>();