Concurrency in Entity Framework Core - c#

When interacting with the database, Entity Framework can suddenly throw an "An operation is already in progress." exception. I have done some testing by sending many concurrent requests and come to the conclusion that this is has to do with some requests sharing the same persistence context. I'm using Entity Framework Core with PostgreSQL.
This is how I've configured EF in my startup class:
services.AddEntityFramework()
.AddNpgsql()
.AddDbContext<PersistenceContext>(options =>
{
options.UseNpgsql(Configuration["Database:ConnectionString"]);
});
The PersistenceContext is a class with DbSet< ModelType > for each type that I store in the database. The PersistenceContext are injected using dependency injection. An example of a connection string I'm using now is:
Server=localhost;User Id=myUser;Password=myPasswordDatabase=myDb;
I've also tried to add a Pooling flag to the string without any affect.
An example of a simple controller for a web-API that would throw the exception:
[Route("example")]
public class FooController : Controller
{
private PersistenceContext dbContext;
public ChannelController(PersistenceContext dbContext)
{
this.dbContext = dbContext;
}
[HttpGet]
public async Task<IActionResult> GetFoo()
{
Model.Foo instance = await (from p in dbContext.Foo
select p).SingleOrDefaultAsync();
return Ok(instance);
}
}
Am I missing out on some details for setting up EF or could this be a framework bug? I'm on RC1-final.

Related

.NET Core Entity Framework - asynchronous writting into database

I have a problem. I have ASP .NET Core REST API app and in one mothod I"m trying to write more changes into a DB asynchronously. Everytime the different amount of objects is written into the DB and everytime one of three different errors ocured. Any suggestions what can be wrong?
This is my code:
Startup.cs
public void ConfigureServices(IServiceCollection services)
{
...
services.AddDbContext<MyDbContext>(options => options.UseSqlServer(connection_string), ServiceLifetime.Transient);
services.AddScoped<IHelper, Helper>();
...
}
Helper.cs
private MyDbContext _dbContext;
public Helper(IOptions<HelperSettings> settings, ILogger<Helper> logger, MyDbContext dbContext)
{
...
_dbContext = dbContext;
...
}
public void Save(object entity)
{
...
_dbContext.Add(entity);
}
This is the controller and the method that throws the exceptions.
public class MyController : ControllerBase
{
private readonly Helper _db;
public MyController(IHelper helper)
{
_db = helper;
}
...
[HttpPost]
[Route("something")]
[Produces("application/json")]
public async Task<ActionResult<Guid>> CreateSomethingAsync([FromBody] DataRequest data)
{
...
if (data.Answers != null)
{
List<Task> saveTasks = new List<Task>();
foreach (AnswerData ans in data.Answers)
{
Answer answer = ans.ConvertToAnswer(); //just create new Answer instance and filll it with data from AnswerData
saveTasks.Add(Task.Run(() => _db.Save(answer)));
}
await Task.WhenAll(saveTasks);
await _db.DbContext.SaveChangesAsync();
}
return Ok(...);
}
}
I call the CreateSomethingAsync() in a cycle in another app. It throws one of this three exceptions:
System.IndexOutOfRangeException: 'Index was outside the bounds of the array.'
or
System.InvalidOperationException: 'Operations that change non-concurrent collections must have exclusive access. A concurrent update was performed on this collection and corrupted its state. The collection's state is no longer correct.'
or
System.InvalidOperationException: Cannot start tracking InternalEntityEntry for entity type 'Answer' because another InternalEntityEntry is already tracking the same entity
on the line _dbContext.Add(entity); in my Helper.cs.
I know that the problem is in the paralelism, but I dont know how to solve it. Any ideas?
DbContext is not thread-safe (that's what the exceptions you get are telling you), and calling DbContext.Set<T>.Add() does not take a significant amount of time. By parallelizing the Add() you are not adding multiple entities to the database asynchronously - you're just marking the entities as to-be-added upon calling SaveChanges().
So while I'm sure you have your reasons to parallelize your _db.Save(answer) calls, it probably has no performance improvements whatsoever, so you can remove it entirely, serializing the work.
If the work you do in there does benefit from parallelization, simply move the call to DbContext.Set<T>.Add() out of there - it is not thread safe.

Service lifetime in ASP.NET Core

I have a simple question but didn't find an answer anywhere.
Solution contain two Web API. One is .NET Core 2.1 with EF Core 2.1.1 and the second is 3.1 with EF Core 3.1.1 and my code is the same for both. There are one custom repository and one controller.
Person repository:
public PersonRepository(AppContext appContext)
{
this.appContext = appContext;
}
public async Task<IEnumerable<Person>> GetAll()
{
return await appContext.People.ToListAsync();
}
Controller:
public MyController(PersonRepository personRepository)
{
this.personRepository = personRepository;
}
[HttpGet]
public async Task<ActionResult> Get()
{
var data = personRepository.GetAll();
var data1 = personRepository.GetAll();
var result = await Task.WhenAll(data, data1);
return Ok(data.Result);
}
services.AddDbContext<AppContext>(options => options
.UseSqlServer("")
.EnableSensitiveDataLogging(true));
It might seem nonsense. But this is only for demonstration.
My question is, why this code works in 2.1 solution but in 3.1 not and exception appear
InvalidOperationException: A second operation started on this context before a previous operation completed. (Same for IIS and Kestrel).
I know how to fix it in 3.1 this is not my question. I just need to know why this happened and what's changed between these versions or whenever.
Thank you very much for any response.
If you really want to run both queries in parallel you'd need two DbContexts because DbContext is not thread safe.
You need to change how you register the DbContext in your service container to do this:
services.AddDbContext<AppDbContext>(options => options
.UseSqlServer("")
.EnableSensitiveDataLogging(true),
ServiceLifetime.Transient);
Add the ability for the depedency to create a new instance of the DbContext (a simple factory):
services.AddTransient<Func<AppDbContext>>(provider => provider.GetRequiredService<AppDbContext>);
and change your dependency accordingly:
public PersonRepository(Func<AppContext> appContextFactory)
{
this.appContextFactory = appContextFactory;
}
public async Task<IEnumerable<Person>> GetAll()
{
using (var appContext = appContextFactory())
{
return await appContext.People.ToListAsync();
}
}
Remember that changing the lifetime scope to Transient means that if you inject DbContext in multiple classes within the same request you will not get the same DbContext instance. Use with caution.

EF Core multiple HTTP requests throws an error

I cannot seem to find an answer to this question.
So in the frontend when the user loads a page we call an API for each item on that page (10 items). So that equals 10 API calls.
Most of the calls work but there are always a few that fail when trying to query the database resulting in the following error:
InvalidOperationException: A second operation started on this
context before a previous operation completed. Any instance members
are not guaranteed to be thread safe.
Now I understand that Entity Framework is not thread safe but I am unsure how to get around this error.
Everywhere where I am using a DBContext it is always injected in using the built in .net core Ioc container.
Here is the DI setup
services.AddScoped<IOmbiContext, OmbiContext>();
services.AddTransient<ISettingsRepository, SettingsJsonRepository>();
All of my repositories are setup in a Transient scope with the Context as Scoped according to this article: https://learn.microsoft.com/en-us/aspnet/core/data/entity-framework-6
Now I have tried changing the context to Transient and it still happens.
How can I avoid this?
More Information
The API Method:
[HttpGet("movie/info/{theMovieDbId}")]
public async Task<SearchMovieViewModel> GetExtraMovieInfo(int theMovieDbId)
{
return await MovieEngine.LookupImdbInformation(theMovieDbId);
}
Which eventually calls the following where the exception is being thrown:
public async Task<RuleResult> Execute(SearchViewModel obj)
{
var item = await PlexContentRepository.Get(obj.CustomId); <-- Here
if (item != null)
{
obj.Available = true;
obj.PlexUrl = item.Url;
obj.Quality = item.Quality;
}
return Success();
}
PlexContentRepository
public PlexContentRepository(IOmbiContext db)
{
Db = db;
}
private IOmbiContext Db { get; }
public async Task<PlexContent> Get(string providerId)
{
return await Db.PlexContent.FirstOrDefaultAsync(x => x.ProviderId == providerId); <-- Here
}
If you use Entity Framework Core usually you do not need to add your Database Context as an additional service
I recommend to setup your DbContext in the Startup.cs as following:
services.AddEntityFrameworkSqlServer()
.AddDbContext<OmbiContext>();
Followed by a Controller class for your API calls taking the DBContext as constructor parameter.
public class ApiController : Controller
{
protected OmbiContext ctx;
public ApiController(OmbiContext dbctx)
{
ctx = dbctx;
}
public async Task<IActionResult> yourAsyncAction()
{
// access ctx here
}
}

Confusion implementing Dependency Injection with a repository pattern - c# EF Core

I'm moving the data access portion of a project to a WebAPI and using Entity Framework Core to do what EF does.
I know that the preferred pattern is to now rely on dependency injection to create the dbContext when "the controller" is used. The "Hello, World!" tutorials show examples of accessing the context by the following:
private BloggingContext _context;
public BlogsController(BloggingContext context)
{
_context = context;
}
The issue that I'm having is I need to go one layer up...as I have a "Service" interface that exposes the methods of the repository.
I tried putting the context instantiation into my repository, but, of course, then when I tried to new up an instance of the service, it was requiring a context object to be passed in. I tried to resolve it by sending in a new BloggingContext() as the context parameter, but received the error: No database provider has been configured for this DbContext. Which, of course, makes sense because the new one that I was passing in didn't have a connection to the database associated with it. That connection is defined in the Startup.cs as described in the various tutorials I'm following trying to cut my teeth on this transition.
So, given the following code in my controller, how can I invoke the service and still let the Dependency Injection do what it's designed to do at the repository level?
[HttpGet("{bool}")]
public IActionResult GetRoleNames(bool? isActive)
{
var service = new SecurityService();
try
{
return Ok(service.GetRoleNames(isActive));
}
catch (Exception ex)
{
Log.Error("Failed to get list of role names from the API", ex.Message);
return BadRequest(ex);
}
}
I'm pretty sure I have the [HttpGet("{bool}")] a bit FUBARd as well since GetRoleNames will permit NULL, but I'll cross that bridge after resolving the DI issue.
Use Dependency Injection to create your service (I like to have an interface)-
private readonly ISecurityService _service;
public BlogsController(ISecurityService service)
{
_service = service;
}
Call your method using the constructed service
[HttpGet("{bool}")]
public IActionResult GetRoleNames(bool? isActive)
{
try
{
return Ok(_service.GetRoleNames(isActive));
}
catch (Exception ex)
{
Log.Error("Failed to get list of role names from the API", ex.Message);
return BadRequest(ex);
}
}
Your security service can have the context injected
private readonly BloggingContext _context;
public class SecurityService : ISecurityService
{
public SecurityService (BloggingContext context)
{
_context = context
}
}
You can register your service in Startup.cs in the ConfigureServices method:
service.AddScoped<ISecurityService, SercurityService>();

Only sources that implement IAsyncEnumerable can be used for Entity Framework asynchronous operations

I'm implementing a Model using EF 6.1.3 and .NET Framework 4.6.1.
This model is used by an ASPNET app and by an ASPNET CORE app, for that reason it uses System.Data.Entity and it is located in a separate assembly mymodel.dll.
This is the model
using System.Data.Entity;
public partial class MyDbContext : DbContext
{
public virtual DbSet<Athlete> Athletes{ get; set; }
}
public partial class Athlete
{
public Athlete()
{
}
//...
public string Country { get; set; }
}
I'm developing the MVC app that is implemented in aspnet core with .NET Framework 4.6. It references EF 6.1.3 so that the model can be used.
public class MyViewModel
{
public IList<Athlete> ItalianAthletes{ get; set; }
}
using Microsoft.EntityFrameworkCore;
//solution: comment the previous line and use instead System.Data.Entity;
public class MyController : Controller
{
private readonly MyDbContext _context;
//...
public IActionResult Index()
{
MyViewModel myvm = new MyViewModel();
var result = _context.Athletes.Where(a=>a.Country=="Italy").ToList();
myvm.ItalianAthletes = result ;
return View(myvm);
}
}
... and it works as expected.
Now changing the Index method to async
public async Task<IActionResult> Index()
{
MyViewModel myvm = new MyViewModel();
var result = _context.Athletes.Where(a=>a.Country=="Italy").ToListAsync();
await result; //at this point an exception is thrown
//...
}
InvalidOperationException: The source IQueryable doesn't implement IAsyncEnumerable. Only sources that implement IAsyncEnumerable can be used for Entity Framework asynchronous operations.
Removing the Where() clause the problem persists, so the problem seems related to ToListAsync();
var result = _context.Users.ToListAsync();
Carefully reading the text of the exception I understand that "the IQueryable generated by ToList() doesnt implement IAsyncEnumerable " but this doesnt make sense to me because all that behavior is internal to ToListAsync();
Someone can help me to better understand what's happening here under the hood? and what can I do so that ToListAsync() works as expected ?
thank you in advance for any comment
If you are using Entity Framework Core then you have to use this namespace:
using Microsoft.EntityFrameworkCore;
instead of
using System.Data.Entity;
EF Core Solution
You need to use this using statement.
using Microsoft.EntityFrameworkCore;
EF6 Solution
You will want to do one of these 2 things.
Reference the EF nuget package in both assemblies. This is because this ToListAsync() operation is actually being called through to your EF DbContext and this cannot be done from a project that has no reference to the EF NugetPackage. If this is already the case make sure you are referencing the namespace System.Data.Entity in the using statements at the top of the code:
using System.Data.Entity;
as this is the location of the extension method ToListAsync you want to call.
Wrap the code that retrieves from EF in a service in your project that uses EF, make the call async, and call that from your asp.net mvc project. This would be my preferred choice as it adds a nice layer of abstraction and makes your code easier to test/maintain.
Code example for 2nd option
public interface IAthleteService {
Task<List<Athlete>> GetAthletesByCountryAsync(string country, CancellationToken token);
}
public class AthleteService : IAthleteService {
private MyDbContext _context;
public async Task<List<Athlete>> GetAthletesByCountryAsync(string country, CancellationToken token)
{
return await _context.Athletes.Where(athlete => athlete.Country == country).ToListAsync(token).ConfigureAwait(false);
}
}
public class MyController : Controller
{
private readonly IAthleteService _service;
//...
public async Task<IActionResult> Index(CancellationToken token)
{
MyViewModel myvm = new MyViewModel();
myvm.ItalianAthletes = await _service.GetAthletesByCountryAsync("Italy", token).ConfigureAwait(true);
// rest of code
}
}
Notes:
I used a CancellationToken, it allows for the cancellation of an async operation. This is completely optional.
I used ConfigureAwait, this allows you to specify whethere the same thread context should be recaptured when the operation resumes. It saves resources to not do it (pass false) but you can only do that when its possible. In the above example its done in the library. Also in the example above it is not done from the Controller because you need the Http context associated with the thread (pass true).
I did not take into account cleaning up of resources (like making AthleteService disposable to cleanup the DbContext) or any injection of dependencies.
This topic is old but I ran into the same error in the year 2021 for same or different reasons.
In short: Get rid of .ToListAsync() and replace it with .ToList()
I ran into this issue in my .NET5 project by using EntityFrameworkCoreMock.Moq in a unit test project and failed with this error. I asked a friend and was told to get rid of ToListAsync(). I decided to also get rid of AsyncFixer for that reason.
I'm not really happy with that solution either and hate code that fails at runtime for strange reasons in general. But that's the solution I ended up with.

Categories

Resources