Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

C# Generic Repository - Unit Of Work - Thread Safety

I'm currently writing an application that's relying on a database and I'm using Entity Framework (version 6.1.1 according to Nuget).

Now I've written a Repository pattern which looks like the following:

public class RepositoryBase<TEntity> where TEntity : class
{
    #region Constructors

    protected RepositoryBase(IDbContext context, IUnitOfWork unitOfWork)
    {
        Context = context;
        DbSet = Context.Set<TEntity>();
        UnitOfWork = unitOfWork;
    }

    #endregion

    #region Properties

    protected IDbSet<TEntity> DbSet;

    protected readonly IDbContext Context;

    protected readonly IUnitOfWork UnitOfWork;

    #endregion

    #region Methods

    protected TEntity Get(Expression<Func<TEntity, bool>> filter)
    {
        DbSet.ThrowIfNull("DbSet");

        IQueryable<TEntity> query = DbSet;

        return !query.Any() ? null : !query.Where(filter).Any() ? null : query.First(filter);
    }

    protected TEntity Get(Expression<Func<TEntity, bool>> filter, string[] includeProperties)
    {
        DbSet.ThrowIfNull("DbSet");

        IQueryable<TEntity> query = DbSet;

        includeProperties.Each(x => query = query.Include(x));

        return !query.Any() ? null : !query.Where(filter).Any() ? null : query.First(filter);
    }

    protected virtual IQueryable<TEntity> GetAll()
    {
        DbSet.ThrowIfNull("DbSet");

        IQueryable<TEntity> query = DbSet;

        return query.AsQueryable();
    }

    protected IQueryable<TEntity> GetAll(string[] includeProperties)
    {
        DbSet.ThrowIfNull("DbSet");

        IQueryable<TEntity> query = DbSet;

        includeProperties.Each(x => query = query.Include(x));

        return query.AsQueryable();
    }

    protected IQueryable<TEntity> GetAll(Expression<Func<TEntity, bool>> filter)
    {
        DbSet.ThrowIfNull("DbSet");

        IQueryable<TEntity> query = DbSet;

        query = DbSet.Where(filter);

        return query;
    }

    protected IQueryable<TEntity> GetAll(Expression<Func<TEntity, bool>> filter, string[] includeProperties)
    {
        DbSet.ThrowIfNull("DbSet");

        IQueryable<TEntity> query = DbSet;

        query = DbSet.Where(filter);

        includeProperties.Each(x => query = query.Include(x));

        return query;
    }

    #endregion
}

I do have other classes that inherit from this Repository, but I'll leave them out of scope for the moment.

Now, when I do a Get on a repository, I retrieve the data, but when I execute 2 gets call at the sime time (I just open 2 browser windows with the same url and execute them), then the following error does popup:

An exception of type 'System.Data.Entity.Core.EntityException' occurred in EntityFramework.SqlServer.dll but was not handled in user code

Additional information: The underlying provider failed on Open.

Here's a little image to provide as much as detailed information possible:

enter image description here

Now, I know that by default the Entity Framwork DbContext is not thread-safe, so I know I should do something to make it thread-safe, but I just don't know how to do this.

Can anybode provide my any guidance on how to achieve this? Also, I don't have much experience with IDisposeable, so if you advice to implement this somewhere, it should be very kind of you to provide me some information on how to implement this.

Something important to mention is that I'm using Unity to manage my objects, of which you can find the configuration here:

DynamicModuleUtility.RegisterModule(typeof(UnityPerRequestHttpModule));

The registration of my types is done here:

public static void RegisterTypes(IUnityContainer container)
{
    container.RegisterType<IDbContext, OxygenDataContext>();
    container.RegisterType<IUnitOfWork, UnitOfWork>(new PerRequestLifetimeManager());
}

Where the class 'OxygenDataContext' is my implementation of a 'DbContext' and 'IDbContext'. That code can be found below (a snipped, I don't post all my elements):

public class OxygenDataContext : DbContext, IDbContext
{
    #region Constructors

    public OxygenDataContext()
        : base("Oxygen")
    {
        Configuration.ProxyCreationEnabled = false;
    }

    #endregion

    #region IDbContext Members

    public IDbSet<TEntity> Set<TEntity>() where TEntity : class
    {
        return base.Set<TEntity>();
    }

    public void SaveChanges()
    {
        base.SaveChanges();
    }

    #endregion
}

Update

According to me, it's Unity which is causing this behaviour since I'm using:

container.RegisterType<IDbContext, OxygenDataContext>();

This means that no new instance of the OxygenDataContext is created on every request.

Thanks for the help.

Update 2

When I modify the Unity confiruation like the following:

container.RegisterType<IDbContext, OxygenDataContext>(new PerRequestLifetimeManager());

I tought that there would be only 1 single instance, but it seems that it's not working since I keep receiving the same error.

Update 3

I've changed the Unity configuration to resolve the IDbContext, which is a DbContext with a 'PerResolveLifetimeManager' using the following code:

container.RegisterType<IDbContext, OxygenDataContext>(new PerResolveLifetimeManager());

This would mean that every resolve call should create a new instance of the 'OxygenDataContext' or an I not correct?

In the constructor of my UnitOfWork, I'm assigning the repositories like:

/// <summary>
///     Creates a new instance of the <see cref="IUnitOfWork"/>.
/// </summary>
/// <param name="context">The <see cref="IDbContext"/> in which the entities are available.</param>
public UnitOfWork(IDbContext context)
    : base(context)
{
    versioningRepository = new Repository<Versioning, IDbContext>(context, this);

    settingRepository = new Repository<Setting, IDbContext>(context, this);
    siteRepository = new VersionedRepository<Site, int, IDbContext>(context, this);
    pageRepository = new VersionedRepository<Page, int, IDbContext>(context, this);
    layoutRepository = new VersionedRepository<Layout, int, IDbContext>(context, this);
    assemblyRepository = new VersionedRepository<Assembly, int, IDbContext>(context, this);
    logRepository = new Repository<Log, IDbContext>(context, this);
}

So this should mean that every repository is getting it's own unique instance of the 'IDbContext', however it doesn't work as expected.

Update 4

I've managed to resolve my own issue, be patient as I'm writing the solution as an answer to this post.

Update 5

It doesn't seem to work. See here my solution which I thought was working:

I adapt my IUnitOfWork interface to implement the IDisposeable interface and than re-adjust the implementation to the following:

protected virtual void Dispose(bool disposing)
{
    if (disposing) { Context = null; }
}

public void Dispose()
{
    Dispose(true);
}

However, it doesn't work. I'm getting a bit lost here, so if anyone knows a solution :-)

Update 6

According to some posts, I need to provide some information on how that I instantiate my repositories. So here's the explanation:

I'm using a UnitOfWork that contains all the repositories:

/// <summary>
///     Creates a new instance of the <see cref="IUnitOfWork"/>.
/// </summary>
/// <param name="context">The <see cref="IDbContext"/> in which the entities are available.</param>
public UnitOfWork(IDbContext context)
    : base(context)
{
    versioningRepository = new Repository<Versioning>(context, this);

    settingRepository = new Repository<Setting>(context, this);
    siteRepository = new VersionedRepository<Site, int>(context, this);
    pageRepository = new VersionedRepository<Page, int>(context, this);
    layoutRepository = new VersionedRepository<Layout, int>(context, this);
    assemblyRepository = new VersionedRepository<Assembly, int>(context, this);
    logRepository = new Repository<Log>(context, this);
}

I've also tried to change the code to the following, but that doesn't work either:

/// <summary>
///     Creates a new instance of the <see cref="IUnitOfWork"/>.
/// </summary>
/// <param name="context">The <see cref="IDbContext"/> in which the entities are available.</param>
public UnitOfWork(IDbContext context)
    : base(context)
{
    versioningRepository = new Repository<Versioning>(context, this);

    settingRepository = new Repository<Setting>(new OxygenDataContext(), this);
    siteRepository = new VersionedRepository<Site, int>(new OxygenDataContext(), this);
    pageRepository = new VersionedRepository<Page, int>(new OxygenDataContext(), this);
    layoutRepository = new VersionedRepository<Layout, int>(new OxygenDataContext(), this);
    assemblyRepository = new VersionedRepository<Assembly, int>(new OxygenDataContext(), this);
    logRepository = new Repository<Log>(new OxygenDataContext(), this);
}

I still receive the error message: "The underlying provider failed on Open" with an inner exception that says "The connection was not closed. The connection's current state is connecting."

You see that the constructor of the UnitOfWork does take a IDbContext instance and all the repositories are constructed with that very same instance.

I'm using Unity, and the registration of my IDbContext takes the following code:

container.RegisterType<IDbContext, OxygenDataContext>();

In my Unity configuration, I also do register the IUnitOfWork interface:

container.RegisterType<IUnitOfWork, UnitOfWork>(new PerRequestLifetimeManager());

I do have the feeling that the error is somewhere in the Unity registration or in the UnitOfWork, but I don't seem to find it.

like image 718
Complexity Avatar asked Feb 12 '23 00:02

Complexity


2 Answers

If I'm getting your message right, you're sharing the same DbContext among threads. That is not what DbContext is meant to be used for. Sharing data in that way is very difficult.

I don't like giving answers in the form "but why you need it", but this post looks just like that.

I would suggest you redesign the solution and to keep DbContext objects short lived - do an operation and dispose DbContext. Perform a unit of work, commit/rollback and be done with the repository. Don't forget that DbContext is going to track all objects that it fetches. You probably don't want tracked objects to live longer in memory than the transaction in which they were used.

Let the database be the point of sharing data from multiple threads.

like image 183
Zoran Horvat Avatar answered Feb 15 '23 09:02

Zoran Horvat


It's possible that you're not fully disposing the context here:

protected virtual void Dispose(bool disposing)
{
    if (disposing) { Context = null; }
}

public void Dispose()
{
    Dispose(true);
}

My understanding is, when you create an abstraction of dbContext (like your UoW) you want to expressly call the Garbage Collector to ensure you've cleaned up unused resources. Also, you need to dispose the dbContext not set it to null.

Example:

    private bool disposed = false;

    protected virtual void Dispose(bool disposing) 
    {
        if (!this.disposed) 
        {
            if (disposing)
               context.Dispose(); // <-- dispose
        }
        this.disposed = true;
    }

    public void Dispose() 
    {
        Dispose(true);
        GC.SuppressFinalize(this);
    }

Please give this a try :) - hopefully it helps.

like image 39
wahwahwah Avatar answered Feb 15 '23 10:02

wahwahwah