How to use Ninject in a multi-threaded Windows service to get new instances of a dependency (DbContext) on every tick?

£可爱£侵袭症+ 提交于 2019-11-28 14:25:06

Regarding Named Scope: Consider that when you are creating a DbContext from the same thread but from an object (p.Ex. factory) which was created before the scope was created, it won't work. Either it will fail because there is no scope, or it will inject another instance of DbContext because there is a different scope. If you don't do this, then a scope like named scope or call scope can work for you.

We are doing the following instead:

When a DbContext is requested, we check a ThreadLocal (http://msdn.microsoft.com/de-de/library/dd642243%28v=vs.110%29.aspx) whether there is already one. In case there is, we use that one. Otherwise, we create a new one and assign it to the ThreadLocal<DbContext>.Value. Once all operations are done, we release the DbContext and reset the ThreadLocal<DbContext>.Value.

See this (simplified, not perfect) code for an example:

public interface IUnitOfWork
{
    IUnitOfWorkScope Start();
}

internal class UnitOfWork : IUnitOfWork
{
    public static readonly ThreadLocal<IUnitOfWorkScope> LocalUnitOfWork = new ThreadLocal<IUnitOfWorkScope>();

    private readonly IResolutionRoot resolutionRoot;

    public UnitOfWork(IResolutionRoot resolutionRoot)
    {
        this.resolutionRoot = resolutionRoot;
    }

    public IUnitOfWorkScope Start()
    {
        if (LocalUnitOfWork.Value == null)
        {
            LocalUnitOfWork.Value = this.resolutionRoot.Get<IUnitOfWorkScope>();
        }

        return LocalUnitOfWork.Value;
    }
}

public interface IUnitOfWorkScope : IDisposable
{
    Guid Id { get; }
}

public class UnitOfWorkScope : IUnitOfWorkScope
{
    public UnitOfWorkScope()
    {
        this.Id = Guid.NewGuid();
    }

    public Guid Id { get; private set; }

    public void Dispose()
    {
        UnitOfWork.LocalUnitOfWork.Value = null;
    }
}

public class UnitOfWorkIntegrationTest : IDisposable
{
    private readonly IKernel kernel;

    public UnitOfWorkIntegrationTest()
    {
        this.kernel = new StandardKernel();
        this.kernel.Bind<IUnitOfWork>().To<UnitOfWork>();
        this.kernel.Bind<IUnitOfWorkScope>().To<UnitOfWorkScope>();
    }

    [Fact]
    public void MustCreateNewScopeWhenOldOneWasDisposed()
    {
        Guid scopeId1;
        using (IUnitOfWorkScope scope = this.kernel.Get<IUnitOfWork>().Start())
        {
            scopeId1 = scope.Id;
        }

        Guid scopeId2;
        using (IUnitOfWorkScope scope = this.kernel.Get<IUnitOfWork>().Start())
        {
            scopeId2 = scope.Id;
        }

        scopeId1.Should().NotBe(scopeId2);
    }

    [Fact]
    public void NestedScope_MustReuseSameScope()
    {
        Guid scopeId1;
        Guid scopeId2;
        using (IUnitOfWorkScope scope1 = this.kernel.Get<IUnitOfWork>().Start())
        {
            scopeId1 = scope1.Id;
            using (IUnitOfWorkScope scope2 = this.kernel.Get<IUnitOfWork>().Start())
            {
                scopeId2 = scope2.Id;
            }
        }

        scopeId1.Should().Be(scopeId2);
    }

    [Fact]
    public void MultipleThreads_MustCreateNewScopePerThread()
    {
        var unitOfWork = this.kernel.Get<IUnitOfWork>();
        Guid scopeId1;
        Guid scopeId2 = Guid.Empty;
        using (IUnitOfWorkScope scope1 = unitOfWork.Start())
        {
            scopeId1 = scope1.Id;
            Task otherThread = Task.Factory.StartNew(() =>
                {
                    using (IUnitOfWorkScope scope2 = unitOfWork.Start())
                    {
                        scopeId2 = scope2.Id;
                    }
                },
                TaskCreationOptions.LongRunning);
            if (!otherThread.Wait(TimeSpan.FromSeconds(5)))
            {
                throw new TimeoutException();
            }
        }

        scopeId2.Should().NotBeEmpty();
        scopeId1.Should().NotBe(scopeId2);
    }

    public void Dispose()
    {
        this.kernel.Dispose();
    }
}

Note: i'm using nuget packages: ninject, xUnit.Net, Fluent Assertions

Also note, that you can replace the IUnitOfWork.Start with a ToProvider<IUnitOfWorkScope>() binding. Of course you need to implement the corresponding logic in the provider.

A proper unit-of-work scope, implemented in Ninject.Extensions.UnitOfWork, solves this problem.

Setup:

_kernel.Bind<IService>().To<Service>().InUnitOfWorkScope();

Usage:

using(UnitOfWorkScope.Create()){
    // resolves, async/await, manual TPL ops, etc    
}
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!