2016-08-04 25 views
6

Ich habe eine API-Anwendung, die mehrere Datenbank-Shards mit StructureMap für die Abhängigkeitsinjektion verwendet. Einer der erforderlichen Header in jedem API-Aufruf ist ein ShardKey, der mir mitteilt, welche Datenbank dieser Aufruf adressiert. Um dies zu bewirken, ich habe eine Klasse namens OwinMiddlewareShardingMiddleware, die den folgenden Code enthält (aus Gründen der Übersichtlichkeit snipped):Cross-Thread-Konflikte in StructureMap

var nestedContainer = container.GetNestedContainer(); 
using (var db = MyDbContext.ForShard(shardKey)) // creates a new MyDbContext with connection string appropriate to shardKey 
{ 
    nestedContainer.Configure(cfg => cfg.For<MyDbContext>().Use(db)); 
    await Next.Invoke(context); 
} 

Dies funktioniert wunderbar in meiner Testumgebung und übergibt eine Batterie von Integrationstests.

Aber die Integrationstests sind effektiv single-threaded. Als ich dies in eine QA-Umgebung implementierte, in der eine echte App mit mehreren simultanen Anrufen auf meine API trifft, beginnen die Dinge sich in eine birnenförmige Form zu verwandeln. Ferinstance:

System.ObjectDisposedException: Zugriff auf ein Objekt nicht möglich. Eine häufige Ursache für diesen Fehler ist die Bereitstellung eines Kontexts, der aus der Abhängigkeitsinjektion gelöst wurde und später an anderer Stelle in Ihrer Anwendung dieselbe Kontextinstanz verwenden soll. Dies kann auftreten, wenn Sie Dispose() für den Kontext aufrufen oder den Kontext in einer using-Anweisung umbrechen. Wenn Sie die Abhängigkeitsinjektion verwenden, sollten Sie den Dependency-Injektionscontainer veranlassen, Kontextinstanzen zu entfernen.

Oder andere Ausnahmen, die darauf hinweisen, dass StructureMap keine gültige Instanz von MyDbContext zur Verfügung hat.

Für mich scheint es, dass die mehreren Threads irgendwie die Konfiguration gegenseitig durcheinander bringen, aber für das Leben von mir kann ich nicht verstehen, wie ich einen verschachtelten Container verwende, um den Datenbankkontext für jede API zu speichern Anruf.

Irgendwelche Ideen, was hier schief gehen könnte?

Update: Ich habe auch versucht, meine DB-Kontext in eine Schnittstelle zu abstrahieren. Hat keinen wirklichen Unterschied gemacht; Ich bekomme immer noch den Fehler

System.InvalidOperationException: Beim Versuch, einen Controller des Typs 'SomeController' zu erstellen, ist ein Fehler aufgetreten. Stellen Sie sicher, dass der Controller über einen parameterlosen öffentlichen Konstruktor verfügt. ---> StructureMap.StructureMapConfigurationException: Keine Standard-Instanz registriert ist und nicht automatisch für Typen

Update ‚MyNamespace.IMyDbContext‘ bestimmt werden kann 2: ich das Problem gelöst, aber die Prämie ist noch offen. Bitte sehen Sie meine Antwort unten.

+0

Ihr 'DbContext' könnte als [Captive Dependency] am Leben erhalten werden (http://blog.ploeh.dk/2014/06/02/captive-dependency/). Stellen Sie sicher, dass Konsumenten dieser Abhängigkeit keine Lebensdauer haben, die länger ist als die des "DbContext" oder - besser - verhindern, dass der DbContext direkt in die Konsumenten injiziert wird. Ein 'DbContext' ist Runtime-Daten und Runtime-Daten [sollten nicht in Komponenten injiziert werden] (https://www.cuttingedge.it/blogs/steven/pivot/entry.php?id=99). Verstecken Sie stattdessen den DbContext hinter einer Abstraktion. – Steven

Antwort

2

Nun ... Ich löste das Problem, aber ich verstehe nicht warum dies einen Unterschied gemacht.

Es läuft auf einige feine Unterschiede von dem hinaus, was ich ursprünglich gepostet habe, was ich weggelassen habe, weil ich dachte, die Details wären belanglos und hätten mich von der Frage abgelenkt.Mein Container wurde in der Tat nicht lokal definiert; sondern es ist ein geschütztes Eigentum meiner Middleware war (es ist für die Integration Testzwecke geerbt):

protected IContainer Container { get; private set; } 

Dann wird ein Initialisierungsaufruf innerhalb der Invoke() Methode war:

Container = context.GetNestedContainer(); // gets the nested container created by a previous middleware class, using the context.Environment dictionary 

Mit Logging-Anweisungen während des gesamten Verfahrens, ich habe bis auf den folgenden Code (wie in der Frage erwähnt, mit der Protokollierung hinzugefügt):

_logger.Debug($"Line 1 Context={context.GetHashCode}, Container={Container.GetHashCode()}"); 
var db = MyDbContext.ForShard(shardKey.Value); // no need for "using", since DI will automatically dispose 
_logger.Debug($"Line 2 Context={context.GetHashCode}, Container={Container.GetHashCode()}"); 
Container.Configure(cfg => cfg.For<MyDbContext>().Use(db)); 
await Next.Invoke(context); 

Und astoundingly, hier ist was aus den Protokollen kam:

Linie 1 Context = 56.852.305, Container = 48376271

Linie 1 Context = 88.275.661, Container = 85736099

Linie 2 context = 56.852.305, Container = 85736099

Zeile 2 Kontext = 88275661, Container = 85736099

Erstaunlich! Die Container Eigenschaft meiner Middleware wurde magisch ersetzt! Dies, trotz der Tatsache, dass es mit einem definiert ist, und trotzdem, nur um sicher zu sein, überprüfte ich den Code für MyDbContext.ForShard() und fand nichts, was die Referenz für Container hätte vermasselt hätte.

Also was war die Lösung? Ich habe eine lokale container Variable direkt nach der Initialisierung deklariert und stattdessen verwendet.

Es funktioniert jetzt, aber ich verstehe nicht, warum oder wie dies einen Unterschied hätte machen können.

Bounty geht an die Person, die das erklären kann.

+0

Ich denke, die Gründe sind die gleichen wie in 'Warum verschachtelte Container über HttpContext oder ThreadLocal Scoping?' Beschrieben. hier http://structymap.github.io/the-container/nested-containers/ – ATechieThought

+0

@ATechieThought können Sie das mehr erklären? Ich habe immer den OwinContext verwendet, um den Container zu erhalten, nicht HttpContext oder ThreadLocal. –

+0

mein schlechtes. Ich habe es verpasst. aber mein Denken war irgendwie httpcontext beteiligt, indem ich die Kontextwerte betrachtete. Jetzt ist httpcontext aus dem Bild, gibt es eine Chance, dass MyDbContext beteiligt ist und es sollte als Singleton eingerichtet werden? Entschuldigung, wenn, ich helfe nicht einen Weg zur Begründung des Problems. – ATechieThought

2

Sie sollen dieses umschreiben:

using (var db = MyDbContext.ForShard(shardKey)) // creates a new MyDbContext with connection string appropriate to shardKey 
{ 
    nestedContainer.Configure(cfg => cfg.For<MyDbContext>().Use(db)); 
    await Next.Invoke(context); 
} 

Ursache using entsorgen Ihre DbContext am Ende mit.

Sie sollten Fabrik registrieren statt:

var dbFactory =()=>MyDbContext.ForShard(shardKey); 
nestedContainer.Configure(cfg => cfg.For<Func<MyDbContext>>().Use(dbFactory)); 
await Next.Invoke(context); 

und injizieren diese Func statt DbContext Instanz.

+0

Wenn Sie ein Func anstelle einer Instanz injizieren, wird es eine neue Db-Verbindung in jeder Klasse öffnen, in die MyDbContext injiziert wird? –

+0

hängt davon ab. Standardmäßig - ja. Ist es ein Problem? –

+0

Nicht sicher, ob es ein Problem ist. Meine derzeitige Denkweise ist, dass eine einzelne db-Verbindung für eine Arbeitseinheit ausreichen sollte. Ich bin mir nicht sicher, ob es Konflikte geben könnte, wenn es mehr Verbindungen gibt - oder wenn es die DB übermäßig belastet, zu viele Verbindungen geöffnet zu haben ... aber das ist für mich eine Sorge. Wenn ich eine Verbindung pro UOW haben möchte, übergebe ich die Instanz, und wenn ich eine Verbindung pro Repository möchte, benutze ich den Func. Vielen Dank! –

0

aus den Protokollen, was ich sehe, ist, dass die zweite Anforderung/Thread den Container überschreibt und respektvoll den Datenbankkontext des ersten beide so die gleiche Verbindung verwenden:

Line 2 Context=56852305, Container=85736099 

sollte

Line 2 Context=56852305, Container=48376271 

oder bekomme ich es falsch, so glaube ich nicht, dass Sie es lösen.Der System.ObjectDisposedException Fehler ist von der using Klausel, die Sie verwenden Instanz Ihrer db Kontext zu schaffen, und weil es die Next Delegierten und die context angeordnet. Ich habe auch nicht die Linie verstanden

Container = context.GetNestedContainer(); 

vielleicht haben Sie im Sinn hatte

Container = container.GetNestedContainer(); 

? Ich bin nicht mit StructureMap vertraut, aber ich denke, der Code sollte wie folgt

var nestedContainer = Container.GetNestedContainer(c => 
        { 
         var db = MyDbContext.ForShard(shardKey); 
         c.For<MyDbContext>().Use(db); 
        }); 

await Next.Invoke(context); 

unter der Annahme, dass die Behälter schließen aussieht und DB-Verbindung verfügt, wenn es angebracht ist.