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();
