4

I have created an application (net core 2 & ef core) with Unit Of Work and Generic repository pattern. I used to have one database context but due to some business logic I had to create a second database with some same entities.

For the sake of simplicity, I will use only one Entity, the ProductEntity and I will use only 1 repository method the Get by Id.

In the business logic I must be able to retrieve the Product from the two contexts, do some stuff and then update the contexts, but with a clean UoW design.

The repository is implemented like this

public interface IRepository<TEntity>
    where TEntity : class, new()
{      
    TEntity Get(int id);     
}

public interface IProductsRepository : IRepository<ProductEntity>
{

}

public class ProductsRepository : Repository<ProductEntity>, IProductsRepository
{
    public ProductsRepository(DbContext context) : base(context)
    {

    }
}

Implementation of UOW with one db context

public interface IUnitOfWork : IDisposable
{
    IProductsRepository ProductsRepository { get; }
    int Complete();
}

public class UnitOfWork : IUnitOfWork
{
    private readonly DbContext _context;

    public UnitOfWork(MainContext context)
    {
        // injecting the main database
        _context = context;
    }

    private IProductsRepository _productsRepository;
    public IProductsRepository ProductsRepository => _productsRepository ?? (_productsRepository = new ProductsRepository(_context));

    public int Complete()
    {
        return _context.SaveChanges();
    }

    public void Dispose()
    {
        _context?.Dispose();
    }
}

I am using the default framework of .NET Core for DI, so at my Startup.cs file I have the following

// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
    // ...

    // main database 
    services.AddDbContext<MainContext>(options => options.UseSqlServer(Configuration.GetConnectionString("MainDatabaseConnection"), providerOptions => providerOptions.CommandTimeout(30)));

    // unit of work            
    services.AddTransient<IUnitOfWork, UnitOfWork>();
}

To solve the problem I have created a second UnitOfWork with hardcoded context and I am using the same entities/repositories

My implementation with two db contexts

public interface IUnitOfWorkSecondary : IDisposable
{
    IProductsRepository ProductsRepository { get; }
    int Complete();
}

public class UnitOfWorkSecondary : IUnitOfWorkSecondary
{
    private readonly DbContext _context;

    public UnitOfWork(SecondaryDatabaseContext context)
    {
        // injecting the secondary database
        _context = context;
    }

    // same as above
}

So in a business object I am doing the following

public class Program
{
    private readonly IUnitOfWork _unitOfWork;
    private readonly IUnitOfWorkSecondary _unitOfWorkSecondary;

    public Program(IUnitOfWork unitOfWork, IUnitOfWorkSecondary unitOfWorkSecondary){
        _unitOfWork = unitOfWork;
        _unitOfWorkSecondary = unitOfWorkSecondary;
    }

    public static void Method1(int productId)
    {        
        var mainProduct = _unitOfWork.ProductsRepository.Get(productId);
        var secondaryProduct = _unitOfWorkSecondary.ProductsRepository.Get(productId);

        mainProduct.Name = "Hello Main";
        secondaryProduct.Name = "Hello Secondary";

        _unitOfWork.Complete();
        _unitOfWorkSecondary.Complete();
    }    
}

The Startup.cs is modified to

// main database 
services.AddDbContext<MainContext>(options => options.UseSqlServer(Configuration.GetConnectionString("MainDatabaseConnection"), providerOptions => providerOptions.CommandTimeout(30)));

// secondary database
services.AddDbContext<SecondaryContext>(options => options.UseSqlServer(Configuration.GetConnectionString("SecondaryDatabaseConnectiont"), providerOptions => providerOptions.CommandTimeout(30)));

// unit of work            
services.AddTransient<IUnitOfWork, UnitOfWork>();
services.AddTransient<IUnitOfWorkSecondary , UnitOfWorkSecondary>();

My questions

  1. What is the best (or most practical) design for this
  2. What if the databases are more than 2 ?
  • transaction scope works across databases stackoverflow.com/questions/22512450/… – Ewan Aug 31 '18 at 7:27
  • Sorry, this is not the case. I do not need a transaction, but an architectural solution on multiple database context and Unit Of Work pattern. – Dimitris Jan 24 '19 at 15:26
  • 2
    ? not sure why you think transaction scope does not neatly turn your two DB Context changes into one UoW – Ewan Jan 24 '19 at 15:32
-1

Personally, what I would do in a scenario such as this, would be to implement a cache layer in the middle of your UoW and DbContexts. This would provide several succinct benefits from an architectural point of view as follows:

  1. Quick Access for Visited Data - Any values which have been accessed already would be cached, as is the definition of the word!
  2. Layer of Abstraction for any DbContexts - With this method, you would essentially be creating a layer of abstraction between the UoW and DbContexts by removing the direct dependency on the contexts, and instead placing the dependency on the cache layer.
  3. Easy Extension Point - You will also gain the ability to easily plugin any additional DbContexts which are created by simply adding them to the cache layer as a data source.

Pseudo-Example

This interface would define a method by which a context is added to the Cache, and an additional method which you would call from within the consuming business logic.

public interface IDbContextCache<TContext> where TContext : new(), DbContext
{
    void AddContext<TContext>(TContext dbContext);
    TItem GetProduct<TItem>(int productId);
}

Internally you would want the implementation of the interface to utilize a data structure from the System.Collections.Concurrent namespace for storing the values when they are retreived by the GetProduct method.

Additionally I would implement private methods within the Cache implementation class, for defining the logic by which the DbContext instances are queried. Personally I would utilize the ThreadPool Class when accessing each of the registered contexts. This will allow you to ensure that the operation are non-blocking for any UI thread that may or may not be present in your scenario, but in my opinion it is never a bad idea to ensure your code is non-blocking where not required by business requirements!

Finally you would just modify your startup class to register the new IDbContextCache type as a singleton, and modify your methods which utilize the contexts to instead have the cache instance injected into their constructors or methods.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.