Design Patterns – Asynchronous Factory

By | 13/03/2024

In this post, we will see how to use the Asynchronous Factory Method pattern in our project.
But first of all, what is Asynchronous Factory Method and why should we use it?
“In many applications, especially those that involve database operations, initializing data asynchronously during startup is a common requirement.
However, constructors in C# do not support asynchronous operations directly, which poses a challenge for tasks such as asynchronous database seeding.
The Asynchronous Factory Method pattern offers a clean and efficient solution to this problem, allowing for asynchronous operations during object initialization.”

We will consider a scenario where we have a Dog class and a DataContext class as part of an Entity Framework Core application. We want to seed our in-memory database with dog entities asynchronously during application startup.

Let’s start creating a console application project where, we will install the library “Microsoft.EntityFrameworkCore.InMemory”.

Then, we define the class Dog and the class DataContext:
[DOG.CS]

namespace AsynchronousInitializationPattern
{
    public class Dog
    {
        public int Id { get; set; }
        public string Name { get; set; }
        public string Breed { get; set; }
        public string Color { get; set; }
    }
}

[DATACONTEXT.CS]

using Microsoft.EntityFrameworkCore;

namespace AsynchronousInitializationPattern
{
    public class DataContext : DbContext
    {
        public DataContext(DbContextOptions<DataContext> options) : base(options) { }

        public DbSet<Dog> Dogs { get; set; }
    }
}

Finally, we define the CommandsDog that, in a real project, it should be the business layer:
[COMMANDSDOG.CS]

using Microsoft.EntityFrameworkCore;

namespace AsynchronousInitializationPattern
{
    public class CommandsDog
    {
        private readonly DataContext _dataContext;

        public CommandsDog(DataContext dataContext)
        {
            _dataContext = dataContext;
        }

        public async Task<List<Dog>> GetAllDogs()
        {
            return await _dataContext.Dogs.AsNoTracking().ToListAsync();
        }
    }
}


Now, if we try to use the CommandsDog and call the method ‘GetAllDogs’, it obviously won’t give us any items:
[PROGRAM.CS]

using AsynchronousInitializationPattern;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;


// Setup DI and configuration
var serviceProvider = new ServiceCollection()
    .AddDbContext<DataContext>(options => options.UseInMemoryDatabase("DogsDb"))
    .BuildServiceProvider();

// Get DataContext instance
var dataContext = serviceProvider.GetRequiredService<DataContext>();


CommandsDog objCommand = new CommandsDog(dataContext);
var lstDogs = await objCommand.GetAllDogs();

Console.WriteLine("List of dogs");

foreach (var dog in lstDogs)
{
    Console.WriteLine($"{dog.Name} - {dog.Breed} - {dog.Color}");
}


To avoid this problem, we need to insert some ‘dogs’ when the class CommandsDog is instantiated:
[COMMANDSDOG.CS]

using Microsoft.EntityFrameworkCore;

namespace AsynchronousInitializationPattern
{
    public class CommandsDog
    {
        private readonly DataContext _dataContext;

        public CommandsDog(DataContext dataContext)
        {
            _dataContext = dataContext;
            await DogSeeder();
        }

        public async Task<List<Dog>> GetAllDogs()
        {
            return await _dataContext.Dogs.AsNoTracking().ToListAsync();
        }

        public async Task DogSeeder()
        {
            List<Dog> lstDogs = new List<Dog>();
            lstDogs.Add(new Dog { Id = 1, Name = "Max", Breed = "Labrador Retriever", Color = "Yellow" });
            lstDogs.Add(new Dog { Id = 2, Name = "Bella", Breed = "German Shepherd", Color = "Black" });
            lstDogs.Add(new Dog { Id = 3, Name = "Charlie", Breed = "Beagle", Color = "Tri-color" });
            lstDogs.Add(new Dog { Id = 4, Name = "Lucy", Breed = "Poodle", Color = "White" });
            lstDogs.Add(new Dog { Id = 5, Name = "Cooper", Breed = "Bulldog", Color = "Brindle" });
            lstDogs.Add(new Dog { Id = 6, Name = "Daisy", Breed = "Boxer", Color = "Fawn" });
            lstDogs.Add(new Dog { Id = 7, Name = "Bailey", Breed = "Rottweiler", Color = "Black and Tan" });
            lstDogs.Add(new Dog { Id = 8, Name = "Lola", Breed = "Siberian Husky", Color = "Grey" });
            lstDogs.Add(new Dog { Id = 9, Name = "Oliver", Breed = "Dachshund", Color = "Red" });
            lstDogs.Add(new Dog { Id = 10, Name = "Sadie", Breed = "Golden Retriever", Color = "Golden" });

            foreach (var item in lstDogs)
            {
                await _dataContext.Dogs.AddAsync(item);
            }
        }
    }
}

Unfortunately this solution doesn’t work because, we cannot call an async method in a no async method:


Precisely in these cases, thanks to the Asynchronous Factory Method pattern, we can solve the problem:
[COMMANDSDOGS.CS]

using Microsoft.EntityFrameworkCore;

namespace AsynchronousInitializationPattern
{
    public class CommandsDog
    {
        // Private field to hold the DataContext instance.
        private readonly DataContext _dataContext;

        // Private constructor to enforce the use of the factory method for instance creation.
        private CommandsDog(DataContext dataContext)
        {
            _dataContext = dataContext;
        }

        // Retrieves all Dog entities from the database without tracking changes (optimized for read-only scenarios).
        public async Task<List<Dog>> GetAllDogs()
        {
            // AsNoTracking makes the query faster since no tracking is done.
            return await _dataContext.Dogs.AsNoTracking().ToListAsync();
        }

        // Static asynchronous factory method to create and seed a CommandsDog instance.
        public static async Task<CommandsDog> CreateAndSeesAsync(DataContext dataContext)
        {
            var newCommandsDog = new CommandsDog(dataContext);
            // Calls the private DogSeeder method to seed the database.
            await newCommandsDog.DogSeeder();
            return newCommandsDog;
        }

        public async Task DogSeeder()
        {
            List<Dog> lstDogs = new List<Dog>();
            lstDogs.Add(new Dog { Id = 1, Name = "Max", Breed = "Labrador Retriever", Color = "Yellow" });
            lstDogs.Add(new Dog { Id = 2, Name = "Bella", Breed = "German Shepherd", Color = "Black" });
            lstDogs.Add(new Dog { Id = 3, Name = "Charlie", Breed = "Beagle", Color = "Tri-color" });
            lstDogs.Add(new Dog { Id = 4, Name = "Lucy", Breed = "Poodle", Color = "White" });
            lstDogs.Add(new Dog { Id = 5, Name = "Cooper", Breed = "Bulldog", Color = "Brindle" });
            lstDogs.Add(new Dog { Id = 6, Name = "Daisy", Breed = "Boxer", Color = "Fawn" });
            lstDogs.Add(new Dog { Id = 7, Name = "Bailey", Breed = "Rottweiler", Color = "Black and Tan" });
            lstDogs.Add(new Dog { Id = 8, Name = "Lola", Breed = "Siberian Husky", Color = "Grey" });
            lstDogs.Add(new Dog { Id = 9, Name = "Oliver", Breed = "Dachshund", Color = "Red" });
            lstDogs.Add(new Dog { Id = 10, Name = "Sadie", Breed = "Golden Retriever", Color = "Golden" });

            foreach (var item in lstDogs)
            {
                await _dataContext.Dogs.AddAsync(item);
            }

            _dataContext.SaveChanges();
        }
    }
}

Finally, we modify Program.cs:
[PROGRAM.CS]

// Include required namespaces for the application.
using AsynchronousInitializationPattern;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;

// Setup DI and configuration.
var serviceProvider = new ServiceCollection()
    // Register the DbContext for the application, configuring it to use an in-memory database named "DogsDb".
    .AddDbContext<DataContext>(options => options.UseInMemoryDatabase("DogsDb"))
    .BuildServiceProvider();

// Retrieve an instance of DataContext from the service provider.
var dataContext = serviceProvider.GetRequiredService<DataContext>();

// Create an instance of CommandsDog using the asynchronous factory method CreateAndSeesAsync.
var objCommand = await CommandsDog.CreateAndSeesAsync(dataContext);

// Retrieve a list of all dogs from the database asynchronously using the CommandsDog instance.
var lstDogs = await objCommand.GetAllDogs();

// Output the list of dogs to the console.
Console.WriteLine("List of dogs");
foreach (var dog in lstDogs)
{
    // For each dog, print their name, breed, and color to the console.
    Console.WriteLine($"{dog.Name} - {dog.Breed} - {dog.Color}");
}


We have done and now, if we run the application, the following will be the result:



Leave a Reply

Your email address will not be published. Required fields are marked *