StructureMap

时间:2016-08-04 18:56:34

标签: c# asp.net multithreading structuremap

我有一个使用多个数据库分片的API应用程序,使用StructureMap进行依赖注入。每个API调用中所需的标头之一是ShardKey,它告诉我此调用正在寻址的数据库。为了实现这一点,我有一个名为OwinMiddleware的{​​{1}}类,其中包含以下代码(为清晰起见,已剪切):

ShardingMiddleware

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

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

  

System.ObjectDisposedException:无法访问已处置的对象。此错误的常见原因是处理从依赖项注入解析的上下文,然后尝试在应用程序的其他位置使用相同的上下文实例。您可能会在上下文中调用Dispose()或将上下文包装在using语句中。如果使用依赖注入,则应该让依赖注入容器处理上下文实例。

或其他异常,表明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); } 实例。

对我而言,似乎多个线程在某种程度上混淆了彼此的配置,但对于我的生活,我无法理解如何,因为我正在使用嵌套容器来存储每个API调用的数据库上下文

任何想法可能会出现问题吗?

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

  

System.InvalidOperationException:尝试创建“SomeController”类型的控制器时发生错误。确保控制器具有无参数的公共构造函数。 ---&GT; StructureMap.StructureMapConfigurationException:没有注册默认实例,无法自动确定类型'MyNamespace.IMyDbContext'

更新2:我解决了问题,但赏金仍然是开放的。请参阅下面的答案。

3 个答案:

答案 0 :(得分: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

     

第2行上下文= 88275661,容器= 85736099

惊人!我的中间件的Container属性被神奇地替换了!尽管事实上它是用private set定义的,但无论如何,为了安全起见,我检查了MyDbContext.ForShard()的代码,发现没有任何可能搞砸了{{1}的引用}}

那么解决方案是什么?我刚刚在初始化之后声明了一个本地Container变量,而是使用了它。

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

Bounty会找到可以解释这一点的人。

答案 1 :(得分: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);

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

答案 2 :(得分: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);

假设容器在处理时关闭并处理数据库连接。