Ef Core DbContext tracking across multiple HTTP requests - c#

I have my own simple framework for routing/controllers in C# and .NET Core. I'm using EF Core for the ORM. In Startup.cs I'm configuring it like so:
public void ConfigureServices(IServiceCollection services)
{
services.AddDbContext<Context>(options =>
{
options.EnableSensitiveDataLogging();
options.UseSqlServer(System.Environment.GetEnvironmentVariable("SQL_SERVER_CONNECTION"));
});
}
I'm using dependency injection to get an instance of my DbContext. In my controller actions I do the following:
Action 1:
Do not use AsNoTracking() on my queries
Make changes to a model instance
Do not save changes
Action 2 (another HTTP request):
Do literally anything
Run SaveChangesAsync()on DbContext
The changes made in Action 1 are then persisted. If I severed any relations in Action 1 then I get an error.
I know that by default DbContext is scoped. Do I have to implement some of my own scoping code to ensure that I get a new instance with each HTTP request?
NOTE: I am NOT using MVC, I am using my own little library that I'm developing. I just learned that MVC probably uses IServiceScopeFactory to generate scopes. I am not sure how to use it in middleware though.

I got it. Here's how to wrap a scope with HttpContext in a middleware:
public class Middleware
{
private readonly RequestDelegate _next;
private IServiceScopeFactory _scopeFactory;
public Middleware(RequestDelegate next, IServiceScopeFactory scopeFactory)
{
_next = next;
_scopeFactory = scopeFactory;
}
public async Task Invoke(HttpContext context)
{
using (var scope = _scopeFactory.CreateScope())
{
context.RequestServices = scope.ServiceProvider;
// Do whatever you want here
if (_next != null)
await _next(context);
}
}
}

Related

A second operation started on this context before a previous operation completed. This is usually caused by different threads using the same thread

I have an ASP.NET Core 3.1 Web API project with Microsoft.EntityFrameworkCore version 2.2.6 and Azure SQL as the data source for the application.
I have the following code for setting up the DbContext using a dependency injection Container in the Startup.cs
public static IServiceCollection AddAppDBConfiguration(this IServiceCollection services, IConfiguration configuration, IWebHostEnvironment webHostEnvironment) => services.AddEntityFrameworkSqlServer().AddDbContext<AppDbContext>(appDbContext =>
{
var appDataDBconn = configuration[VaultKeys.DataDBConnString];
if (webHostEnvironment.IsEnvironment(Constants.Local))
{
appDataDBconn = configuration.GetSection(nameof(ApplicationOptions.Local)).Get<Local>().LocalDataDBConn;
}
appDbContext.UseSqlServer(appDataDBconn, sqlServerOptionsAction: sqlOptions =>
{
sqlOptions.EnableRetryOnFailure(maxRetryCount: 10, maxRetryDelay: TimeSpan.FromSeconds(30), errorNumbersToAdd: null);
});
});
The repository class used in this case is also marked as scoped lifetime in this case.
There is no usage of async and await in the method defined at repository layer level.
[HttpGet("{input1}", Name = "TestMethod")]
[ProducesResponseType(400)]
[ProducesResponseType(500)]
public dynamic TestMethod(string input1) => _testRepo.UpdateData(input1);
public class TestRepo : ITestRepo
{
private static AppDbContext _appDbContext;
public TestRepo(AppDbContext appDbcontext)
{
_appDbContext = appDbcontext;
}
public dynamic UpdateData(string input1) => _appDbContext.TestTable.Where(e => e.TestName == input1).Select(e => e.TestId).FirstOrDefault();
}
In the Azure AppInsights, I came across the below error
Error 500: System.InvalidOperationException for “GET TestController/TestMethod”.
Error Message: 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, however instance members are not guaranteed to be thread safe. This could also be caused by a nested query being evaluated on the client, if this is the case rewrite the query avoiding nested invocations.
Can anyone help me to resolve this issue?
The context should not be static.
From DbContext Lifetime, Configuration, and Initialization:
public class MyController
{
private readonly ApplicationDbContext _context;
public MyController(ApplicationDbContext context)
{
_context = context;
}
}

.Net Core: How do I initialize a singleton that needs a DBContext?

I have a .Net Core service ("MyLookup") that does a database query, some Active Directory lookups, and stores the results to a memory cache.
For my first cut, I did .AddService<>() in Startup.cs, injected the service into the constructors of each of the controllers and views that used the service ... and everything worked.
It worked because my service - and it's dependent services (IMemoryCache and a DBContext) were all scoped. But now I'd like to make this service a singleton. And I'd like to initialize it (perform the DB query, the AD lookups, and save the result to a memory cache) when the app initializes.
Q: How do I do this?
Startup.cs
public void ConfigureServices(IServiceCollection services)
{
services.AddDbContext<MyDBContext>(options =>
options.UseSqlServer(Configuration.GetConnectionString("MyDBContext")));
services.AddMemoryCache();
services.AddSingleton<IMyLookup, MyLookup>();
...
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
...
// Q: Is this a good place to initialize my singleton (and perform the expensive DB/AD lookups?
app.ApplicationServices.GetService<IDILookup>();
OneOfMyClients.cs
public IndexModel(MyDBContext context, IMyLookup myLookup)
{
_context = context;
_myLookup = myLookup;
...
MyLookup.cs
public class MyLookup : IMyLookup
...
public MyLookup (IMemoryCache memoryCache)
{
// Perform some expensive lookups, and save the results to this cache
_cache = memoryCache;
}
...
private async void Rebuild() // This should only get called once, when app starts
{
ClearCache();
var allNames = QueryNamesFromDB();
...
private List<string>QueryNamesFromDB()
{
// Q: ????How do I get "_context" (which is a scoped dependency)????
var allNames = _context.MyDBContext.Select(e => e.Name).Distinct().ToList<string>();
return allSInames;
Some of the exceptions I've gotten trying different things:
InvalidOperationException: Cannot consume scoped service 'MyDBContext' from singleton 'MyLookup'.
... and ...
InvalidOperationException: Cannot resolve scoped service 'MyDBContext' from root provider 'MyLookup'
... or ...
System.InvalidOperationException: Cannot resolve scoped service 'IMyLookup' from root provider.
Thanks to Steve for much valuable insight. I was finally able to:
Create a "lookup" that could be used by any consumer at any time, from any session, during the lifetime of the app.
Initialize it once, at program startup.
FYI, it would NOT be acceptable to defer initialization until some poor user triggered it - the initialization simply takes too long.
Use dependent services (IMemoryCache and my DBContext), regardless of those services' lifetimes.
My final code:
Startup.cs
public void ConfigureServices(IServiceCollection services)
{
services.AddDbContext<MyDBContext>(options =>
options.UseSqlServer(Configuration.GetConnectionString("MyDBContext")));
services.AddMemoryCache();
// I got 80% of the way with .AddScoped()...
// ... but I couldn't invoke it from Startup.Configure().
services.AddSingleton<IMyLookup, MyLookup>();
...
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
// This finally worked successfully...
app.ApplicationServices.GetService<IMyLookup>().Rebuild();
OneOfMyClients.cs
public IndexModel(MyDBContext context, IMyLookup myLookup)
{
// This remained unchanged (for all consumers)
_context = context;
_myLookup = myLookup;
...
MyLookup.cs
public interface IMyLookup
{
Task<List<string>> GetNames(string name);
Task Rebuild();
}
public class MyLookup : IMyLookup
{
private readonly IMemoryCache _cache;
private readonly IServiceScopeFactory _scopeFactory;
...
public MyLookup (IMemoryCache memoryCache, IServiceScopeFactory scopeFactory)
{
_cache = memoryCache;
_scopeFactory = scopeFactory;
}
private async void Rebuild()
{
ClearCache();
var allNames = QueryNamesFromDB();
...
private List<string>QueryNamesFromDB()
{
// .CreateScope() -instead of constructor DI - was the key to resolving the problem
using (var scope = _scopeFactory.CreateScope())
{
MyDBContext _context =
scope.ServiceProvider.GetRequiredService<MyDBContext>();
var allNames = _context.MyTable.Select(e => e.Name).Distinct().ToList<string>();
return allNames;
}
}
There is no one single solution to your problem. At play are different principles, such as the idea of preventing Captive Dependencies, which states that a component should only depend on services with an equal or longer lifetime. This idea pushes towards having a MyLookup class that has either a scoped or transient lifestyle.
This idea comes down to practicing the Closure Composition Model, which means you compose object graphs that capture runtime data in variables of the graph’s components. The opposite composition model is the Ambient Composition Model, which keeps state outside the object graph and allows retrieving state (such as your DbContext) on demand.
But this is all theory. At first, it might be difficult to convert this into practice. (Again) in theory, applying the Closure Composition Model is simple, because it simply means giving MyLookup a shorter lifestyle, e.g. Scoped. But when MyLookup itself captures state that needs to be reused for the duration of the application, this seems impossible.
But this is often not the case. One solution is to extract the state out of the MyLookup, into a dependency that holds no dependencies of its own (or only depends on singletons) and than becomes a singleton. The MyLookup can than be 'downgraded' to being Scoped and pass the runtime data on to its singleton dependency that does the caching. I would have loved showing you an example of this, but your question needs more details in order to do this.
But if you want to leave the MyLookup a singleton, there are definitely ways to do this. For instance, you can wrap a single operation inside a scope. Example:
public class MyLookup : IMyLookup
...
public MyLookup (IMemoryCache memoryCache, IServiceScopeFactory scopeFactory)
{
_cache = memoryCache;
_scopeFactory = scopeFactory;
}
private List<string> QueryNamesFromDB()
{
using (var scope = _scopeFactory.CreateScope())
{
var context = scope.ServiceProvider.GetRequiredService<MyDbContext>();
var allNames = context.Persons.Select(e => e.Name).Distinct().ToList<string>();
return allSInames;
}
}
}
In this example, the MyLookup is injected with a IServiceScopeFactory. This allows the creation (and destruction) of an IServiceScope in a single call. Downside of this approach is that MyLookup now requires a dependency on the DI Container. Only classes that are part of the Composition Root should be aware of the existence of the DI Container.
So instead, a common approach is to inject a Func<MyDbContext> dependency. But this is actually pretty hard with MS.DI, because when you try this, the factory comes scoped to the root container, while your DbContext always needs to be scoped. There are ways around this, but I'll not go into those, due to time constrains from my side, and because that would just complicate my answer.
To separate the dependency on the DI Container from your business logic, you would either have to:
Move this complete class inside your Composition Root
or split the class into two to allow the business logic to be kept outside the Composition Root; you might for instance achieve this using sub classing or using composition.

Create EF Core Context once per request in ASP.Net Core

After reading a lot on the subject it looks like a good approach is to create a context once per request.
To achive this, in in Startup.cs I have declared two statics objects
public class Startup
{
public static DbContextOptionsBuilder<MCContext> optionsBuilder = new DbContextOptionsBuilder<MCContext>();
public static MCContext db = null;
then init optionsBuilder when the app starts (so only once):
public Startup(IConfiguration configuration)
{
optionsBuilder.UseSqlServer(configuration["ConnectionStrings:DefaultConnection"]);
}
while db at each request:
app.Use(async (context, next) =>
{
db = db ?? new MCContext(optionsBuilder.Options);
await next.Invoke();
});
Then when I need the context in a controller or in a razor page cs I can get it using Startup.db:
User cur = await Startup.db.User.Where(x => x.Id == uid).FirstOrDefaultAsync();
I do not Dispose the Context as per here
As I'm not familiar with DI I wonder if this approach is correct or if I am missing anything.
base on What is new in EF Core 2.0 - EF Core | Microsoft Docs
If you want a new context once per request : AddDbContext
public void ConfigureServices(IServiceCollection services)
{
services.AddDbContext<MCContext >(
options => options.UseSqlServer(connectionString));
}
then you can
public class TiketsController : ControllerBase
{
private readonly MCContext _context;
public TiketsController (MCContext context)
{
_context = context;
}
}
The basic pattern for using EF Core in an ASP.NET Core application
usually involves registering a custom DbContext type into the
dependency injection system and later obtaining instances of that type
through constructor parameters in controllers. This means a new
instance of the DbContext is created for each requests.
but if you need High Performance/Safe reuse : AddDbContextPool
public void ConfigureServices(IServiceCollection services)
{
services.AddDbContextPool<MCContext >(
options => options.UseSqlServer(connectionString));
}
then you can
public class TiketsController : ControllerBase
{
private readonly MCContext _context;
public TiketsController (MCContext context)
{
_context = context;
}
}
If this method is used, at the time a DbContext instance is requested
by a controller we will first check if there is an instance available
in the pool. Once the request processing finalizes, any state on the
instance is reset and the instance is itself returned to the pool.
If you are not creating object, then don't dispose it. Let IOC container handle it.
btw, I don't think this block of code is required. MCContext is a dependency, so its instance creation and injection gets done by IOC container.
app.Use(async (context, next) =>
{
db = db ?? new MCContext(optionsBuilder.Options);
await next.Invoke();
});

ASP.NET Core OWIN Middleware

I have an ASP.NET Core app and a simple OWIN middleware to check some data. But I'd like to only run the middleware when a page is requested. Right now its running when assets are requested as well such as images, css, etc.
How can I make the owin middleware code only execute on page requests?
Registration:
app.UseSiteThemer();
Site Themer extension class:
public static class SiteThemerExtensions
{
public static IApplicationBuilder UseSiteThemer(this IApplicationBuilder builder)
{
return builder.UseMiddleware<SiteThemerMiddleware>();
}
}
OWIN Middleware:
public class SiteThemerMiddleware
{
private readonly RequestDelegate _next;
private readonly ISiteService _siteService;
public SiteThemerMiddleware(RequestDelegate next, ISiteService siteService)
{
_siteService = siteService;
_next = next;
//_logger = loggerFactory.CreateLogger<SiteThemerMiddleware>();
}
public async Task Invoke(HttpContext context)
{
await Task.Run(() =>
{
Console.Write("OWIN Hit");
});
//_logger.LogInformation("Handling request: " + context.Request.Path);
await _next.Invoke(context);
//_logger.LogInformation("Finished handling request.");
}
}
There are two aspects of ASP.NET Core pipeline you can use for you goal here: ordering and branching.
The rules around ordering are very simple - the order in which middlewares are added is the order in which they are going to be executed. This means that if middleware like yours is placed after some middleware which can end the pipeline (for example static files) it will not be invoked if it happens.
In order to branch the pipeline you can use Map or MapWhen methods. The first branches the pipeline based on path while the other based on predicate. Middlewares added with Map or MapWhen will be invoked only if the branch condition is met.
You can read more details about the pipeline here

Setting up autofac with webapi and an EntityFramework context & repository layer

I am having some trouble with autofac and webapi, I think its due to my lack of understanding on the correct way to register them.
I have a repository layer using entityframework and standard type of repository pattern to interact with the database.
My repository layer, I inject the context via the constructor and also other repositories, in my example below I am passing in the customerRepository.
e.g.
public CustomerTransactionsRepository(MyContext context,
ICustomerRepository customerRepository,
ILog log)
{
_context = context;
_customerRepository = customerRepository;
_log = log;
}
public async Task<CustomerWithTransactions> FindCustomerSalesAsync(int customerId)
{
var customer= await _customerRepository.FindAsync(customerId);
var transactions = await _context.Transactions
.Include(c => c.CancelledSales)
.SingleOrDefaultAsync(c => c.CustomerId== customer.UserId);
return transactions;
}
My startup.cs configuration looks like
private static void RegisterDependences(ContainerBuilder builder)
{
builder.RegisterType<AifsLog>().As<ILog>().InstancePerDependency();
builder.RegisterAssemblyTypes(Assembly.Load("MyNS.DAL"))
.Where(t => t.Name.EndsWith("Repository"))
.AsImplementedInterfaces().InstancePerDependency();
builder.Register(c => new LoggingActionFilter())
.AsWebApiActionFilterFor<ApiController>()
.PropertiesAutowired();
builder.RegisterType<MyContext>().InstancePerRequest();
}
When I try running the application, I get an error
No scope with a Tag matching 'AutofacWebRequest' is visible from the scope in which the instance was requested. This generally indicates that a component registered as per-HTTP request is being requested by a SingleInstance() component (or a similar scenario.) Under the web integration always request dependencies from the DependencyResolver.Current or ILifetimeScopeProvider.RequestLifetime, never from the container itself.
I think its due to using InstancePerDependency for my repository and InstancePerRequest for my dbContext.
Anyone else had this problem or can see what I am doing wrong?

Categories