我有一个使用多个数据库分片的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:我解决了问题,但赏金仍然是开放的。请参阅下面的答案。
答案 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);
假设容器在处理时关闭并处理数据库连接。