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.
Related
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.
Recently one of our tester reported an error on our app.
this error seems to be related to EF core transaction but we are not using transaction in our code. But we didn't use transaction at all on this app.
This issue seems occurs at random, when multiple user are connected on our app.
The error is:
Error : System.invalidOperationException
BeginExecuteReader require the command to have a transaction when the connection assigned to the command is in a pending local transaction.
The transaction property of the command has not been initialized.
And from the stack trace this error occurs when we simply do in a class called "SurveyOperations":
Survey surveyToSave = await _context.Surveys.FindAsync(id);
in details:
The _context is initialized using asp.net core Dependency injection in the SurveyOperations constructor.
In the startup.cs, SurveyOperations is scoped as "Transient" and the DB connection also.
100% of our EF core calls are async.
We got this error on a Blazor component where we inject SurveyOperations using OwningComponentBase:
#using Microsoft.Extensions.DependencyInjection
#inherits OwningComponentBase
#code{
private SurveyOperations _surveyOperations;
private Survey survey;
protected override async Task OnInitializedAsync()
{
_surveyOperations = ScopedServices.GetRequiredService<SurveyOperations>();
survey = await _surveyOperations.GetSurveyAsync(id);
}
private async Task HandleValidSubmit()
{
// Get sone data from the form on the component
await _surveyOperations.SaveSurvey(survey);
}
}
```
We suspect that EF core is reusing connections but we didn't know how to avoid that.
It seems to me that your design is wrong, and the use of OwningComponentBase does not secure you from the issues aroused from using the DbContext in Blazor. You may resolve this issue in either using the following:
#inherits OwningComponentBase<AppDbContext>
AppDbContext is your DbContext
Or still better, and more appropriate to your design, implement the IDbContextFactory... in that case, you can configure the dbcontext like this:
builder.Services.AddDbContextFactory<ContactContext>(opt =>
opt.UseSqlite($"Data Source={nameof(ContactContext.ContactsDb)}.db"))
See here full explanation: full explanation
And in your SurveyOperations service you can do something like this:
public SurveyOperations(IDbContextFactory<ContactContext> factory)
{
DbFactory = factory;
}
And in your various methods you can do something like this:
using var _context = DbFactory.CreateDbContext();
Survey surveyToSave = await _context.Surveys.FindAsync(id);
Note that the second option render the use of OwningComponentBase superfluous.
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 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>();
I am a bit new to StructureMap's IoC (and IoC in general). From examples, I have my stuff set up like this:
DefaultRegistry:
public DefaultRegistry() {
Scan(
scan => {
scan.TheCallingAssembly();
scan.WithDefaultConventions();
scan.With(new ControllerConvention());
});
For<IRepository>().Use<Repository>().Ctor<string>("connectionString").Is(ConfigurationManager.ConnectionStrings["DBConnection"].ConnectionString);
//For<IExample>().Use<Example>();
}
Then, in each controller that any Action needs the database, I have:
private IRepository _Repository;
public TipsController(IRepository repository)
{
_Repository = repository;
}
When I need to use it, I just do:
data.Information = await _Repository.GetInformationAsync();
When I used ADO.NET, I always had a using statement around everything. I've seen examples of Entity Framework that utilizes the using statement. But, when using EF in conjunction with StuctureMap, do I need to somehow wrap a using statement around it? If so, how do I?
If you create a context and use it within the scope of a single method then it's always recommended that you wrap your DbContext use within a using statement as you mentioned, however when your DbContext life time is not bound to the execution of a single method then you have to dispose of the context youself.
A common pattern (and the one recommended in the StructureMap 3 documentation) is to have harness nested containers that are bound to the HttpContext.
This works by creating a nested container at the beginning of a user's Http request and then disposing of the nested container (and the DbContext instance) at the end of the request.
When using an ORM such as Entity Framework with an IoC Container like StructureMap, you can use this nested container that's bound to the HTTP request to control the lifetime of your DbContext. So as a request begins, a new database connection is created and then close and disposed at the end of the request.
This is probably the most complete tutorial that I've found that best describes setting up nested StructureMap containers that are bound to the Http request, and is almost identical to the way the StructureMap.MVC5 package does it that's referenced in the documentation.
Once this is implemented all you would need to do pull the open database connection from the container and dispose of it at the end of the Http request within application_endrequest in your Global.asax.cs file