我遇到了这个错误:
InvalidOperationException:在上一个操作完成之前,在此上下文实例上启动了第二个操作。这通常是由不同线程同时使用同一个 DbContext 实例引起的。有关如何避免 DbContext 线程问题的更多信息,
我有一个 Blazor 服务器应用程序,其中有一个带有 TreeView 的侧边栏。然后我还通过卡片在索引页面上加载菜单项。
初始加载很好,因为菜单项是在内存中而不是数据库中生成的。
在初始登录成功时,我看到由于侧边栏页面和索引页面调用相同的数据库调用来获取菜单项而导致的错误。
如果我刷新页面,一切都很好。然后菜单项被添加到内存缓存并随后工作。
我已经尝试过这个链接,但它不起作用。
这是我的菜单代码:
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);
}
}
有什么方法可以检查通话是否仍在运行?
我添加了一些日志,以下是时间差异:
不确定我可以添加什么来延迟侧栏加载调用?
侧边栏页面和索引页面都有相同的代码:
@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());
}
}
}
首先要检查是否有任何未等待的异步操作。例如,如果您定义了等待的
async
方法,但它忘记在 DbContext 上等待 .ToListAsync()
等。
下一个罪魁祸首可能是延迟加载。代码如下:
MenuData = mapper.Map<IList<MenuItemInfoModel>>(menuItems.ToList());
或者如果启用 LL,从 DbContext 加载的类似代码消耗实体可以触发延迟加载调用。如果这是 Automapper,更好的选择是在正在加载的实体的
ProjectTo
上使用 IQueryable
,但这意味着新菜单的任何构造(如果数据不存在)都需要在投影尝试之后完成。使用 ProjectTo
或 Select
进行投影优于加载实体,因为任何引用的实体都将被组合到查询中以填充视图模型等,而不是加载实体并可能触发延迟加载调用。