2016-08-04 6 views
6

종속성 주입을 위해 StructureMap과 함께 여러 데이터베이스 조각을 사용하는 API 응용 프로그램이 있습니다. 각 API 호출에서 필요한 헤더 중 하나는 ShardKey이며이 호출이 어떤 호출을 처리하는지 알려줍니다.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); 
} 

이 내 테스트 환경에서 아름답게 작동 및 통합 테스트의 배터리를 전달합니다이를 적용하기 위해, 나는 (명확성을 위해 냈다) 다음 코드를 포함 ShardingMiddleware라는 OwinMiddleware 클래스를 가지고있다.

그러나 통합 테스트는 효과적으로 단일 스레드입니다. 이를 실제 API가 여러 개의 동시 호출로 치고있는 QA 환경에이를 배치하면 배 모양이 시작됩니다. Ferinstance :

System.ObjectDisposedException : 폐기 된 개체에 액세스 할 수 없습니다. 이 오류의 일반적인 원인은 종속성 주입으로 해결 된 컨텍스트를 삭제 한 다음 나중에 응용 프로그램의 다른 위치에서 동일한 컨텍스트 인스턴스를 사용하려고하는 것입니다. 컨텍스트에서 Dispose()를 호출하거나 using 문에서 컨텍스트를 래핑하는 중일 수 있습니다. 의존성 주입을 사용하는 경우, 의존성 주입 컨테이너가 문맥 인스턴스를 처리하도록해야합니다.

또는 StructMap에 유효한 MyDbContext의 유효한 인스턴스가 없다는 것을 나타내는 다른 예외.

여러 스레드가 어떻게 든 서로의 구성을 어지럽히는 것 같지만 나에게있어 중첩 된 컨테이너를 사용하여 각 API에 대한 데이터베이스 컨텍스트를 저장하는 방법을 이해하는 방법은 이해할 수 없습니다. 요구.

어떤 아이디어가 잘못되었을 수 있습니까?

업데이트 : 또한 인터페이스에 내 Db 컨텍스트를 추상화하려고 시도했습니다. 진정한 차이는 없었습니다. 오류가 계속 발생합니다.

'System.InvalidOperationException :'SomeController '형식의 컨트롤러를 만들려고 할 때 오류가 발생했습니다. 컨트롤러에 매개 변수없는 public 생성자가 있는지 확인하십시오. ---> StructureMap.StructureMapConfigurationException : 없음 기본 인스턴스가 등록되지 않고 자동으로 'MyNamespace.IMyDbContext'

업데이트 2 유형 결정되지 않을 수 있습니다 내가 문제를 해결하지만, 현상금은 여전히 ​​열려 있습니다. 내 대답은 아래를 참조하십시오.

+0

당신의'DbContext'는 [Captive Dependency] (http://blog.ploeh.dk/2014/06/02/captive-dependency/)로 살아있을 수 있습니다. 이 의존성의 소비자가'DbContext'보다 수명이 길지 않거나 직접적으로 소비자에게 DbContext를 주입하는 것을 더 잘 방지해야합니다. 'DbContext'는 런타임 데이터이고 런타임 데이터는 구성 요소에 주입되어서는 안됩니다 (https://www.cuttingedge.it/blogs/steven/pivot/entry.php?id=99). 대신 추상화 뒤에서 DbContext를 숨 깁니다. – Steven

답변

2

음 ... 문제는 해결되었지만 이해할 수 없습니다. 이유가으로 변경되었습니다.

내가 처음 게시 한 것과 약간 다른 점이 있는데, 세부 사항은 중요하지 않다고 생각하고 질문에서주의를 산만하게 만들었 기 때문에 생략했습니다.내 컨테이너는 사실 지역적으로 정의되지 않았습니다. 오히려 내 미들웨어의 보호 특성이었다 (그것은 통합 테스트 목적으로 상속 된 것) :

protected IContainer Container { get; private set; } 

그런 다음 Invoke() 방법 내부 초기화 호출이 있었다 :

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

방법을 통해 로깅 문을 사용하여, 다음 코드로 넘어갔습니다 (로깅이 추가 된 상태에서 질문에 언급 된 바와 같이).

_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); 

그리고 놀랍게도 로그 나온 :

1 개 호선 문맥 = 56852305, 컨테이너 = 48376271

1 호선 문맥 = 88275661, 컨테이너 = 85736099

2 호선 문맥 = 56852305, 컨테이너 = 85736099

라인 2 컨텍스트 = 88275661, 컨테이너 = 85736099

놀라운! 내 미들웨어의 Container 속성이 마법처럼 바뀌 었습니다! 이것은 private set으로 정의되어 있고 어쨌든 안전한 것으로 확인되었지만 MyDbContext.ForShard() 코드를 확인한 결과 Container에 대한 참조를 엉망으로 만들 수있는 것이 발견되지 않았습니다.

그래서 해결책은 무엇입니까? 초기화 직후에 로컬 container 변수를 선언하고이를 대신 사용했습니다.

지금은 작동하지만 왜 이것이 어떻게 달라질 수 있는지 이해할 수 없습니다.

바운티는 이것을 설명 할 수있는 사람에게갑니다.

+0

이유는 'HttpContext 또는 ThreadLocal 범위 지정을 통해 중첩 컨테이너를 사용해야하는 이유는 무엇입니까?' 여기 http://structuremap.github.io/the-container/nested-containers/ – ATechieThought

+0

@ATechieThought 더 설명해 주시겠습니까? 나는 항상 OwinContext를 사용하여 HttpContext 또는 ThreadLocal 범위가 아닌 컨테이너를 가져 왔습니다. –

+0

내 잘못입니다. 나는 그것을 놓쳤다. 하지만 내 생각은 어떻게 든 httpcontext는 컨텍스트 값을보고 참여했습니다. 이제 httpcontext가 그림에서 벗어났습니다. MyDbContext가 관련되어있어 그것이 싱글 톤으로 설정되어야 할 가능성이 있습니까? 죄송합니다. 문제의 추론을 돕는 것이 아닙니다. – ATechieThought

2

이 다시 작성해야합니다

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); 
} 

원인 using는 사용의 끝에서 당신의 dbcontext을 폐기하십시오.

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

을이 대신 dbcontext 인스턴스의 FUNC 주입 :

대신 공장을 등록해야합니다. 내가 무엇을보고 로그에서

+0

인스턴스 대신 Func를 삽입하면 MyDbContext가 삽입 된 모든 클래스에서 새 Db 연결이 열리게됩니까? –

+0

에 따라 다릅니다. 기본적으로 - 예. 이게 문제가 되나요? –

+0

문제인지 확실하지 않습니다. 현재 생각하고있는 나의 방식은 하나의 작업 단위에 대해 단일 db 연결로 충분하다는 것입니다. 더 많은 연결이있는 경우 충돌을 일으킬 수 있는지 확실하지 않습니다. 또는 너무 많은 연결을 열려고 DB에 과도한 부담을 줄 수있는 경우 ... 걱정할 필요가 있습니다. 기본적으로 UOW 당 하나의 연결을 원하면 인스턴스를 전달하고 저장소 당 하나의 연결을 원하면 Func을 사용합니다. 감사! –

0

는 두 번째 요청/스레드가 컨테이너 그래서 첫 번째의 정중 데이터베이스 컨텍스트가 동일한 연결을 사용하는 무시한다는 것입니다 :

Line 2 Context=56852305, Container=85736099 

이어야를

Line 2 Context=56852305, Container=48376271 

내가 잘못 이해하고 있으므로 해결하지 못했습니다.System.ObjectDisposedException 오류는 당신이 당신의 DB 컨텍스트의 인스턴스를 만드는 데 사용하는 using 절에서이다 그것의 Next 대표와 context 이 배치되어 있기 때문이다. 또한

Container = context.GetNestedContainer(); 

어쩌면 당신이 마음

Container = container.GetNestedContainer(); 

에 한 줄을 이해하지 않았다? 나는 StructureMap에 익숙하지 오전하지만 코드가해야이 배치 될 때 컨테이너가 닫히고은 DB 연결을 처분 가정이

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

await Next.Invoke(context); 

처럼 보이는 생각합니다.