2016-08-04 72 views
6

我有一个使用多个数据库分片的API应用程序,使用StructureMap进行依赖注入。每个API调用中所需的标题之一是ShardKey,它告诉我此调用正在寻址哪个数据库。为了实现这一点,我有一个OwinMiddleware类称为ShardingMiddleware,其中包含下面的代码(剪断为清楚起见):StructureMap中的跨线程冲突

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

这精美的作品在我的测试环境,并通过集成测试的电池。

但是集成测试实际上是单线程的。当我将这部署到一个QA环境中时,一个真正的应用程序通过多个同时调用在我的API中触发,事情开始变成梨形。 Ferinstance:

System.ObjectDisposedException:无法访问处置的对象。造成这种错误的一个常见原因是处理从依赖注入解决的上下文,然后尝试在应用程序的其他地方使用相同的上下文实例。这可能发生在你正在上下文中调用Dispose()或将上下文包装在using语句中。如果您正在使用依赖注入,则应该让依赖注入容器负责处理上下文实例。

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

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

任何想法可能会在这里出错?

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

System.InvalidOperationException:尝试创建类型为'SomeController'的控制器时发生错误。确保控制器有一个无参数的公共构造函数。 ---> StructureMap.StructureMapConfigurationException:没有默认实例被注册,并且不能为类型“MyNamespace.IMyDbContext”

更新2自动确定:我解决了这个问题,但奖金仍然是开放的。请参阅下面的答案。

+0

您的'DbContext'可能会作为[Captive Dependency]保留下来(http://blog.ploeh.dk/2014/06/02/captive-dependency/)。确保此依赖项的使用者的生命周期的寿命不超过'DbContext'的寿命,或者 - 甚至更好 - 防止将DbContext直接注入到消费者中。 'DbContext'是运行时数据,运行时数据[不应该注入到组件中](https://www.cuttingedge.it/blogs/steven/pivot/entry.php?id=99)。而是在抽象背后隐藏DbContext。 – Steven

回答

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

而且令人叹为观止,这里就是冒出来的日志:

行1个上下文= 56852305,容器= 48376271

1行上下文= 88275661,容器= 85736099

行2上下文= 56852305,容器= 85736099

Line 2上下文= 88275661,Container = 85736099

令人惊叹!我的中间件的Container属性被神奇地取代了!尽管事实上它被定义为private set,并且无论如何,为了安全起见,我通过MyDbContext.ForShard()的代码进行了检查,但没有发现任何可能导致Container的参考文件混乱的内容。

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

它现在可以工作,但我不明白为什么或如何做出改变。

赏金去的人可以解释这一点。

+0

我认为,原因与'为什么使用HttpContext或ThreadLocal范围界定嵌套容器?'相同。这里http://structuremap.github.io/the-container/nested-containers/ – ATechieThought

+0

@ATechieThought你能解释一下吗?我总是使用OwinContext来获取容器,而不是使用HttpContext或ThreadLocal范围。 –

+0

我的不好。我错过了。但我的想法是以某种方式通过查看上下文值来参与httpcontext。现在,httpcontext不在图片中,有没有可能涉及到MyDbContext,它应该被设置为singleton?对不起,如果我不以任何方式推理问题。 – ATechieThought

2

您应该重写此:

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

,并注入本功能代替的DbContext实例。

+0

如果你注入一个Func而不是一个实例,它会在注入MyDbContext的每个类中打开一个新的Db连接吗? –

+0

它取决于。默认情况下 - 是的。这是个问题吗? –

+0

不确定是否有问题。我目前的想法是,单个数据库连接应该足够用于一个工作单元。我不确定是否可能会导致冲突,如果有更多的连接 - 或者如果它可能会过度的压力数据库有太多的连接打开......但这是我担心。基本上,如果我想每个UOW有一个连接,我将传递该实例,如果我想为每个存储库创建一个连接,我将使用Func。谢谢! –

0

从我看到的日志是第二请求/线程覆盖容器和第一个如此恭敬的数据库上下文都使用相同的连接:

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连接。