Run EF Core Database Operations in Background

The purpose of this article is to provide a simple framework for queuing EF core database jobs that can be executed asynchronously using the Task.Run method.

In this article, we will explore the various components of the code and understand their functionality.

IInvoke Interfaces

The IInvoke interfaces are defined as follows:

public interface IInvoke
{
    Task InvokeAsync();
}

public interface IInvoke<TParams>
{
    TParams Data { get; set; }
}

The IInvoke interface declares a single method named InvokeAsync, an asynchronous method executed when a job is queued.

The IInvoke<TParams> interface declares a property named Data, which can be used to pass data to the job.

IJob Interface

The IJob interface is defined as follows:

public interface IJob
{
    void QueueJob<T, TParams>(TParams data) where T : IInvoke, IInvoke<TParams>;      
}

The interface declares a single method named QueueJob, which takes in two generic parameters, T and TParams. T represents a type that implements the IInvoke and IInvoke<TParams> interfaces. TParams represents the data that will be passed to the job.

Job Class

The Job class implements the IJob interface and provides the implementation for the QueueJob method.

public class Job : IJob
{
    private readonly IServiceProvider _serviceProvider;
    public Job(IServiceProvider serviceProvider)
    {
        _serviceProvider = serviceProvider;
    }

    public void QueueJob<T, TParams>(TParams data) where T : IInvoke, IInvoke<TParams>
    {
        _ = Task.Run(async () =>
        {
            using (var scope = _serviceProvider.CreateScope())
            {
                var service = scope.ServiceProvider.GetService<T>();

                if (service != null)
                {
                    service.Data = data;

                    await service.InvokeAsync();
                }
            }
        });
    }
}

The Job class constructor takes in an IServiceProvider object, which is used to get the required service from dependency injection. The QueueJob method takes in a data object and queues a job for execution.

Add as Singleton in your application DI

services.AddSingleton<IJob, Job>();

Usage

Here is a simple example to utilize this.

public class BackgroundJob: IInvoke, IInvoke<Guid>
{  
    private readonly ApplicationDbContext _context;
    BackgroundJob(ApplicationDbContext context)
    {
        _context = context;
    }
    
    public Guid Data { get; set; }

    public async Task InvokeAsync()
    {
        // Your logic goes here
    }
}

Don't forget to inject the class in DI

services.AddTransient<BackgroundJob>();

Now, You can queue the job from anywhere in your application, as below

_job.QueueJob<BackgroundJob, Guid>(userId);

//Here, _job is instance of IJob which you can get from DI and use anywhere in your app and userId is the parameter which will be used in the logic

Conclusion

In conclusion, the above code provides a simple framework for queuing jobs that can be executed asynchronously using the Task.Run method.