草庐IT

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

coder 2024-05-19 原文

我有一个 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/

有关c# - asp.net core 在上一个操作完成之前在此上下文中开始第二个操作的更多相关文章

  1. ruby - 使用 Vim Rails,您可以创建一个新的迁移文件并一次性打开它吗? - 2

    使用带有Rails插件的vim,您可以创建一个迁移文件,然后一次性打开该文件吗?textmate也可以这样吗? 最佳答案 你可以使用rails.vim然后做类似的事情::Rgeneratemigratonadd_foo_to_bar插件将打开迁移生成的文件,这正是您想要的。我不能代表textmate。 关于ruby-使用VimRails,您可以创建一个新的迁移文件并一次性打开它吗?,我们在StackOverflow上找到一个类似的问题: https://sta

  2. ruby-on-rails - Rails - 一个 View 中的多个模型 - 2

    我需要从一个View访问多个模型。以前,我的links_controller仅用于提供以不同方式排序的链接资源。现在我想包括一个部分(我假设)显示按分数排序的顶级用户(@users=User.all.sort_by(&:score))我知道我可以将此代码插入每个链接操作并从View访问它,但这似乎不是“ruby方式”,我将需要在不久的将来访问更多模型。这可能会变得很脏,是否有针对这种情况的任何技术?注意事项:我认为我的应用程序正朝着单一格式和动态页面内容的方向发展,本质上是一个典型的网络应用程序。我知道before_filter但考虑到我希望应用程序进入的方向,这似乎很麻烦。最终从任何

  3. ruby-on-rails - 渲染另一个 Controller 的 View - 2

    我想要做的是有2个不同的Controller,client和test_client。客户端Controller已经构建,我想创建一个test_clientController,我可以使用它来玩弄客户端的UI并根据需要进行调整。我主要是想绕过我在客户端中内置的验证及其对加载数据的管理Controller的依赖。所以我希望test_clientController加载示例数据集,然后呈现客户端Controller的索引View,以便我可以调整客户端UI。就是这样。我在test_clients索引方法中试过这个:classTestClientdefindexrender:template=>

  4. ruby-on-rails - 如果 Object::try 被发送到一个 nil 对象,为什么它会起作用? - 2

    如果您尝试在Ruby中的nil对象上调用方法,则会出现NoMethodError异常并显示消息:"undefinedmethod‘...’fornil:NilClass"然而,有一个tryRails中的方法,如果它被发送到一个nil对象,它只返回nil:require'rubygems'require'active_support/all'nil.try(:nonexisting_method)#noNoMethodErrorexceptionanymore那么try如何在内部工作以防止该异常? 最佳答案 像Ruby中的所有其他对象

  5. ruby - 为什么 SecureRandom.uuid 创建一个唯一的字符串? - 2

    关闭。这个问题需要detailsorclarity.它目前不接受答案。想改进这个问题吗?通过editingthispost添加细节并澄清问题.关闭8年前。Improvethisquestion为什么SecureRandom.uuid创建一个唯一的字符串?SecureRandom.uuid#=>"35cb4e30-54e1-49f9-b5ce-4134799eb2c0"SecureRandom.uuid方法创建的字符串从不重复?

  6. ruby - 如何在 Rails 4 中使用表单对象之前的验证回调? - 2

    我有一个服务模型/表及其注册表。在表单中,我几乎拥有服务的所有字段,但我想在验证服务对象之前自动设置其中一些值。示例:--服务Controller#创建Action:defcreate@service=Service.new@service_form=ServiceFormObject.new(@service)@service_form.validate(params[:service_form_object])and@service_form.saverespond_with(@service_form,location:admin_services_path)end在验证@ser

  7. ruby-on-rails - Rails - 从另一个模型中创建一个模型的实例 - 2

    我有一个正在构建的应用程序,我需要一个模型来创建另一个模型的实例。我希望每辆车都有4个轮胎。汽车模型classCar轮胎模型classTire但是,在make_tires内部有一个错误,如果我为Tire尝试它,则没有用于创建或新建的activerecord方法。当我检查轮胎时,它没有这些方法。我该如何补救?错误是这样的:未定义的方法'create'forActiveRecord::AttributeMethods::Serialization::Tire::Module我测试了两个环境:测试和开发,它们都因相同的错误而失败。 最佳答案

  8. c# - 如何在 ruby​​ 中调用 C# dll? - 2

    如何在ruby​​中调用C#dll? 最佳答案 我能想到几种可能性:为您的DLL编写(或找人编写)一个COM包装器,如果它还没有,则使用Ruby的WIN32OLE库来调用它;看看RubyCLR,其中一位作者是JohnLam,他继续在Microsoft从事IronRuby方面的工作。(估计不会再维护了,可能不支持.Net2.0以上的版本);正如其他地方已经提到的,看看使用IronRuby,如果这是您的技术选择。有一个主题是here.请注意,最后一篇文章实际上来自JohnLam(看起来像是2009年3月),他似乎很自在地断言RubyCL

  9. ruby - 用 Ruby 编写一个简单的网络服务器 - 2

    我想在Ruby中创建一个用于开发目的的极其简单的Web服务器(不,不想使用现成的解决方案)。代码如下:#!/usr/bin/rubyrequire'socket'server=TCPServer.new('127.0.0.1',8080)whileconnection=server.acceptheaders=[]length=0whileline=connection.getsheaders想法是从命令行运行这个脚本,提供另一个脚本,它将在其标准输入上获取请求,并在其标准输出上返回完整的响应。到目前为止一切顺利,但事实证明这真的很脆弱,因为它在第二个请求上中断并出现错误:/usr/b

  10. ruby - 一个 YAML 对象可以引用另一个吗? - 2

    我想让一个yaml对象引用另一个,如下所示:intro:"Hello,dearuser."registration:$introThanksforregistering!new_message:$introYouhaveanewmessage!上面的语法只是它如何工作的一个例子(这也是它在thiscpanmodule中的工作方式。)我正在使用标准的ruby​​yaml解析器。这可能吗? 最佳答案 一些yaml对象确实引用了其他对象:irb>require'yaml'#=>trueirb>str="hello"#=>"hello"ir

随机推荐