Cross-thread StructureMap 中的冲突

Cross-thread conflicts in StructureMap

我有一个 API 应用程序,它使用多个数据库分片,并使用 StructureMap 进行依赖注入。每个 API 调用中必需的 headers 之一是 ShardKey,它告诉我此调用正在寻址哪个数据库。为此,我有一个名为 ShardingMiddlewareOwinMiddleware class,其中包含以下代码(为清楚起见,已删减):

var nestedContainer = container.GetNestedContainer();
using (var db = MyDbContext.ForShard(shardKey)) // creates a new MyDbContext with connection string appropriate to shardKey
{
    nestedContainer.Configure(cfg => cfg.For<MyDbContext>().Use(db));
    await Next.Invoke(context);
}

这在我的测试环境中运行良好,并通过了一系列集成测试。

但集成测试有效 single-threaded。当我将它部署到 QA 环境中时,一个真正的应用程序正在通过多个同时调用攻击我的 API,事情开始变得 pear-shaped。实例:

System.ObjectDisposedException: Cannot access a disposed object. A common cause of this error is disposing a context that was resolved from dependency injection and then later trying to use the same context instance elsewhere in your application. This may occur is you are calling Dispose() on the context, or wrapping the context in a using statement. If you are using dependency injection, you should let the dependency injection container take care of disposing context instances.

或其他异常表明 StructureMap 没有 MyDbContext 可用的有效实例。

对我来说,多个线程似乎以某种方式弄乱了彼此的配置,但对于我的生活,我无法理解如何,因为我正在使用嵌套容器来存储每个线程的数据库上下文API呼唤。

知道这里可能出了什么问题吗?

更新: 我还尝试将我的 Db 上下文抽象到一个接口中。没有真正的区别;我仍然收到错误

System.InvalidOperationException: An error occurred when trying to create a controller of type 'SomeController'. Make sure that the controller has a parameterless public constructor. ---> StructureMap.StructureMapConfigurationException: No default Instance is registered and cannot be automatically determined for type 'MyNamespace.IMyDbContext'

更新2:我解决了这个问题,但是赏金仍然开放。请看下面我的回答。

好吧...我解决了问题,但我不明白为什么这会有所不同。

它归结为与我最初发布的内容存在一些细微差别,我将其遗漏是因为我认为细节无关紧要并且会分散对问题的注意力。事实上,我的容器并不是在本地定义的;相反,它是我的中间件的受保护 属性(它是出于集成测试目的而继承的):

protected IContainer Container { get; private set; }

然后在Invoke()方法里面有一个初始化调用:

Container = context.GetNestedContainer(); // gets the nested container created by a previous middleware class, using the context.Environment dictionary

在整个方法中使用日志语句,我得到了以下代码(如问题中所述,添加了日志记录):

_logger.Debug($"Line 1 Context={context.GetHashCode}, Container={Container.GetHashCode()}");
var db = MyDbContext.ForShard(shardKey.Value);  // no need for "using", since DI will automatically dispose
_logger.Debug($"Line 2 Context={context.GetHashCode}, Container={Container.GetHashCode()}");
Container.Configure(cfg => cfg.For<MyDbContext>().Use(db));
await Next.Invoke(context);

令人惊讶的是,日志中的内容如下:

Line 1 Context=56852305, Container=48376271

Line 1 Context=88275661, Container=85736099

Line 2 Context=56852305, Container=85736099

Line 2 Context=88275661, Container=85736099

太棒了!我的中间件 Container 属性 神奇地被替换了!这个,尽管它是用 private set 定义的,无论如何,为了安全起见,我检查了 MyDbContext.ForShard() 的代码,没有发现任何可能弄乱 [=14] 的引用=].

那么解决方案是什么?我在初始化后声明了一个本地 container 变量,并使用它来代替。

它现在有效,但我不明白为什么或如何产生不同。

悬赏给能解释这个的人。

你应该重写这个:

using (var db = MyDbContext.ForShard(shardKey)) // creates a new MyDbContext with connection string appropriate to shardKey
{
    nestedContainer.Configure(cfg => cfg.For<MyDbContext>().Use(db));
    await Next.Invoke(context);
}

导致 using 在使用结束时处理您的 dbcontext。

您应该改为注册工厂:

var dbFactory = ()=>MyDbContext.ForShard(shardKey);
nestedContainer.Configure(cfg => cfg.For<Func<MyDbContext>>().Use(dbFactory));
await Next.Invoke(context);

并注入此 Func 而不是 dbcontext 实例。

从日志中我看到,第二个 request/thread 覆盖了第一个的容器和数据库上下文,因此两者使用相同的连接:

Line 2 Context=56852305, Container=85736099 

应该是

Line 2 Context=56852305, Container=48376271

还是我理解错了,所以我认为你没有解决它。 System.ObjectDisposedException 错误来自 using 用于创建数据库上下文实例的子句,因此 Next 代表和 context 被处置。我也不明白这行

Container = context.GetNestedContainer(); 

也许你已经想到了

Container = container.GetNestedContainer(); 

?我不熟悉 StructureMap 但我认为代码应该是这样的

var nestedContainer = Container.GetNestedContainer(c =>
                    {
                        var db = MyDbContext.ForShard(shardKey);
                        c.For<MyDbContext>().Use(db);
                    });

await Next.Invoke(context);

假设容器在处理时关闭并处理db连接。