Cross Conflicts in StructureMap

I have an API application that uses multiple database shards, with StructureMap for dependency injection. One of the required headers in every API call is ShardKey , which tells me which database this call is accessing. For this, I have an OwinMiddleware class called ShardingMiddleware , which contains the following code (for clarity):

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

This works great in my test environment and passes a battery of integration tests.

But integration tests are effectively single-threaded. When I deployed it in a QA environment where a real application hits my API with multiple simultaneous calls, everything starts to become pear-shaped. Ferinstance:

System.ObjectDisposedException: Unable to access the remote object. A common cause of this error is to remove the context that was allowed from the dependency injection, and then try to use the same context instance in another place in your application. This can happen if you call Dispose () in a context or wrap a context in a using statement. If you use dependency injection, you must let the dependency injection container take care of disposing of context instances.

Or other exceptions indicating that StructureMap does not have a valid instance of MyDbContext .

It seems to me that several threads somehow messed up each other, but for life I can’t figure out how, seeing how I use a nested container to store the database context for each API call.

Any ideas what might be wrong here?

Update: I also tried to abstract my Db context in the interface. There was no real difference; I still get the error message

System.InvalidOperationException: An error occurred while trying to create a controller of type SomeController. Make sure the controller has an immortal public constructor. ---> StructureMap.StructureMapConfigurationException: the default instance is not registered and cannot be automatically determined for the type "MyNamespace.IMyDbContext"

Update 2: I solved the problem, but the generosity is still open. See my answer below.

+6
source share
3 answers

Well ... I solved the problem, but I do not understand why it mattered.

It comes down to some subtle differences from what I originally posted, what I forgot, because I thought the details were inconsequential and would be distracted from the question. In fact, my container was not defined locally; rather, it was a protected property of my middleware (it is inherited for integration testing purposes):

 protected IContainer Container { get; private set; } 

Then the initialization call was called inside the Invoke() method:

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

Using the logging instructions in the whole method, I moved on to the following code (as mentioned in the issue with adding a log):

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

And amazingly, this is what came out of the magazines:

Line 1 Context = 56852305, Container = 48376271

Line 1 Context = 88275661, Container = 85736099

Line 2 Context = 56852305, Container = 85736099

Line 2 Context = 88275661, Container = 85736099

Awesome! My middleware's Container property got a magical replacement! This is despite the fact that it is defined using a private set , and in any case, to be safe, I checked the code MyDbContext.ForShard() and did not find anything that could ruin the link for Container .

So what was the solution? I declared a local variable Container immediately after initialization and used it instead.

Now it works, but I don’t understand why and how it might matter.

The bounty goes to a person who can explain this.

+2
source

You should rewrite this:

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

cause using remove your dbcontext at the end of use.

Instead, you should register a factory:

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

and enter this Func instead of the dbcontext instance.

+2
source

From the logs it can be seen that the second request / stream redefines the container and respects the database context of the first, so both use the same connection:

 Line 2 Context=56852305, Container=85736099 

it should be

 Line 2 Context=56852305, Container=48376271 

or I'm wrong, so I don’t think you are solving it. System.ObjectDisposedException error from using that you use to create an instance of your db context, and because of it Next delegate and context . I also did not understand the line

 Container = context.GetNestedContainer(); 

Did you mean

 Container = container.GetNestedContainer(); 

? I am not familiar with StructureMap, but I think the code should look like this:

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

assuming the container closes and removes the db connection when it is located.

0
source

All Articles