2016-08-04 15 views
6

Tôi đã có một ứng dụng API sử dụng nhiều phân đoạn cơ sở dữ liệu, với StructureMap để tiêm phụ thuộc. Một trong các tiêu đề bắt buộc trong mỗi lệnh gọi API là ShardKey, cho tôi biết cơ sở dữ liệu nào cuộc gọi này đang giải quyết. Để thực hiện điều này, tôi có một lớp OwinMiddleware gọi ShardingMiddleware, trong đó có chứa đoạn mã sau (snipped cho rõ ràng):Xung đột chéo chuỗi trong 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); 
} 

này làm việc tuyệt vời trong môi trường thử nghiệm của tôi và vượt qua một loạt các xét nghiệm hội nhập.

Nhưng kiểm tra tích hợp có hiệu quả đơn luồng. Khi tôi triển khai điều này vào môi trường QA, nơi một ứng dụng thực đang truy cập API của tôi với nhiều cuộc gọi đồng thời, mọi thứ bắt đầu đi hình quả lê. Ferinstance:

System.ObjectDisposedException: Không thể truy cập đối tượng được xử lý. Một nguyên nhân phổ biến của lỗi này là xử lý một ngữ cảnh đã được giải quyết từ việc tiêm phụ thuộc và sau đó cố gắng sử dụng cùng một cá thể ngữ cảnh ở nơi khác trong ứng dụng của bạn. Điều này có thể xảy ra là bạn đang gọi Dispose() trên ngữ cảnh, hoặc gói ngữ cảnh trong một câu lệnh sử dụng. Nếu bạn đang sử dụng tiêm phụ thuộc, bạn nên để thùng chứa phụ thuộc tiêm xử lý các trường hợp bối cảnh.

Hoặc các trường hợp ngoại lệ khác cho thấy rằng Sơ đồ cấu trúc không có phiên bản hợp lệ MyDbContext khả dụng.

Với tôi có vẻ như nhiều chủ đề bằng cách nào đó làm rối loạn cấu hình của nhau, nhưng với cuộc sống của tôi, tôi không thể hiểu được, nhìn như tôi đang sử dụng một thùng chứa lồng nhau để lưu trữ bối cảnh cơ sở dữ liệu cho mỗi API gọi điện.

Bất kỳ ý tưởng nào có thể xảy ra ở đây?

Cập nhật: Tôi cũng đã thử trừu tượng ngữ cảnh Db của mình thành giao diện. Không có sự khác biệt thực sự; Tôi vẫn gặp lỗi

System.InvalidOperationException: Đã xảy ra lỗi khi cố gắng tạo bộ điều khiển kiểu 'SomeController'. Đảm bảo rằng bộ điều khiển có một hàm tạo công khai không tham số. ---> StructureMap.StructureMapConfigurationException: Không Instance mặc định đã được đăng ký và không thể tự động xác định cho loại 'MyNamespace.IMyDbContext'

Cập nhật 2: tôi đã giải quyết được vấn đề, nhưng tiền thưởng vẫn còn mở. Xin vui lòng xem câu trả lời của tôi dưới đây.

+0

'DbContext' của bạn có thể được giữ nguyên dưới dạng [Phụ thuộc Captive] (http://blog.ploeh.dk/2014/06/02/captive-dependency/). Hãy chắc chắn rằng người tiêu dùng của sự phụ thuộc này không có một đời lâu hơn của 'DbContext' hoặc-tốt hơn-ngăn chặn tiêm DbContext vào người tiêu dùng trực tiếp. Một 'DbContext' là dữ liệu thời gian chạy và dữ liệu thời gian chạy [không nên được đưa vào các thành phần] (https://www.cuttingedge.it/blogs/steven/pivot/entry.php?id=99). Thay vào đó hãy ẩn DbContext đằng sau một trừu tượng. – Steven

Trả lời

2

Vâng ... Tôi đã giải quyết được sự cố, nhưng tôi không hiểu lý do tại sao điều này tạo nên sự khác biệt.

Nó tóm tắt một số khác biệt tinh tế so với những gì tôi đăng ban đầu, mà tôi đã bỏ qua bởi vì tôi nghĩ rằng các chi tiết không quan trọng và sẽ bị xao lãng khỏi câu hỏi.Vùng chứa của tôi không, trên thực tế, được xác định cục bộ; thay vì đó là một tài sản của middleware tôi được bảo vệ (nó thừa hưởng cho mục đích thử nghiệm tích hợp):

protected IContainer Container { get; private set; } 

Sau đó, đã có một cuộc gọi khởi tạo bên trong phương pháp Invoke():

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

Sử dụng báo cáo khai thác gỗ trên khắp phương pháp, Tôi đã nhận được mã sau đây (như đã đề cập trong câu hỏi, với việc đăng nhập được thêm):

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

Và đáng kinh ngạc, đây là những gì ra khỏi các bản ghi:

Line 1 Context = 56.852.305, container = 48376271

Line 1 Context = 88.275.661, container = 85736099

Dòng 2 Context = 56.852.305, container = 85736099

Dòng 2 Ngữ cảnh = 88275661, Vùng chứa = 85736099

Tuyệt vời! Các tài sản Container của middleware của tôi đã thay thế kỳ diệu! Điều này, mặc dù thực tế là nó được định nghĩa với một private set, và dù sao, chỉ để được an toàn, tôi đã kiểm tra thông qua mã cho MyDbContext.ForShard() và không tìm thấy gì có thể đã làm sai lệch tham chiếu cho Container.

Vậy giải pháp là gì? Tôi tuyên bố một biến địa phương container chỉ sau khi khởi tạo, và sử dụng thay vào đó.

Nó hoạt động ngay bây giờ, nhưng tôi không hiểu tại sao hoặc làm thế nào điều này có thể tạo ra sự khác biệt.

Bounty đến người có thể giải thích điều này.

+0

Tôi nghĩ, các lý do tương tự như được mô tả trong 'Tại sao Thùng chứa Nested trên HttpContext hoặc Phạm vi ThreadLocal?' tại đây http://structuremap.github.io/the-container/nested-containers/ – ATechieThought

+0

@ATechieBạn có thể giải thích thêm điều đó không? Tôi đã luôn luôn sử dụng OwinContext để có được các container, không HttpContext hoặc ThreadLocal phạm vi. –

+0

xấu của tôi. Tôi nhớ nó. nhưng suy nghĩ của tôi là bằng cách nào đó httpcontext đã được tham gia bằng cách xem xét các giá trị ngữ cảnh. Bây giờ, httpcontext là ra khỏi hình ảnh, là có bất kỳ cơ hội mà MyDbContext có liên quan và nó nên được thiết lập như singleton? Xin lỗi, nếu, tôi không giúp đỡ bất kỳ cách nào đối với lý do của vấn đề. – ATechieThought

2

Bạn nên viết lại này:

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

nguyên nhân using đoạt dbcontext của bạn vào cuối của việc sử dụng.

Bạn nên đăng ký nhà máy thay vì:

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

và tiêm này Func thay vì dbcontext dụ.

+0

Nếu bạn tiêm một Func thay vì một thể hiện, nó sẽ mở một kết nối Db mới trong mỗi lớp mà MyDbContext được tiêm? –

+0

nó phụ thuộc. theo mặc định - có. Đó có phải là vấn đề? –

+0

Không chắc chắn nếu nó là một vấn đề. Cách suy nghĩ hiện tại của tôi là một kết nối db đơn phải đủ cho một đơn vị công việc. Tôi không chắc chắn nếu nó có thể gây ra xung đột nếu có nhiều kết nối hơn - hoặc nếu nó có thể đặt căng thẳng quá mức trên db để có quá nhiều kết nối mở ... nhưng đó là để tôi lo lắng. Về cơ bản, nếu tôi muốn một kết nối cho mỗi UOW, tôi sẽ vượt qua thể hiện, và nếu tôi muốn có một kết nối cho mỗi kho lưu trữ, tôi sẽ sử dụng Func. Cảm ơn! –

0

Từ các bản ghi những gì tôi thấy là yêu cầu thứ hai/thread ghi đè container và trân trọng bối cảnh cơ sở dữ liệu của người đầu tiên vì vậy cả hai sử dụng cùng một kết nối:

Line 2 Context=56852305, Container=85736099 

nên

Line 2 Context=56852305, Container=48376271 

hoặc tôi nhận được nó sai, vì vậy tôi không nghĩ rằng bạn giải quyết nó.Các lỗi System.ObjectDisposedException là từ using khoản mà bạn sử dụng để tạo ra thể hiện của bối cảnh db của bạn và vì nó Next đại biểu và context được xử lý. Tôi cũng không hiểu dòng

Container = context.GetNestedContainer(); 

có lẽ bạn đã có trong tâm trí

Container = container.GetNestedContainer(); 

? Tôi không quen thuộc với StructureMap nhưng tôi nghĩ mã sẽ trông như thế này

giả định rằng thùng chứa đóng và hủy kết nối db khi nó được xử lý.