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.
Related
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.
I have a controller in a .NET Core application:
public FriendsController(IFriendRepository friendRepository)
{
this.friendRepository= friendRepository;
}
The IFriendRepository is an interface which is implemented with the class:
public class FriendRepository : IFriendRepository {
...
}
In Startup I set it up by using the following line in ConfigureServices() :
services.AddScoped<IFriendRepository , FriendRepository >();
However, when the controller is used, FriendRepository is has the lifetime set as a singleton instead of scoped. The reason I was able to find was on this page:
https://learn.microsoft.com/en-us/aspnet/core/fundamentals/dependency-injection?view=aspnetcore-3.1
Under Service lifetimes, Scoped. It shows:
I do not understand how to use Invoke instead of a constructor. The example they use is for a custom middleware, which I at least can't wrap my head on how to interpret it for a constructor.
public class FriendRepository : IFriendRepository
{
private readonly ManagementDbContext dbContext;
public FriendRepository(ManagementDbContext dbContext)
{
this.dbContext = dbContext;
}
public void Add(Friend friend)
{
this.dbContext.Friends.Add(friend);
}
public void Remove(Friend friend)
{
this.dbContext.Remove(friend);
}
public void Update(Friend friend)
{
this.dbContext.Update(friend);
}
}
The following is "GetFriends", inside FriendRepository:
public async Task<QueryResult<Friend>> GetFriendsAsync(FriendQuery queryObj)
{
var result = new QueryResult<Friend>();
var query = dbContext.Friends
.Include(c => c.Type)
.AsQueryable();
if(queryObj.TypeId.HasValue)
{
query = query.Where(c => c.Type.Id == queryObj.TypeId);
}
if(queryObj.Name != null && queryObj.Name.Length > 0)
{
query = query.Where(c => c.Name.Contains(queryObj.Name));
}
// todo add total price here
var columnsMap = new Dictionary<string, Expression<Func<Calculation, object>>>()
{
["id"] = c => c.Id,
["name"] = c => c.Name,
["type"] = c => c.Type,
["totalFriends"] = c => c.TotalFriends,
["createdTime"] = c => c.CreatedTime
};
query = query.ApplyOrdering(queryObj, columnsMap);
result.TotalItems = await query.CountAsync();
query = query.ApplyPaging(queryObj);
result.Items = await query.ToListAsync();
return result;
}
I solved it, I will first explain my assumption, since the fix might be very much limited to my scenario.
I have all of my DBContext used in 3 repositories. They all use async functions however they all contain awaits inside for any of the async functions used inside of them.
The issue seemed to only occur once I started using these repositories as before I was accessing the dbContext directly in the Controller. This made me consider the problems in the link, which I also posted a picture of in the question:
https://learn.microsoft.com/en-us/aspnet/core/fundamentals/dependency-injection?view=aspnetcore-3.1
Even though it specified middle ware only, I assumed it was worth a chance since I couldn't figure any other problem.
Now as for the actual problem. One of my functions in the UserRepository, GetUser() is an async method, and even though the error seemed to be in the FriendRepository methods, since they were always the ones crashing, it turns out that the GetUser() function was used once in startup under AddJwtBearer without await.
I had assumed that since it had an await inside of it, it would not create a problem. I also had not noticed this was a problem since I was so focused on the other repository. My hope was that maybe I was missing something as simple as the dependency injection through a constructor in middleware switching lifetime regardless of what the lifetime was already set to.
For anyone else in the future, I ended up doing 2 things which allowed me to clearly debug my application step by step.
I created a Logger static class which allows me to save text to file easily. I use this to log functions being used, constructors etc. This let me ensure that I could track the amount of times constructors and functions were called, in what order and which ones would not be reached. Here is the Logger for anyone else:
public static class Logger
{
public static void Log(string text, string fileName)
{
string path = System.IO.Path.GetDirectoryName(Assembly.GetEntryAssembly().Location) + "/" + fileName;
bool done = false;
while (!done)
{
done = true;
try
{
FileStream fileStream = null;
fileStream = System.IO.File.Open(path, System.IO.File.Exists(path) ? FileMode.Append : FileMode.OpenOrCreate);
using (StreamWriter fs = new StreamWriter(fileStream))
{
fs.WriteLine(text);
};
fileStream.Close();
}
catch (IOException)
{
done = false;
}
}
}
public static void Log(string text)
{
Log(text, "logger.txt");
}
}
I added a string to the DBContext and whenever I use it in any function I would add the name of the function after the name of the class it is used in. So if my FriendsRepository would use it in a function GetTypes, it would look like:
myDbContext.methodUsing = "FriendsRepository>GetTypes()";
Thank you to #GuruStron for being patient and giving me advice on how to take this step by step, explaining to me that the middleware error idea had no leg to stand on and suggesting to me on how to approach debugging.
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;
}
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();
}
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
}
}