0

I have multiple background Batch jobs to Run and I have been asked to have single webJob to run all task, which I need to schedule to run at different time.

I have used Timer feature from webJob.Extensions.

i.e in the Program.cs

var config = new JobHostConfiguration
            {
                JobActivator = new AutofacActivator(ContainerConfig<Functions>.GetContainer())
            };

            if (config.IsDevelopment)
            {
                config.UseDevelopmentSettings();
            }
            config.UseTimers();

            var host = new JobHost(config);
            host.RunAndBlock();

And the function will have multiple method and are triggered in interval of 30 min.

public void ProcessMethod1([TimerTrigger("0 0/30 * * * *")] TimerInfo timer)
{
   //Logic
}

public void ProcessMethod2([TimerTrigger("0 0/30  * * * *")] TimerInfo timer)
{
   //Logic
}

Issue: Since I am using Autofac DI. I am creating the instance for dbContext at the start of the Job

JobActivator = new AutofacActivator(ContainerConfig<Functions>.GetContainer())

While executing the webJob I am getting errors while executing DB select like "An attempt was made to use the context while it is being configured".

Since I gave scope as InstancePerLifetimeScope(). I want to know if the Two operation will get same instance?

Also for Logging I have similar issue, since it looks like only one instance is created for these two different Operation.

All I want is to have separate instance for both DBCOntext and Logger based on operation. Pls advise me how I can set DI for this scenario.

Update:

public class AutofacActivator: IJobActivator
    {
        private readonly Autofac.IContainer _container;

        public AutofacActivator(Autofac.IContainer container)
        {
            _container = container;
        }

        public T CreateInstance<T>()
        {
            return _container.Resolve<T>();
        }
    }

 internal class WebJobIocModule<T> : Autofac.Module
    {
        protected override void Load(ContainerBuilder builder)
        {
            if (builder == null)
                throw new ArgumentNullException("WebJobBuilder");


            //Cascade  
            builder.RegisterModule(new BusinessObjectIocModule());

            // Register the functions class - WebJobs will discover our triggers this way
            builder.RegisterType<T>();
        }
    }



  public class BusinessObjectIocModule :Autofac.Module
    {
        protected override void Load(ContainerBuilder builder)
        {
            if(builder == null) throw new ArgumentNullException("BusinessObjectBuilder");


            //Cascade
            builder.RegisterModule(new DataAccessRepoIocModule());

            builder.RegisterType<BusinessObjBpc>().AsImplementedInterfaces();

        }
    }

In DataAccessIOC:

 string connectionString = ConfigurationManager.ConnectionStrings["DBConnectionAppKeyName"].ConnectionString;
            optionsStagingBuilder.UseSqlServer(connectionString);

            builder.RegisterType<DataAccessDbContext>()
                .AsSelf()
                .WithParameter("options", optionsStagingBuilder.Options)
                .InstancePerLifetimeScope();
RajGan
  • 801
  • 3
  • 13
  • 28
  • Can you post the code of the AutofacActivator, the code that shows how you registered your dependencies and also the code of the class that encapsulate your job functions. Thanks – Thomas Jan 06 '17 at 00:32
  • To check if you are using the same instance of the `DbContext` in different places you can tag your instance of DbContext with a unique value and log it/check it on debug. Add a property in your `DbContext` and fill it with a new Guid in the constructor, or something similar. [As mentioned here](https://github.com/aspnet/EntityFramework/issues/6488) – Diana Jan 06 '17 at 16:12
  • @Thomas Thanks.. I will please find container code – RajGan Jan 08 '17 at 20:42
  • Have a look at this Q/A: http://stackoverflow.com/questions/35186456/azure-triggered-webjobs-scope-for-dependency-injection – Thomas Jan 08 '17 at 21:12
  • @Diana Yes I am getting same instance for those two operation.. which I don't want.. As said I created a GUID in constructor and I see it its same for different operation and even for multiple Run.. – RajGan Jan 08 '17 at 21:12
  • Both functions are in the same class ? – Thomas Jan 08 '17 at 21:13
  • There is only one Function class with two different processMethod. Sorry I didn't get you question.. – RajGan Jan 08 '17 at 21:16
  • If you put a break point inside this method `public T CreateInstance()`. Is it triggered twice ? It is possible that the jobhost creates only one instance of the Function class. – Thomas Jan 08 '17 at 21:25
  • Yes looks like JobHost creates only one function instance. But I see For each run the createinstance() is called.separately for both methods – RajGan Jan 08 '17 at 22:57

1 Answers1

0

If you use a LifetimeScope for the DbContext you should make sure that you make a new scope in each execution of the processing methods, by using code like this:

public void ProcessMethod1([TimerTrigger("0 0/30 * * * *")] TimerInfo timer)
{
    using (var scope = container.BeginLifetimeScope())
    {
        //Logic
    }
}

It would work, but I think it may have side effects. Also, for other components it is more efficient not to instantiate them in each execution of the processing methods. Perhaps you would like to use a different scope.

Update - How to inject the container in the function constructor:

In your constructor you can use a parameter Func<IContainer> instead of a IContainer. It will work as a container factory.

When you build your container (at the beginning of the job, at the point where you build your container) you register the instance of your container factory:

Func<IContainer> containerFactory = () => yourContainer;
...
builder.RegisterInstance(containerFactory);

Then in your constructor you let Autofac inject the factory and you store the container in a property:

public SomeConstructor(Func<IContainer> containerFactory, ...<other injected params>)
{
    ...
    this.container = containerFactory();
}
Diana
  • 2,186
  • 1
  • 20
  • 31
  • I could not get the instance for container. It is throwing error when I try to resolve FUnction Instance. Error: None of the constructors found with 'Autofac.Core.Activators.Reflection.DefaultConstructorFinder' on type 'Functions' can be invoked with the available services and parameters: Cannot resolve parameter 'Autofac.IContainer container' of constructor 'Void .ctor(IbusinessOpsBpc,ILogging, Autofac.IContainer)'. – RajGan Jan 09 '17 at 11:05
  • I updated my answer to show a way to inject the container in the constructor, please have a look. Also, [this answer](http://stackoverflow.com/a/4609510/1182515) shows another approach. – Diana Jan 25 '17 at 23:00
  • Thanks for your reply I did came with similar solution which I will update the code sample soon.. – RajGan Feb 09 '17 at 14:34