Closed wleader closed 8 months ago
Theoretically, it would be possible to create a custom lifestyle, but there are many caveats in doing that. This is something I, therefore, don't advice.
This 'scope skipping' is something that is not built-in to Simple Injector and I think most DI Containers will give you troubles implementing this.
But there are always ways around this. For instance, by adding a wrapper class for SharedObject
. For instance:
// Wrapper
class SharedObjectContainer
{
public SharedObject SharedObject { get; set; }
}
// Registration
container.Register(
typeof(SharedObjectContainer),
typeof(SharedObjectContainer),
Lifestyle.Scoped);
var producer = Lifestyle.Transient.CreateProducer<SharedObject>(
typeof(SharedObject), container);
container.Register<SharedObject>(
() => container.GetInstance<SharedObjectContainer>().SharedObject
?? producer.GetInstance(), Lifestyle.Scoped);
// Inside ParentTask
public async Task Run(Container container)
{
var innerScope = AsyncScopedLifestyle.BeginScope(container);
// IMPORTANT: Set SharedObject from ParentTask
innerScope.GetInstance<SharedObjectContainer>().SharedObject = this.SharedObject;
this.ChildTask = innerScope.GetInstance<ChildTask>();
await this.ChildTask.Run();
}
I hope this helps
I'll give things a try. I actually had tried the whole wrapper thing I couldn't figure out how to get the container to verify the registrations when the wrapper had not been initialized yet. I think the magic in your suggestion over what I was trying is the part where you create a transient producer to create one if the wrapper doesn't already have one set.
Also, How very awesome how quickly you came back with advice. 👍
I did have to make a small change to the suggestion, the producer needed to be scoped.
var producer = Lifestyle.Scoped.CreateProducer<SharedObject>(
typeof(SharedObject), container);
Unfortunately this introduces a new problem. When the inner scope ends, the SharedObject is getting disposed. It does work if I add a flag to the shared object that causes it to ignore the dispose, and then clear the flag after the scope is disposed, but that feels ugly.
I'm open to further suggestion, but if there isn't anything else to be done then this question can be closed.
Thank you for your help.
Just as a follow up in case anyone else comes across this post, and runs into a similar situation:
Another side effect of this strategy has been that inside my child scope I am using Entity Framework Core , and the Entity Framework Core DBContext is an IDisposable. The object that I am sharing between contexts is the DB connection and Transaction. So this ends up meaning that when the inner scope ends, it tries to dispose the DBContext, and the Entity Framework DBContext assumes that it owns the database connection and transaction and tries to dispose them as well. The work around here is to Extend DbContext and intercept the Dispose and prevent it from running too soon.
It feels a bit broken to me that an object should know something about the container scope, and change its behavior around when the scope ends. I'm not sure what can be done about it.
I think I may have an unusual use case, and I think maybe a custom lifestyle could do what I want but I am not sure if that is the right approach or how to do it. The Code below I hope will illustrate what I am trying to accomplish. First I will try to explain why I need to do what I am trying to do.
Assume that there is a service, and that this service behaves like a multi-threaded program. (Its not really Traditional threads, its all Async/Await). Inside this process there are multiple parent workers. The parent worker connects to the database, starts a transaction, finds a piece of data to work on, and then calls a child task to do its work on that data. The child task must utilize the same database connection and transaction as the parent task so the changes to the database that the child makes will be included in the transaction that the parent started. Finally the parent commits the transaction and runs the loop again.
Without going into distracting complexity, The child task may be different classes each time a unit of work is handled, and are not even known at compile time. They will come from a different assembly with their rules for matching child task instances to the data found by the parent, So the child task instances must be created on demand. To be more clear the parent task is in library code, and the child task is in the application that is consuming the library code. I don't really have an option to deviate from this pattern.
What would be the right way of creating multiple parent tasks with their own scopes, that create child tasks in their own child scopes, and where the child scope would return the same shared object as its respective parent?
Thank you for any guidance you can provide