2

I have a tricky requirement where I need create copy of a service that has been created via Constructor DI in my Azure Function

public MyFunction(IMyService myService,
             IServiceProvider serviceProvider, 
             ServiceCollectionContainer serviceCollectionContainer) 
{
    _myService = tmToolsService;
    _serviceProvider = serviceProvider;
    _serviceCollectionContainer = serviceCollectionContainer;
}

[FunctionName("diagnostic-orchestration")]
public async Task DiagnosticOrchestrationAsync(
    [OrchestrationTrigger] IDurableOrchestrationContext context)
{

}
    

This service has a lot of dependencies so I dont really want go down the manual Activator.CreateInstance route

I have tried 2 different approaches

Approach 1

I have ServiceCollectionContainer. This is filled in Configure of the startup and simply holds the services

public override void Configure(IFunctionsHostBuilder builder)
{
   base.Configure(builder);

   var services = builder.Services; 
   services.AddSingleton(s => new ServiceCollectionContainer(services));
}

In my function I call

var provider = _serviceCollectionContainer.ServiceCollection.BuildServiceProvider();
if (provider.GetService<IMyService>() is IMyService myService)
{
   await myService.MyMathodAsync();
}

This throws the error

System.InvalidOperationException: 'Unable to resolve service for type 
'Microsoft.Azure.WebJobs.Script.IEnvironment' while attempting to activate
'Microsoft.Azure.WebJobs.Script.Configuration.ScriptJobHostOptionsSetup'.'

I believe this could be because although the service collection looks fine (276 registered services) I have seen references online that say that Configure may be unreliable

Approach 2

The second approach is the more conventional one, I just tried to use the service provider injected without making any changes

if (_serviceProvider.GetService<IMyService>() is IMyService myService)
{
   await myService.MyMathodAsync();
}

But if I use this approach I get the error

'Scope disposed{no name} is disposed and scoped instances are disposed and no longer availab

How can I fix this?

I have large date range of data that I am processing. I need to split my date range and use my service to process each date range. My service has repositories. Each repository has a DbContext. Having each segment of dates run in the context of its own service allows me to run the processing in parallel without having DbContext queries being run in parallel which causes issues with Ef Core

This processing is running inside a durable function

Paul
  • 2,773
  • 7
  • 41
  • 96
  • **Why** do need a "copy"? Are you maintaining state within this dependency? If you are, you should address **that** issue. – Daniel Mann Nov 20 '22 at 16:45
  • I have added more info to question - but basically its because I want to run DbContext queries in parallel. Being able to have a copy of IMyService for each date range allows me to speed up my processing a lot – Paul Nov 20 '22 at 18:01
  • Can't you just add the `(I)MyService` and the `DbContext` as transient to your DI or use `_serviceProvider.CreateScope` to create a new scope and then go with your approach #2? – Jürgen Röhr Nov 20 '22 at 18:27
  • No because in the constructor injection I don’t know how many instances of IMyService I need. For example if I have to process 30 days with a segment size of 5 I will have 6 instances of the service. These will perform their processing in parallel – Paul Nov 20 '22 at 19:21
  • Any ideas on this? – Paul Nov 22 '22 at 11:39
  • I suggest you move most of the heavy lifting into an activity function that you potentially call multiple times. E.g. one activity call per date range. You should also no longer have issues with DI in this case – Alex AIT Nov 23 '22 at 07:45
  • I am trying to avoid the route of running multiple Durable functions within because I dont understand why the process is so slow with that approach. I created https://stackoverflow.com/questions/73919626/how-to-identify-why-durable-functions-running-in-parallel-are-still-slow but had no response – Paul Nov 23 '22 at 13:20
  • Register your service like `services.AddSCoped(serviceProvider => { var s1 = serviceProvider.GetRequiredService() ; ...other services...; return new MyService(s1, ...));` You also can experiment if AddTransient or other Add* method will work for you. – Michał Turczyn Nov 28 '22 at 08:24

2 Answers2

2

I don't know if this holds true for Azure Functions and moreover I am not experienced with durable ones, though as it seems that the main goal is to run parallel queries via ef core through your IMyService then you could in the constructor:

public MyFunction(IServiceScopeFactory serviceScopeFactory) 
{
  _serviceScopeFactory = serviceScopeFactory;
}

And then in the function call, assuming you have an IEnumerable "yourSegments" of the things you want to process in parallel:

var tasks = yourSegments.Select(async segment =>
        {
            using (var scope = _serviceScopeFactory.CreateScope())
            {
                var IMyService = scope.ServiceProvider.GetRequiredService<IMyService>();
                await IMyService.MyMathodAsync(segment);


            }
        });

 await Task.WhenAll(tasks);

I got this from a nice blog post that explains "Since we project our parameters to multiple tasks, each will have it's own scope which can resolve it's own DbContext instance."

david-ao
  • 3,000
  • 5
  • 12
  • I get the error Scope disposed{no name} is disposed and scoped instances are disposed and no longer available.' – Paul Nov 24 '22 at 06:39
0

You can create a 1:1 copy by using this extension method. It is a large function, to large for SO, so I've put a pastebin here. https://pastebin.com/1dKu01w9

Just call _myService.DeepCopyByExpressionTree(); within your constructor.

Marvin Klein
  • 1,436
  • 10
  • 33