user2945722
user2945722

Reputation: 1313

Should i be passing an entity DbContext by ref when sharing the same one between services / repositories

My services take a DbContext in their constructor, and I have created a UnitOfWork class that contains all my services in order to make sure the same DbContext is used between them all.

Sample of unitofwork class

   private myEntities myContext

   public UnitOfWork()
    {
        myContext = new myEntities();
    }

    private RequestService requestService;
    public RequestService RequestService
    {
        get
        {
            if (requestService == null)
                requestService = new RequestService(myContext);

            return requestService;
        }

    }

By Using this unitofwork class all the DbContext for my services are now consistent and a change made in one service will appear in another.

However if i need to change the actual Entity context class then that does not get persisted across each service.

Below i have a "Refresh" method that re-initializes it (I need to refresh the context so i can have this class work with some legacy code).

    public void Refresh()
    {
        myContext = new myEntities();
    }

However my service classes DbContext objects aren't passed by ref so the context is not set to a new instance of my entity class and this results in the context not being refreshed.

So I think i can solve this by passing by ref as shown below

Service class sample

    MyEntities myContext;

    public RequestService(ref MyEntities myContext)
    {
        this.myContext = myContext;

    }

However i have seen people say you should not pass context classes by ref so i am curious if there is a better way out there and i am looking at this the wrong way?

Edit

Sorry turns out my proposed solution of passing by ref does not solve my problem, but i am still interested as to how i can update the entity context on the UnitOfWork class e.g. setting it to null and have that effect the service classes.

Upvotes: 1

Views: 1992

Answers (1)

Darek
Darek

Reputation: 4797

Never ever should you share DbContext, by reference or as reference. It is not thread safe.

http://msdn.microsoft.com/en-us/data/jj729737.aspx

If you need an easy way to generate multiple DbContext, use ObjectPool from Parallel Extensions Extras.

Update 1

@tia is correct in saying that the private instance will not be updated when original changes:

class Program
{
    static void Main(string[] args)
    {
        var pool1 = new ObjectPool<IDbConnection>(() => new SqlConnection("Data Source=server1"));
        var service = new Service(ref pool1);
        pool1 = new ObjectPool<IDbConnection>(() => new SqlConnection("Data Source=server2"));
        Console.WriteLine(service.Pool.GetObject().ConnectionString);
    }
}

class Service
{
    private ObjectPool<IDbConnection> connectionPool;

    public Service(ref ObjectPool<IDbConnection> pool) { this.connectionPool = pool; }

    public ObjectPool<IDbConnection> Pool { get { return connectionPool; }  }
}

Will print "Data Source=server 1", even if it would be a static field.

Enter Monostate, a wicked pattern, very similar to Singleton.

class Program
{
    static void Main(string[] args)
    {
        var mop = new MonoObjectPool();
        mop.Pool = new ObjectPool<IDbConnection>(() => new SqlConnection("Data Source=server1"));
        var service = new Service();
        mop.Pool = new ObjectPool<IDbConnection>(() => new SqlConnection("Data Source=server2"));
        Console.WriteLine(service.Pool.GetObject().ConnectionString);
    }
}

internal class MonoObjectPool
{
    private static ObjectPool<IDbConnection> pool1;

    public ObjectPool<IDbConnection> Pool

    {
        get { return pool1; }
        set { pool1 = value; }
    }
}

class Service
{
    public ObjectPool<IDbConnection> Pool { get { return new MonoObjectPool().Pool; } }
}

I am getting rid of the constructor for service, as I can always get the current IDbConnection generator. There will always be only one instance of it, regardless how many times someone instantiates the MonoObjectPool.

Update 2

The other option might be to use Autofac, but I am not too familiar with it, yet, so I can't give you an example how a type could get resolved in a service instance. Here is a simple example:

class Program
{

    private static IContainer container { get; set; }

    static void Main(string[] args)
    {
        var builder = new ContainerBuilder();
        builder.RegisterType<DbCtx1>().As<IDbCtx>();
        container = builder.Build();

        using (var scope = container.BeginLifetimeScope())
        {
            var dbctx = scope.Resolve<IDbCtx>();
            Console.WriteLine(dbctx.GetType());
        }

        builder = new ContainerBuilder();
        builder.RegisterType<DbCtx2>().As<IDbCtx>();
        container = builder.Build();

        using (var scope = container.BeginLifetimeScope())
        {
            var dbctx = scope.Resolve<IDbCtx>();
            Console.WriteLine(dbctx.GetType());
        }
    }
}

interface IDbCtx
{

}

class DbCtx1 : IDbCtx { }
class DbCtx2 : IDbCtx { }

Update 3

So going back to the Monostate, this works as expected:

class Program
{
    static void Main(string[] args)
    {
        var mop = new MonoObjectPool();
        mop.Pool = new ObjectPool<IDbConnection>(() => new SqlConnection("Data Source=server1"));
        var service = new Service(mop);
        mop.Pool = new ObjectPool<IDbConnection>(() => new SqlConnection("Data Source=server2"));
        Console.WriteLine(service.Pool.GetObject().ConnectionString);
    }
}

internal class MonoObjectPool
{
    private static ObjectPool<IDbConnection> pool1;

    public ObjectPool<IDbConnection> Pool
    {
        get { return pool1; }
        set { pool1 = value; }
    }
}

class Service
{
    private MonoObjectPool myPool;
    public Service(MonoObjectPool pool) { myPool = pool; }
    public ObjectPool<IDbConnection> Pool { get { return myPool.Pool; } }
}

I hope this helps.

Upvotes: 3

Related Questions