LƯU Ý: Câu trả lời này nói về Khung thực thể DbContext
, nhưng nó có thể áp dụng cho bất kỳ loại Đơn vị thực hiện Công việc nào, chẳng hạn như LINQ to SQL DataContext
và NHibernate ISession
.
Hãy bắt đầu bằng cách lặp lại Ian: Có một cái duy nhất DbContext
cho toàn bộ ứng dụng là một ý tưởng tồi. Tình huống duy nhất có ý nghĩa này là khi bạn có một ứng dụng đơn luồng và cơ sở dữ liệu chỉ được sử dụng bởi cá thể ứng dụng đó. Nó DbContext
không an toàn cho luồng và vì DbContext
dữ liệu lưu trữ, nó sẽ bị cũ khá sớm. Điều này sẽ khiến bạn gặp nhiều rắc rối khi nhiều người dùng / ứng dụng hoạt động trên cơ sở dữ liệu đó cùng một lúc (điều này rất phổ biến). Nhưng tôi hy vọng bạn đã biết điều đó và chỉ muốn biết tại sao không tiêm một ví dụ mới (tức là với lối sống thoáng qua) DbContext
vào bất cứ ai cần nó. (để biết thêm thông tin về lý do tại sao một DbContext
hoặc ngay cả trên ngữ cảnh trên mỗi luồng - là xấu, hãy đọc câu trả lời này ).
Hãy để tôi bắt đầu bằng cách nói rằng đăng ký DbContext
tạm thời có thể hoạt động, nhưng thông thường bạn muốn có một phiên bản duy nhất của một đơn vị công việc như vậy trong một phạm vi nhất định. Trong một ứng dụng web, việc xác định phạm vi như vậy trên các ranh giới của một yêu cầu web có thể là thực tế; do đó, một lối sống trên mỗi yêu cầu web. Điều này cho phép bạn để cho cả một tập hợp các đối tượng hoạt động trong cùng một bối cảnh. Nói cách khác, họ hoạt động trong cùng một giao dịch kinh doanh.
Nếu bạn không có mục tiêu có một tập hợp các hoạt động trong cùng một bối cảnh, thì trong trường hợp đó, lối sống thoáng qua là tốt, nhưng có một vài điều cần xem:
- Vì mỗi đối tượng có thể hiện riêng của nó, mỗi lớp thay đổi trạng thái của hệ thống, cần phải gọi
_context.SaveChanges()
(nếu không các thay đổi sẽ bị mất). Điều này có thể làm phức tạp mã của bạn và thêm trách nhiệm thứ hai vào mã (trách nhiệm kiểm soát bối cảnh) và vi phạm Nguyên tắc Trách nhiệm duy nhất .
- Bạn cần đảm bảo rằng các thực thể [được tải và lưu bởi a
DbContext
] không bao giờ rời khỏi phạm vi của một lớp như vậy, bởi vì chúng không thể được sử dụng trong trường hợp ngữ cảnh của một lớp khác. Điều này có thể làm phức tạp mã của bạn rất nhiều, bởi vì khi bạn cần những thực thể đó, bạn cần tải lại chúng bằng id, điều này cũng có thể gây ra vấn đề về hiệu suất.
- Kể từ khi
DbContext
thực hiện IDisposable
, có lẽ bạn vẫn muốn Loại bỏ tất cả các phiên bản đã tạo. Nếu bạn muốn làm điều này, về cơ bản bạn có hai lựa chọn. Bạn cần loại bỏ chúng theo cùng một phương thức ngay sau khi gọi context.SaveChanges()
, nhưng trong trường hợp đó logic nghiệp vụ sẽ sở hữu một đối tượng mà nó được truyền từ bên ngoài. Tùy chọn thứ hai là Loại bỏ tất cả các phiên bản được tạo trên ranh giới của Yêu cầu http, nhưng trong trường hợp đó, bạn vẫn cần một số phạm vi để cho container biết khi nào các trường hợp đó cần được xử lý.
Một lựa chọn khác là không tiêm một DbContext
chút nào. Thay vào đó, bạn tiêm một DbContextFactory
cái có thể tạo một thể hiện mới (tôi đã từng sử dụng phương pháp này trong quá khứ). Bằng cách này logic kinh doanh kiểm soát bối cảnh rõ ràng. Nếu có thể trông như thế này:
public void SomeOperation()
{
using (var context = this.contextFactory.CreateNew())
{
var entities = this.otherDependency.Operate(
context, "some value");
context.Entities.InsertOnSubmit(entities);
context.SaveChanges();
}
}
Điểm cộng của việc này là bạn quản lý cuộc sống DbContext
một cách rõ ràng và thật dễ dàng để thiết lập điều này. Nó cũng cho phép bạn sử dụng một bối cảnh duy nhất trong một phạm vi nhất định, có lợi thế rõ ràng, chẳng hạn như chạy mã trong một giao dịch kinh doanh duy nhất và có thể vượt qua các thực thể, vì chúng có nguồn gốc từ cùng một DbContext
.
Nhược điểm là bạn sẽ phải chuyển DbContext
từ phương thức này sang phương thức khác (được gọi là Phương pháp tiêm). Lưu ý rằng theo một nghĩa nào đó, giải pháp này giống như phương pháp 'phạm vi', nhưng bây giờ phạm vi được kiểm soát trong chính mã ứng dụng (và có thể được lặp lại nhiều lần). Đây là ứng dụng chịu trách nhiệm tạo và xử lý đơn vị công việc. Vì DbContext
biểu đồ được tạo sau khi biểu đồ phụ thuộc được xây dựng, Con constructor Injection không có hình ảnh và bạn cần trì hoãn Phương thức tiêm khi bạn cần chuyển ngữ cảnh từ lớp này sang lớp khác.
Phương thức Tiêm không tệ, nhưng khi logic nghiệp vụ trở nên phức tạp hơn và có nhiều lớp tham gia hơn, bạn sẽ phải chuyển nó từ phương thức này sang phương thức và lớp này sang lớp khác, điều này có thể làm phức tạp mã (tôi đã thấy cái này trong quá khứ). Đối với một ứng dụng đơn giản, cách tiếp cận này sẽ làm tốt mặc dù.
Do những nhược điểm, cách tiếp cận nhà máy này dành cho các hệ thống lớn hơn, cách tiếp cận khác có thể hữu ích và đó là cách bạn để bộ chứa hoặc mã cơ sở hạ tầng / Thành phần gốc quản lý đơn vị công việc. Đây là phong cách mà câu hỏi của bạn là về.
Bằng cách để bộ chứa và / hoặc cơ sở hạ tầng xử lý việc này, mã ứng dụng của bạn không bị ô nhiễm bằng cách phải tạo, (tùy chọn) cam kết và Loại bỏ một cá thể UoW, giữ cho logic nghiệp vụ đơn giản và sạch sẽ (chỉ là một Trách nhiệm duy nhất). Có một số khó khăn với phương pháp này. Chẳng hạn, bạn có Cam kết và vứt bỏ ví dụ không?
Việc xử lý một đơn vị công việc có thể được thực hiện ở cuối yêu cầu web. Nhiều người tuy nhiên, sai giả định rằng điều này cũng là nơi để Commit đơn vị làm việc. Tuy nhiên, tại thời điểm đó trong ứng dụng, bạn chỉ cần xác định chắc chắn rằng đơn vị công việc thực sự phải được cam kết. ví dụ: Nếu mã lớp doanh nghiệp đã ném một ngoại lệ được bắt lên cao hơn trong bảng gọi, bạn chắc chắn không muốn Cam kết.
Giải pháp thực sự là một lần nữa để quản lý rõ ràng một số loại phạm vi, nhưng lần này thực hiện nó bên trong Root Root. Tóm tắt tất cả logic nghiệp vụ đằng sau mẫu lệnh / trình xử lý , bạn sẽ có thể viết một trình trang trí có thể được bao quanh mỗi trình xử lý lệnh cho phép thực hiện điều này. Thí dụ:
class TransactionalCommandHandlerDecorator<TCommand>
: ICommandHandler<TCommand>
{
readonly DbContext context;
readonly ICommandHandler<TCommand> decorated;
public TransactionCommandHandlerDecorator(
DbContext context,
ICommandHandler<TCommand> decorated)
{
this.context = context;
this.decorated = decorated;
}
public void Handle(TCommand command)
{
this.decorated.Handle(command);
context.SaveChanges();
}
}
Điều này đảm bảo rằng bạn chỉ cần viết mã cơ sở hạ tầng này một lần. Bất kỳ thùng chứa DI rắn nào cũng cho phép bạn định cấu hình một bộ trang trí như vậy được bao bọc xung quanh tất cả các ICommandHandler<T>
cài đặt một cách nhất quán.