gpt4 book ai didi

c# - asp.net core 在上一个操作完成之前在此上下文中开始第二个操作

转载 作者:可可西里 更新时间:2023-11-01 03:01:56 25 4
gpt4 key购买 nike

我有一个 ASP.Net Core 2 Web 应用程序。

我正在尝试创建一个自定义路由中间件,这样我就可以从数据库中获取路由。

ConfigureServices() 我有:

services.AddDbContext<DbContext>(options => 
options.UseMySQL(configuration.GetConnectionString("ConnectionClient")));
services.AddScoped<IServiceConfig, ServiceConfig>();

Configure() 中:

app.UseMvc(routes =>
{
routes.Routes.Add(new RouteCustom(routes.DefaultHandler);
routes.MapRoute(name: "default", template: "{controller=Home}/{action=Index}/{id?}");
});

RouteCustom

public class RouteCustom : IRouteCustom
{
private readonly IRouter _innerRouter;
private IServiceConfig _serviceConfig;

public RouteCustom(IRouter innerRouter)
{
_innerRouter = innerRouter ?? throw new ArgumentNullException(nameof(innerRouter));
}

public async Task RouteAsync(RouteContext context)
{
_serviceConfig = context.HttpContext
.RequestServices.GetRequiredService<IServiceConfig>();
/// ...
// Operations inside _serviceConfig to get the route
}

public VirtualPathData GetVirtualPath(VirtualPathContext context)
{
_serviceConfig = context.HttpContext
.RequestServices.GetRequiredService<IServiceConfig>();
// ...
// Operations inside _serviceConfig to get the route
}
}

IServiceConfig 它只是一个类,我在其中访问数据库以获取数据,在本例中为路由,还​​有应用程序所需的其他配置数据。

public interface IServiceConfig
{
Config GetConfig();
List<RouteWeb> SelRoutesWeb();
}

public class ServiceConfig : IServiceConfig
{
private readonly IMemoryCache _memoryCache;
private readonly IUnitOfWork _unitOfWork;
private readonly IServiceTenant _serviceTenant;

public ServiceConfig(IMemoryCache memoryCache,
IUnitOfWork unitOfWork,
IServiceTenant serviceTenant)
{
_memoryCache = memoryCache;
_unitOfWork = unitOfWork;
_serviceTenant = serviceTenant;
}


public Config GetConfig()
{
var cacheConfigTenant = Names.CacheConfig + _serviceTenant.GetId();

var config = _memoryCache.Get<Config>(cacheConfigTenant);

if (config != null)
return config;

config = _unitOfWork.Config.Get();
_memoryCache.Set(cacheConfigTenant, config,
new MemoryCacheEntryOptions()
{
SlidingExpiration = Names.CacheExpiration
});

return config;
}


public List<RouteWeb> SelRoutesWeb()
{
var cacheRoutesWebTenant = Names.CacheRoutesWeb + _serviceTenant.GetId();

var routesWebList = _memoryCache.Get<List<RouteWeb>>(cacheRoutesWebTenant);

if (routesWebList != null)
return routesWebList;

routesWebList = _unitOfWork.PageWeb.SelRoutesWeb();
_memoryCache.Set(cacheRoutesWebTenant, routesWebList,
new MemoryCacheEntryOptions()
{
SlidingExpiration = Names.CacheExpiration
});

return routesWebList;
}

}

问题是我在打开多个选项卡进行测试并尝试同时刷新所有选项卡时收到此消息:“在前一个操作完成之前,第二个操作在此上下文中开始”

我确定我做错了什么,但我不知道是什么。它必须是访问自定义路由中间件内的数据库的更好方法,甚至是更好的方法。

例如,在常规中间件(不是路由中间件)上,我可以将依赖项注入(inject) Invoke 函数,但我不能在此处将依赖项注入(inject) RouteAsync 或 GetVirtualPath()

这里会发生什么?

提前致谢。


更新这些是我遇到的异常。

An unhandled exception occurred while processing the request.InvalidOperationException: A second operation started on this context before a previous operation completed. Any instance members are not guaranteed to be thread safe.

还有这个:

An unhandled exception occurred while processing the request.MySqlException: There is already an open DataReader associated with this Connection which must be closed first.

这是工作单元

public interface IUnitOfWork : IDisposable
{
ICompanyRepository Company { get; }
IConfigRepository Config { get; }

// ...

void Complete();
}


public class UnitOfWork : IUnitOfWork
{
private readonly DbContext _context;

public UnitOfWork(DbContext context)
{
_context = context;

Company = new CompanyRepository(_context);
Config = new ConfigRepository(_context);
// ...
}

public ICompanyRepository Company { get; private set; }
public IConfigRepository Config { get; private set; }

// ...

public void Complete()
{
_context.SaveChanges();
}

public void Dispose()
{
_context.Dispose();
}

}

更新 2

在查看评论并进行大量测试后,我得到的最好线索是当我删除 CustomRoute 行时问题就消失了。从 Startup.cs 的 Configure 函数中删除这一行

routes.Routes.Add(new RouteCustom(routes.DefaultHandler));

我也曾尝试删除,首先是 RouteAsync,然后是 GetVirtualPath() 方法,但如果存在其中一个方法,我会得到一个错误,所以很明显问题出在这个 CustomRoute 类。

TenantMiddleware(对于任何请求都会首先调用)中,我正在注入(inject) UnitOfWork,我没有遇到任何问题。这个中间件是在 Configure 函数中创建的:

app.UseMiddleware<TenantMiddleware>();

在内部,我正在注入(inject) UnitOfWork,并在每次请求时使用它,如下所示:

public async Task Invoke(HttpContext httpContext, IServiceTenant serviceTenant)
{
// ...performing DB operations to retrieve the tenent's data.
}

public class ServiceTenant : IServiceTenant
{
public ServiceTenant(IHttpContextAccessor contextAccessor,
IMemoryCache memoryCache,
IUnitOfWorkMaster unitOfWorkMaster)
{
_unitOfWorkMaster = unitOfWorkMaster;
}

// ...performing DB operations
}

所以,CustomRoute 的问题是我无法通过像这样添加到 Invoke 函数来注入(inject)依赖项:

public async Task Invoke(HttpContext httpContext, IServiceTenant serviceTenant)

所以我必须像这样调用相应的服务(在该服务中我注入(inject) UnitOfWork 并执行数据库操作),我认为这可能是导致问题的原因:

public async Task RouteAsync(RouteContext context)
{
_serviceConfig = context.HttpContext
.RequestServices.GetRequiredService<IServiceConfig>();
// ....
}

因为这是我知道的将 IServiceConfig“注入(inject)”到 RouteAsync 和 GetVirtualPath() 中的唯一方法...

此外,由于我使用的是 BaseCOntroller,所以我在每个 Controller 中都这样做,所以我决定我使用哪个操作系统的注入(inject)服务...

public class BaseWebController : Controller
{
private readonly IMemoryCache _memoryCache;
private readonly IUnitOfWork _unitOfWork;
private readonly IUnitOfWorkMaster _unitOfWorkMaster;
private readonly IServiceConfig _serviceConfig;
private readonly IServiceFiles _serviceFiles;
private readonly IServiceFilesData _serviceFilesData;
private readonly IServiceTenant _serviceTenant;

public BaseWebController(IServiceProvider serviceProvider)
{
_memoryCache = serviceProvider.GetRequiredService<IMemoryCache>();
_unitOfWork = serviceProvider.GetRequiredService<IUnitOfWork>();
_unitOfWorkMaster = serviceProvider.GetRequiredService<IUnitOfWorkMaster>();
_serviceConfig = serviceProvider.GetRequiredService<IServiceConfig>();
_serviceFiles = serviceProvider.GetRequiredService<IServiceFiles>();
_serviceFilesData = serviceProvider.GetRequiredService<IServiceFilesData>();
_serviceTenant = serviceProvider.GetRequiredService<IServiceTenant>();
}
}

然后在每个 Controller 中,无需引用所有注入(inject)的服务,我可以只为我需要的服务执行此操作,如下所示:

public class HomeController : BaseWebController
{
private readonly IUnitOfWork _unitOfWork;

public HomeController(IServiceProvider serviceProvider) : base(serviceProvider)
{
_unitOfWork = serviceProvider.GetRequiredService<IUnitOfWork>();
}

public IActionResult Index()
{
// ...
}
}

我不知道这是否与我的问题有关,但我只是向您展示了我认为可能的问题所在,以便您获得更多信息。

谢谢。

更新 3

这是检索路由的数据库代码:

public class PageWebRepository : Repository<PageWeb>, IPageWebRepository
{
public PageWebRepository(DbContext context) : base(context) { }


public List<RouteWeb> SelRoutesWeb()
{
return Context.PagesWebTrs
.Include(p => p.PageWeb)
.Where(p => p.PageWeb.Active)
.Select(p => new RouteWeb
{
PageWebId = p.PageWebId,
LanguageCode = p.LanguageCode,
Route = p.Route,
Regex = p.PageWeb.Regex.Replace("<route>", p.Route),
Params = p.PageWeb.Params,
Area = p.PageWeb.Area,
Controller = p.PageWeb.Controller,
Action = p.PageWeb.Action,
Type = p.PageWeb.Type,
Sidebar = p.PageWeb.Sidebar,
BannerIsScript = p.PageWeb.BannerIsScript,
Title = p.Title,
Description = p.Description,
Keywords = p.Keywords,
ScriptHead = p.ScriptHead,
ScriptBody = p.ScriptBody,
BannerScript = p.BannerScript,
BannerUrl = p.BannerUrl,
})
.ToList();
}
}

其中 PagesWebTrs 是页面的翻译(多语言),PagesWeb 是主表。

最佳答案

这个问题确实在路由中间件中。

根据定义,中间件是单例,因此单个实​​例处理所有请求。这导致实例状态(IServiceConfigDbContext Hook )被多个同时请求访问和更改;这是一个伪装得很好的经典并发问题。

一个例子。

请求 A 执行 RouteAsync,设置 _serviceConfig 并在 DbContext 上执行查询。纳秒(或更少 :))之后,请求 B 执行相同的操作。在执行请求 B 的查询时,请求 A 执行 GetVirtualPath,但这次是在请求 B 设置的 DbContext 上执行。这导致在 上执行第二个查询请求 B 的 >DbContext 仍然有一个正在运行,你得到了提到的错误。

解决方案是通过在每个方法开始时检索 IServiceConfig 来防止共享状态。

如您所说,通过 Invoke 方法注入(inject)这样的依赖项是行不通的; Invoke 方法没有被执行。

下面是修改后的 RouteCustom

public class RouteCustom : IRouteCustom
{
private readonly IRouter _innerRouter;

public RouteCustom(IRouter innerRouter)
{
_innerRouter = innerRouter ?? throw new ArgumentNullException(nameof(innerRouter));
}

public async Task RouteAsync(RouteContext context)
{
var serviceConfig = context.HttpContext.RequestServices.GetRequiredService<IServiceConfig>();
// ...

}

public VirtualPathData GetVirtualPath(VirtualPathContext context)
{
var serviceConfig = context.HttpContext.RequestServices.GetRequiredService<IServiceConfig>();
// ...

}
}

关于c# - asp.net core 在上一个操作完成之前在此上下文中开始第二个操作,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/50565991/

25 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com