I'm running into this error:
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,
I have a Blazor Server app where I have a SideBar with a TreeView. Then I also load my menu items on my Index page via Cards.
The initial load is fine because the menu items are generated in memory and not the database.
On the initial login success, I am seeing the error due to the Sidebar page and then the Index page calling the same database call to get the menu items.
If I refresh the page, everything is fine. Then the menu items are added to the memory cache and work thereafter.
I've tried this link, but it did not work.
Here is my menu code:
public class MenuUtilities : IMenuUtilities
{
private bool disposedValue;
private readonly IMenuRepo menuRepo;
private readonly IMemoryCache memoryCache;
private readonly ILogger logger;
public MenuUtilities(IMenuRepo menuRepo, IMemoryCache memoryCache, ILogger<MenuUtilities> logger)
{
this.menuRepo = menuRepo;
this.memoryCache = memoryCache;
this.logger = logger;
}
public async Task<IEnumerable<MenuItemDTO>> GetMenuItems(bool isLoggedIn, Guid? userId, string fromLocation)
{
logger.LogInformation(message: $"fromLocation: {fromLocation} | isLoggedIn: {isLoggedIn}");
var menuItems = new List<MenuItemDTO>();
// Check if the item is in the cache
if (memoryCache.TryGetValue(CacheConstants.MenuItems, out List<MenuItemDTO>? cachedItems))
{
// Item is in the cache, use it
// You can access cachedItems here
// ...
menuItems = cachedItems;
}
else
{
menuItems.Add(new MenuItemDTO { MenuItemId = -1, MenuItemParentId = null, Name = "Home", IconTxt = "icon-microchip icon", NavigationUrlTxt = "/" });
if (isLoggedIn)
{
var items = await menuRepo.GetUserMenuItems(userId).ConfigureAwait(false);
menuItems.AddRange(items);
memoryCache.Set(CacheConstants.MenuItems, menuItems, TimeSpan.FromMinutes(10));
}
else
{
menuItems.Add(new MenuItemDTO { MenuItemId = 10000, MenuItemParentId = null, Name = "Registration", IconTxt = "icon-circle-thin icon", NavigationUrlTxt = "/Account/Register" });
menuItems.Add(new MenuItemDTO { MenuItemId = 10001, MenuItemParentId = null, Name = "Login", IconTxt = "bi bi-person icon", NavigationUrlTxt = "/Account/Login" });
}
}
return menuItems;
}
protected virtual void Dispose(bool disposing)
{
if (!disposedValue)
{
if (disposing)
{
// TODO: dispose managed state (managed objects)
}
// TODO: free unmanaged resources (unmanaged objects) and override finalizer
// TODO: set large fields to null
disposedValue = true;
}
}
public void Dispose()
{
// Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method
Dispose(disposing: true);
GC.SuppressFinalize(this);
}
}
Is there a way I can check to see if the call is still running?
I've added some logs and here are the time differences:
Not sure what I can add to delay the SideBar loading call?
The Sidebar page and Index page both have the same code as such:
@code {
private IList<MenuItemInfoModel> MenuData = new List<MenuItemInfoModel>();
protected override async Task OnInitializedAsync()
{
await LoadData();
}
private async Task LoadData()
{
if (menuUtilities is not null)
{
var menuItems = (await menuUtilities.GetMenuItems(await UserUtility.IsUserAuthenicated(AuthenticationStateProvider).ConfigureAwait(false), await UserUtility.GetCurrentUserId(AuthenticationStateProvider).ConfigureAwait(false), "cardMenu_Call").ConfigureAwait(false)).ToList();
MenuData = mapper.Map<IList<MenuItemInfoModel>>(menuItems.ToList());
}
}
}
UPDATE 1
Here is the database call:
public async Task<IList<MenuItemDTO>> GetUserMenuItems(Guid? userId)
{
var menuItemsWithRoles = await Context.MenuItems
.Join(
Context.MenuItemsToRoles,
menuItem => menuItem.MenuItemId,
menuItemRole => menuItemRole.MenuItemId,
(menuItem, menuItemRole) => new { menuItem, menuItemRole }
)
.Join(
Context.UserRoles,
combined => combined.menuItemRole.RoleId,
userRole => userRole.RoleId,
(combined, userRole) => new { combined.menuItem, combined.menuItemRole, userRole }
)
.Where(combined => !userId.HasValue || combined.userRole.UserId == userId.Value)
.Select(menuItem => new MenuItemDTO
{
MenuItemId = menuItem.menuItem.MenuItemId,
MenuItemParentId = menuItem.menuItem.MenuItemParentId,
Name = menuItem.menuItem.Name,
DescriptionTxt = menuItem.menuItem.DescriptionTxt,
NavigationUrlTxt = menuItem.menuItem.NavigationUrlTxt,
IsActiveInd = menuItem.menuItem.IsActiveInd,
SortOrder = menuItem.menuItem.SortOrder,
IconTxt = menuItem.menuItem.IconTxt,
CreatedById = menuItem.menuItem.CreatedById,
CreatedByNm = $"{menuItem.menuItem.CreatedByUserNavigation.FirstName} {menuItem.menuItem.CreatedByUserNavigation.LastName}",
CreatedDate = menuItem.menuItem.CreatedDate,
ModifiedById = menuItem.menuItem.ModifiedById,
ModifiedByNm = $"{menuItem.menuItem.ModifiedByUserNavigation.FirstName} {menuItem.menuItem.ModifiedByUserNavigation.LastName}",
ModifiedDate = menuItem.menuItem.ModifiedDate,
SoftDeletedById = menuItem.menuItem.SoftDeletedById,
SoftDeletedByNm = GetSoftDeletedName(menuItem.menuItem),
})
.ToListAsync();
foreach (var item in menuItemsWithRoles)
{
item.HasChildren = menuItemsWithRoles.Any(a => a.MenuItemParentId == item.MenuItemId);
}
return menuItemsWithRoles;
}
Update 2:
Here is the Program.cs setup for the dbContext. I've tried all life cycles and they all cause the error ONLY on the first call. If I log out and log back in (which that code will be called in that order again at Login, then it will NOT happen.
var connectionString = builder.Configuration.GetConnectionString("DefaultConnection") ?? throw new InvalidOperationException("Connection string 'DefaultConnection' not found.");
builder.Services.AddDbContext<ApplicationDbContext>(options =>
options.UseSqlServer(connectionString));
builder.Services.AddDatabaseDeveloperPageExceptionFilter();
builder.Services.AddIdentity<ApplicationUser, IdentityRole<Guid>>(options => options.SignIn.RequireConfirmedAccount = true)
.AddEntityFrameworkStores<ApplicationDbContext>()
.AddSignInManager<SignInManager<ApplicationUser>>()
.AddDefaultTokenProviders();

The first thing to check would be for any asynchronous operations not being awaited. For instance if you define an
asyncmethod that is awaited, but it forgets to await a.ToListAsync()etc. on the DbContext.The next culprit can be lazy loading. Code like:
or similar code consuming entities loaded from a DbContext can trigger lazy loading calls if LL is enabled. A better option if this is Automapper is to use
ProjectToon theIQueryableof entities being loaded, but this mean that any construction of a new menu (if data does not exist) would need to be done after the projection attempt. Projection withProjectToorSelectis preferable to loading entities as any referenced entities will get composed into the query to populate the view models etc. rather than loading entities and potentially tripping lazy load calls.