Comment utiliser Ninject dans un service Windows multi-threadé pour obtenir de nouvelles instances d’une dépendance (DbContext) à chaque tick?

J’ai hérité d’un service Windows dans lequel toutes les dépendances sont créées au démarrage du service et injectées dans l’étendue transitoire.

Nous rencontrons un certain nombre de problèmes avec ce service. Nous avons notamment un DbContext qui dure pendant toute la durée de l’exploitation du service, et différentes instances sont injectées à chaque fois.

Je voudrais refactoriser afin que chaque thread de travail reçoive son propre DbContext injecté qui vivra uniquement pendant la durée de chaque tick.

J’ai regardé la scope personnalisée . Cela semble bien pour une application à un seul thread, mais pas à plusieurs threads. J’ai aussi considéré InThreadScope . Alors que cela donnerait à chaque thread sa propre instance, ce sont des singletons en ce qui concerne le thread, de sorte qu’il ne remplit pas l’exigence par tick.

Mon idée actuelle est d’utiliser l’ extension de scope nommée et d’injecter une fabrique de scope que je peux utiliser pour créer une nouvelle scope à chaque tick.

Est-ce le chemin à parcourir? Toute suggestion, astuce ou alternative serait la bienvenue.

METTRE À JOUR

En raison d’une contrainte de temps, nous avons fini par utiliser l’étendue nommée, mais celle-ci n’était pas aussi propre que la solution de @ BatteryBackupUnit. Il y avait des dépendances plus loin dans le graphique qui nécessitaient un DbContext et nous avons dû réinjecter l’usine de l’oscilloscope pour l’obtenir. En utilisant la solution de @ BatteryBackupUnit, nous aurions pu réutiliser la même instance à partir du stockage ThreadLocal .

En ce qui concerne l’étendue nommée: notez que lorsque vous créez un DbContext à partir du même thread, mais à partir d’un object (fab.ex. fab.) Créé avant la création de l’étendue, cela ne fonctionnera pas. Soit il échouera parce qu’il n’ya pas de scope, soit il injectera une autre instance de DbContext car il y a une scope différente. Si vous ne le faites pas, une étendue comme celle nommée ou celle des appels peut fonctionner pour vous.

Nous faisons les choses suivantes à la place:

Lorsqu’un DbContext est demandé, nous vérifions si un ThreadLocal ( http://msdn.microsoft.com/de-de/library/dd642243%28v=vs.110%29.aspx ) existe déjà. Dans le cas contraire, nous l’utilisons. Sinon, nous en créons un nouveau et l’ ThreadLocal.Value à ThreadLocal.Value . Une fois que toutes les opérations sont terminées, nous libérons le DbContext et réinitialisons le ThreadLocal.Value .

Voir ce code (simplifié, pas parfait) pour un exemple:

 public interface IUnitOfWork { IUnitOfWorkScope Start(); } internal class UnitOfWork : IUnitOfWork { public static readonly ThreadLocal LocalUnitOfWork = new ThreadLocal(); private readonly IResolutionRoot resolutionRoot; public UnitOfWork(IResolutionRoot resolutionRoot) { this.resolutionRoot = resolutionRoot; } public IUnitOfWorkScope Start() { if (LocalUnitOfWork.Value == null) { LocalUnitOfWork.Value = this.resolutionRoot.Get(); } 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().To(); this.kernel.Bind().To(); } [Fact] public void MustCreateNewScopeWhenOldOneWasDisposed() { Guid scopeId1; using (IUnitOfWorkScope scope = this.kernel.Get().Start()) { scopeId1 = scope.Id; } Guid scopeId2; using (IUnitOfWorkScope scope = this.kernel.Get().Start()) { scopeId2 = scope.Id; } scopeId1.Should().NotBe(scopeId2); } [Fact] public void NestedScope_MustReuseSameScope() { Guid scopeId1; Guid scopeId2; using (IUnitOfWorkScope scope1 = this.kernel.Get().Start()) { scopeId1 = scope1.Id; using (IUnitOfWorkScope scope2 = this.kernel.Get().Start()) { scopeId2 = scope2.Id; } } scopeId1.Should().Be(scopeId2); } [Fact] public void MultipleThreads_MustCreateNewScopePerThread() { var unitOfWork = this.kernel.Get(); 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(); } } 

Remarque: j’utilise des packages de nuget: ninject, xUnit.Net, Assertions Fluent

Notez également que vous pouvez remplacer le IUnitOfWork.Start par une ToProvider() . Bien entendu, vous devez implémenter la logique correspondante dans le fournisseur.

Une étendue d’unité de travail appropriée, implémentée dans Ninject.Extensions.UnitOfWork , résout ce problème.

Installer:

 _kernel.Bind().To().InUnitOfWorkScope(); 

Usage:

 using(UnitOfWorkScope.Create()){ // resolves, async/await, manual TPL ops, etc }