Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Can't use DbContext in message consumer #34

Closed
GMillerVarian opened this issue Oct 5, 2020 · 5 comments
Closed

Can't use DbContext in message consumer #34

GMillerVarian opened this issue Oct 5, 2020 · 5 comments
Assignees
Labels

Comments

@GMillerVarian
Copy link

I'm following the approach in the WebApi sample for our web application. My message consumer needs a DbContext (or a class which needs a DbContext), which must be scoped to avoid concurrency errors. However, since the IMessageBus is a singleton, created when the application starts, it cannot have a scoped service as a dependency. How can the message consumer use a scoped dependency, such as a DbContext?

@zarusz
Copy link
Owner

zarusz commented Oct 5, 2020

What transport provider do you use @GMillerVarian?

@GMillerVarian
Copy link
Author

We are using Redis.

We have discovered that creating a scope in the dependency resolver seems to have fixed the issue:

            .WithDependencyResolver(new LookupDependencyResolver(provider.CreateScope().ServiceProvider.GetRequiredService))

Does this seem like the best solution?

@zarusz
Copy link
Owner

zarusz commented Oct 5, 2020

Whenever SMB requires an instance of a consumer to be created it reaches out to the resolver to obtain it from DI. In the above example the LookupDependencyResolver will delegate to the supplied implicit lambda which has the side effect of creating a scope. However, nothing will dispose the scope. This isn't particularly bad because SMB will cache the resolved consumer instance and use it with the subsequent messages it has to handle for that same consumer type.

It all depends what you want to achieve:

  • If you want a scope created for one message and then disposed, it will require a slightly different solution:
// Startup.cs:

            services.AddScoped<SomeConsumer>();
            services.AddTransient<SomeConsumer.ScopedWrapper>();

// SMB builder config

                        x.Consume<SomeEvent>(
                            x => x.Topic(...)
                                    .WithConsumer<SomeConsumer.ScopedWrapper>());
// the wrapper:

    public class ScopedConsumerDecorator<T> : IConsumer<T>
    {
        private readonly IServiceProvider serviceProvider;
        private readonly Type consumerType;

        public ScopedConsumerDecorator(IServiceProvider serviceProvider, Type consumerType)
        {
            this.serviceProvider = serviceProvider;
            this.consumerType = consumerType;
        }

        public async Task OnHandle(T message, string name)
        {
            using var scope = serviceProvider.CreateScope();

            var targetConsumer = (IConsumer<T>)scope.ServiceProvider.GetRequiredService(consumerType);

            await targetConsumer.OnHandle(message, name);
        }
    }

  public class SomeConsumer: IConsumer<SomeEvent>
    {
        private readonly MyDbContect dbContext; // scoped

        public CrmEntityEventConsumerForWorkOrder(MyDbContect dbContext) { this.dbContext = dbContext;  }

        public async Task OnHandle(SomeEvent message, string name)
        {
        }

        public class ScopedWrapper : ScopedConsumerDecorator<SomeEvent>
        {
            public ScopedWrapper(IServiceProvider serviceProvider) : base(serviceProvider, typeof(SomeConsumer))
            {
            }
        }
    }

I am planning to add a feature to SMB, so that handlers could just be scoped in the DI and SMB creates a scope for each message and then it disposes the scope after handling.

  • If you are fine with all the messages being processed by the single scope assigned to your consumer then your example should work. However, since you're using DbContext which should be scoped per each transaction/unit of work (message scope, or web request) then you should be looking at the former.

Please let me know if the above answers.

@GMillerVarian
Copy link
Author

Thanks. In our case, we are only reading from the DB (never writing) so I think a single scope is sufficient.

@zarusz zarusz self-assigned this Oct 5, 2020
@zarusz zarusz added the question label Oct 5, 2020
@zarusz
Copy link
Owner

zarusz commented Oct 6, 2020

Closing. Feel free to reopen of you still have questions.

@zarusz zarusz closed this as completed Oct 6, 2020
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

2 participants