Method context.Orders.RemoveRange raised InvalidOperationException - c#

Method context.Orders.RemoveRange raised InvalidOperationException. It called from multiple tasks. I tried to lock context.Orders.RemoveRange but the same exception was raised.
Exception is:
InvalidOperationException: A second operation started on this context before a previous operation completed. Any instance members are not guaranteed to be thread safe.
This is the source code where an exception was raised
public class Foo : IFoo
{
private MyContext context;
public Foo(MyContext context)
{
this.context = context;
}
public async Task Update(Order order)
{
context.Orders.RemoveRange(context.Orders.Where(r => r.CustomerID == 100));
context.Orders.RemoveRange(context.Orders.Where(r => r.CustomerID == 120));
order.EmployeeID = 2;
context.Update(order);
await context.SaveChangesAsync();
}
}
Exception stacktrace:
at Microsoft.EntityFrameworkCore.Internal.ConcurrencyDetector.EnterCriticalSection()
at Microsoft.EntityFrameworkCore.Query.Internal.LinqOperatorProvider.ExceptionInterceptor`1.EnumeratorExceptionInterceptor.MoveNext()
at Microsoft.EntityFrameworkCore.DbContext.RemoveRange(IEnumerable`1 entities)
at WebApplication2.Foo.Update(Order order) in D:\Projects\RemoveRangeIssue\WebApplication2\Foo.cs:line 24
I added the small project to GitHub to reproduce the issue above. Here is a link. It has Task.WaitAll to run both method in two threads.
How can I fix the issue with the method context.Orders.RemoveRange called from multiple tasks without removing Task.WaitAll?

I just realized that the problem is not actually the code that you show here but instead it is this bit that you have used in the GitHub repo:
var task1 = Task.Run(() => _foo.Update(order));
var task2 = Task.Run(() => _foo2.Update(order));
Task.WaitAll(task1, task2);
So here you effectively have two Foo implementations and you want to run a query on both in parallel. Since you are using dependency injection and they are both created in the same scope, they will also resolve the same database context.
Running concurrent queries in the same database context is generally not supported. Entity Framework database contexts use a single underlying database connection and you can only ever have one query run at the same time.
If you absolutely need to have these two queries run at the same time, then the solution is to use separate database contexts which each have their own database connection. To do this, you will need to create a new service scope and resolve the database context from there.
With Microsoft.Extensions.DependencyInjection, this would look like this:
public class Foo : IFoo
{
private readonly IServiceScopeFactory _serviceScopeFactory;
public Foo(IServiceScopeFactory serviceScopeFactory)
{
_serviceScopeFactory = serviceScopeFactory;
}
public async Task Update(Order order)
{
using (var scope = _serviceScopeFactory.CreateScope())
{
var context = scope.ServiceProvider.GetService<MyContext>();
// this context is now separate from others
// …
}
}
}
You would have to check with the Autofac documentation to see how that is done there.
Alternatively, you could also keep the Foo implementation the way it is and instead resolve Foo from within a new scope (which would then pull in the context from that same scope). This moves the service scope creation into the caller of Foo which might be a better thing to do depending on what Foo’s responsibility actually is.
public class ExampleController : ControllerBase
{
private readonly IServiceScopeFactory _serviceScopeFactory;
public ValuesController(IServiceScopeFactory serviceScopeFactory)
{
_serviceScopeFactory = serviceScopeFactory;
}
[HttpPost]
public ActionResult DoStuff()
{
var task1 = Task.Run(async () =>
{
using (var scope = _serviceScopeFactory.CreateScope())
{
var foo = scope.ServiceProvider.GetService<IFoo>();
await foo.Update();
}
});
var task2 = Task.Run(async () =>
{
using (var scope = _serviceScopeFactory.CreateScope())
{
var foo = scope.ServiceProvider.GetService<IFoo>();
await foo.Update();
}
});
await Task.WhenAll(task1, task2);
return Ok();
}
}
context.Orders.Where(r => r.CustomerID == 100)
This will just return an IQueryable that represents the query but was not yet executed. When you then implicitly iterate that queryable with RemoveRange, the query is then executed.
This is generally not a good idea with EntityFramework. You should always explicitly execute a query using ToListAsync() or ToArrayAsync():
public async Task Update(Order order)
{
var ordersToRemove = await context.Orders
.Where(r => r.CustomerID == 100 || r.CustomerID == 120)
.ToListAsync();
context.Orders.RemoveRange(ordersToRemove);
order.EmployeeID = 2;
context.Update(order);
await context.SaveChangesAsync();
}

Related

Run an async method only once, and return the same result to all concurrent and future calls [duplicate]

This question already has answers here:
Enforce an async method to be called once
(4 answers)
Closed 6 months ago.
I'm writing an ASP.net Core 6 application (but the question is more about C# in general) where I have a controller action like this:
[HttpGet]
public async Task<IActionResult> MyAction() {
var result = await myService.LongOperationAsync();
return Ok(result);
}
Basically, the action calls a service that needs to do some complex operation and can take a bit of time to respond, up to a minute. Obviously, if in the meantime another request arrives a second run of LongOperationAsync() starts, consuming even more resources.
What I would like to do is redesign this so that the calls to LongOperationAsync() don't run in parallel, but instead wait for the result of the first call and then all return the same result, so that LongOperationAsync() only runs once.
I thought of a few ways to implement this (for example by using a scheduler like Quartz to run the call and then check if a relevant Job is already running before enqueueing another one) but they all require quite a bit of relatively complicated plumbing.
So I guess my questions are:
Is there an established design pattern / best practice to implement this scenario? Is it even practical / a good idea?
Are there features in the C# language and/or the ASP.net Core framework that facilitate implementing something like this?
Clarification: basically I want to run the long-running operation only once, and "recycle" the result to any other call that was waiting without executing the long-running operation again.
You could use an async version of Lazy<T> to do this.
Stephen Toub has posted a sample implementation of LazyAsync<T> here, which I reproduce below:
public class AsyncLazy<T> : Lazy<Task<T>>
{
public AsyncLazy(Func<T> valueFactory) :
base(() => Task.Run(valueFactory))
{ }
public AsyncLazy(Func<Task<T>> taskFactory) :
base(() => Task.Run(taskFactory))
{ }
public TaskAwaiter<T> GetAwaiter() { return Value.GetAwaiter(); }
}
You could use it like this:
public class Program
{
public static async Task Main()
{
var test = new Test();
var task1 = Task.Run(async () => await test.AsyncString());
var task2 = Task.Run(async () => await test.AsyncString());
var task3 = Task.Run(async () => await test.AsyncString());
var results = await Task.WhenAll(task1, task2, task3);
Console.WriteLine(string.Join(", ", results));
}
}
public sealed class Test
{
public async Task<string> AsyncString()
{
Console.WriteLine("Started awaiting lazy string.");
var result = await _lazyString;
Console.WriteLine("Finished awaiting lazy string.");
return result;
}
static async Task<string> longRunningOperation()
{
Console.WriteLine("longRunningOperation() started.");
await Task.Delay(4000);
Console.WriteLine("longRunningOperation() finished.");
return "finished";
}
readonly AsyncLazy<string> _lazyString = new (longRunningOperation);
}
If you run this console app, you'll see that longRunningOperation() is only called once, and when it's finished all the tasks waiting on it will complete.
Try it on DotNetFiddle
As Matthew's answer points out, what you're looking for is an "async lazy". There is no built-in type for this, but it's not that hard to create.
What you should be aware of, though, is that there are a few design tradeoffs in an async lazy type:
What context the factory function is run on (the first invoker's context or no context at all). In ASP.NET Core, there isn't a context. So the Task.Factory.StartNew in Stephen Toub's example code is unnecessary overhead.
Whether failures should be cached. In the simple AsyncLazy<T> approach, if the factory function fails, then a faulted task is cached indefinitely.
When to reset. Again, by default the simple AsyncLazy<T> code never resets; a successful response is also cached indefinitely.
I'm assuming you do want the code to run multiple times; you just want it not to run multiple times concurrently. In that case, you want the async lazy to be reset immediately upon completion, whether successful or failed.
The resetting can be tricky. You want to reset only when it's completed, and only once (i.e., you don't want your reset code to clear the next operation). My go-to for this kind of logic is a unique identifier; I like to use new object() for this.
So, I would start with the Lazy<Task<T>> idea, but wrap it instead of derive, which allows you to do a reset, as such:
public class AsyncLazy<T>
{
private readonly Func<Task<T>> _factory;
private readonly object _mutex = new();
private Lazy<Task<T>> _lazy;
private object _id;
public AsyncLazy(Func<Task<T>> factory)
{
_factory = factory;
_lazy = new(_factory);
_id = new();
}
private (object LocalId, Task<T> Task) Start()
{
lock (_mutex)
{
return (_id, _lazy.Value);
}
}
private void Reset(object localId)
{
lock (_mutex)
{
if (localId != _id)
return;
_lazy = new(_factory);
_id = new();
}
}
public async Task<T> InvokeAsync()
{
var (localId, task) = Start();
try
{
return await task;
}
finally
{
Reset(localId);
}
}
}

Method returns before await finishes executing

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.

Entity Framework A second operation was started Error, despite await

I have this list of names (List<string>) and want to map them to entities from the database. If there is an entity with the given name, just return it. If not, create and return it.
public async Task<List<Element>> MapStringsToEntities(List<strings> raws)
{
var result = new List<Elements>();
foreach (var raw in raws)
{
var element = await _context.Elements
.Where(t => t.Name.ToLower().Equals(raw))
.SingleOrDefaultAsync();
if (element == null)
{
element = new Element(raw);
await _context.Elements.AddAsync(raw);
}
result.Add(element);
}
await _context.SaveChangesAsync();
return result;
}
Although I'm awaiting the call to SingleOrDefaultAsync() I still get an error:
InvalidOperationException: A second operation was started on this context before a previous operation completed.
This is usually caused by different threads concurrently using the same instance of DbContext.
The class where this method lives has a field of type MyAppContext:
private readonly MyAppContext _context;
And a constructor:
public ElementsService(MuAppContext ctx)
{
this._context = ctx;
}
In Startup.cs, the context is initialized like so:
public void ConfigureServices(IServiceCollection services)
{
services
.AddDbContext<MyAppContext>(options =>
options.UseSqlServer(Configuration["ConnectionString:DbString"])
);
}
Any ideas?
You only get an IEnumerable or IQueryable (or something) back from the .Where and therefore actual execution is still delayed until you ask for it to be enumerated. You need to materialise the result by calling .ToList() (or whatever) to force it to run the query and load the results into memory before you try to use the connection again.

Entity Framework Core Is Not Saving Changing to the Database After Setting the Entity State into Add

In my case, I have a singleton service that depends on a scoped service, which is a DbContext implementation.
The singleton service basically is a data access layer that performs the CRUD operation into the SQL server database.
In the data access layer, I have injected the IServiceScopeFactory to get an instance of my DbContext per request.
The following code block is showing a sample of the data access implementation:
public class Repository<IEntity> : IRepository<IEntity> where IEntity : BaseEntity
{
private readonly IServiceScopeFactory _scopeFactory;
public Repository(
IServiceScopeFactory scopeFactory)
{
_scopeFactory = scopeFactory;
}
public void Add(IEntity entity)
{
using (var scope = _scopeFactory.CreateScope())
{
var _context = scope.ServiceProvider.GetRequiredService<PCPSContext>();
_context.Set<IEntity>().Add(entity);
scope.Dispose();
}
}
public Task<int> SaveChangesAsync()
{
Task<int> result;
using (var scope = _scopeFactory.CreateScope())
{
var _context = scope.ServiceProvider.GetRequiredService<PCPSContext>();
result = _context.SaveChangesAsync();
scope.Dispose();
}
return result;
}
}
Registration of the data access service:
services.AddSingleton(typeof(IRepository<>), typeof(Repository<>));
Registration of the dbContext:
var connection = configuration.GetConnectionString("PCPS_CS");
LogManager.Configuration.Variables["connectionString"] = connection;
services.AddDbContext<PCPSContext>(options =>
options.UseSqlServer(connection, b => b.MigrationsAssembly("PCPS.API")));
The problem is, the changes are not reflected in the SQL server database after saving the changes using EFcore, also there is no exception occurred during the process of adding an entity.
I'm interested to know what causes the data to be not reflected in the database.
The changes are not saved since your are calling the async function without await.
Change:
result = _context.SaveChangesAsync();
to
result = await _context.SaveChangesAsync();
Also there is no need to call for Dispose inside the using block. The object is disposed automatically after the using block ends.
You didn't used await keyword.The await operator suspends evaluation of the enclosing async method until the asynchronous operation represented by its operand completes
public async int SaveChangesAsync()
{
int result;
using (var scope = _scopeFactory.CreateScope())
{
var _context = scope.ServiceProvider.GetRequiredService<PCPSContext>();
result =await _context.SaveChangesAsync();
scope.Dispose();
}
return result;
}

Access dependency-injected Entity Framework database from static class method

In my project, I have a static converter method to convert client and database objects into each other. One of those static methods needs to access the database. Before introducing dependency injection into my project, that was quite simple:
internal async static Task<ViewerColumn> FromClientColumn(ViewerColumnSettings col) {
using MpaContext db = new MpaContext();
return new ViewerColumn() {
// ...
SourceColumnID = await db.SourceColumns
.Where(sc => sc.Key == col.DataField)
.Select(sc => sc.ID)
.SingleAsync()
};
}
I want to change this by introducing dependency injection project-wide. My first approach was to simply add the database context as a separate parameter:
internal async static Task<ViewerColumn> FromClientColumn(ViewerColumnSettings col, MpaContext context) {
using MpaContext db = context;
// ...
}
This, however, leads to problems, if the context from the parameter gets disposed somewhere else. So my idea was to dependency-inject the context to the class inself. This, however, doesn't work, because you obviously can't use parameters for static constructors.
Here's how the method is called (currently with the context parameter):
// Controller method with dependency injection
[HttpPut("ViewerRoles/{vrID}")]
public async Task<ActionResult> UpdateViewSettings(int vrID, ViewerRoleSettings updatedData) {
using MpaContext db = _mpaContext;
await storedViewerRole.ApplyViewerRoleSettingsAsync(updatedData, _mpaContext);
}
// ViewerRole.cs
internal async Task ApplyViewerRoleSettingsAsync(ViewerRoleSettings updatedData, MpaContext context) {
// Create new entries
foreach (Client.ViewerColumnSettings col in updatedData.ViewerColumns) {
ViewerColumns.Add(await ViewerColumn.FromClientColumn(col, context));
}
}
This approach fails, because the context gets disposed in UpdateViewSettings and in FromClientColumn.
What's the best-practice approach for such a case? I could dispose the context only, if it wasn't open beforehand, but that sounds stupid to me.
Dependency Inversion / Dependency Injection does not play well with static.
Make an abstraction and derived implementation with injected context
public class ViewerColumnService : IViewerColumnService {
private readonly MpaContext db ;
public ViewerColumnService (MpaContext db) {
this.db = db;
}
public async Task<ViewerColumn> FromClientColumn(ViewerColumnSettings col) {
return new ViewerColumn() {
// ...
SourceColumnID = await db.SourceColumns
.Where(sc => sc.Key == col.DataField)
.Select(sc => sc.ID)
.SingleAsync()
};
}
}
Register this new service and explicitly inject it where it is needed. Stop manually disposing of the context by wrapping it in a using statement. Let the DI container handle the lifetime of the components.

Categories

Resources