NOT: Bu yanıt Entity Framework'lerden bahseder DbContext
, ancak SQL'lere LINQ DataContext
ve NHibernate'ler gibi her türlü İş Birimi uygulaması için geçerlidir ISession
.
Ian'ı yankılayarak başlayalım: DbContext
Tüm uygulama için tek bir sahip olmak Kötü Bir Fikirdir. Bunun mantıklı olduğu tek durum, tek iş parçacıklı bir uygulamanız ve yalnızca o tek uygulama örneği tarafından kullanılan bir veritabanınız olmasıdır. İş DbContext
parçacığı için güvenli değildir ve DbContext
verileri önbelleğe aldığından, kısa bir süre içinde eski haline gelir. Bu, birden fazla kullanıcı / uygulama aynı anda bu veritabanında çalıştığında (bu çok yaygın bir durumdur) sizi her türlü sıkıntıya sokacaktır. Ama bunu zaten bilmenizi bekliyorum ve neden sadece yeni bir örneğini (yani geçici bir yaşam tarzı ile) DbContext
ihtiyaç duyan herkese enjekte etmeyeceğinizi bilmek istiyorum . (tek bir DbContext
-veya iş parçacığı başına bağlamda bile - neden kötü olduğu hakkında daha fazla bilgi için bu yanıtı okuyun ).
DbContext
Geçici olarak kaydolmanın işe yarayabileceğini söyleyerek başlayayım, ancak tipik olarak belirli bir kapsamda böyle bir iş biriminin tek bir örneğine sahip olmak istiyorsunuz. Bir web uygulamasında, bir web isteğinin sınırları üzerine böyle bir kapsam tanımlamak pratik olabilir; böylece Web Başına İstek yaşam tarzı. Bu, bir dizi nesnenin aynı bağlam içinde çalışmasına izin vermenizi sağlar. Başka bir deyişle, aynı ticari işlem dahilinde çalışırlar.
Aynı bağlamda bir dizi işlemin yürütülmesi hedefiniz yoksa, bu durumda geçici yaşam tarzı iyidir, ancak izlenmesi gereken birkaç şey vardır:
- Her nesne kendi örneğini aldığından, sistemin durumunu değiştiren her sınıfın çağırması gerekir
_context.SaveChanges()
(aksi takdirde değişiklikler kaybolur). Bu, kodunuzu karmaşıklaştırabilir ve koda ikinci bir sorumluluk ekler (bağlamı kontrol etme sorumluluğu) ve Tek Sorumluluk İlkesinin ihlalidir .
- [Tarafından yüklenen ve kaydedilen
DbContext
] varlıkların hiçbir zaman böyle bir sınıfın kapsamından ayrılmadığından emin olmanız gerekir , çünkü bunlar başka bir sınıfın bağlam örneğinde kullanılamaz. Bu, kodunuzu büyük ölçüde karmaşıklaştırabilir, çünkü bu varlıklara ihtiyacınız olduğunda, bunları kimlikle tekrar yüklemeniz gerekir; bu da performans sorunlarına neden olabilir.
- Yana
DbContext
uygular IDisposable
, muhtemelen hala tüm oluşturulan örneklerini imha etmek istiyorum. Bunu yapmak istiyorsanız, temel olarak iki seçeneğiniz vardır. Onları aradıktan hemen sonra aynı yöntemle atmanız gerekir context.SaveChanges()
, ancak bu durumda iş mantığı dışarıdan aktarılan bir nesnenin sahipliğini alır. İkinci seçenek, oluşturulan tüm örnekleri Http İsteği sınırına atmaktır, ancak bu durumda kabın, bu örneklerin ne zaman Atılması gerektiğine dair bilgi vermesi için bir çeşit kapsam belirlemeye ihtiyacınız vardır.
Başka bir seçenek de hiç enjekte etmemekDbContext
. Bunun yerine, DbContextFactory
yeni bir örnek oluşturabilecek bir tane enjekte edersiniz (geçmişte bu yaklaşımı kullanırdım). Bu şekilde iş mantığı bağlamı açık bir şekilde kontrol eder. Böyle görünebilirse:
public void SomeOperation()
{
using (var context = this.contextFactory.CreateNew())
{
var entities = this.otherDependency.Operate(
context, "some value");
context.Entities.InsertOnSubmit(entities);
context.SaveChanges();
}
}
Bunun artı tarafı, DbContext
açıkça yaşamı yönetmeniz ve bunu ayarlamanızın kolay olmasıdır. Ayrıca, belirli bir kapsamda, tek bir iş işleminde kod çalıştırma ve varlıkların aynı kaynaktan geldiği için geçiş yapma gibi belirgin avantajlara sahip tek bir bağlam kullanmanıza izin verir DbContext
.
Dezavantajı, DbContext
yöntemden yönteme (Yöntem Enjeksiyonu olarak adlandırılır) geçmeniz gerekecek olmasıdır . Bir anlamda bu çözümün 'kapsamlı' yaklaşımla aynı olduğunu, ancak şimdi kapsamın uygulama kodunun kendisinde kontrol edildiğini (ve muhtemelen birçok kez tekrarlandığını) unutmayın. İş birimini oluşturmak ve atmaktan sorumlu olan uygulamadır. Yana DbContext
bağımlılık grafiği yapımının ardından oluşturulan, Yapıcı Enjeksiyon resmin dışında olduğunu ve bir sınıftan diğerine bağlama geçmesi gerekiyor olduğunda Yöntem Enjeksiyon için ertelemek gerekir.
Yöntem Enjeksiyon o kadar da kötü değil, ancak iş mantığı daha karmaşık hale geldiğinde ve daha fazla sınıf dahil olduğunda, yöntemi çok yöntemle sınıftan sınıfa geçirmek zorunda kalacaksınız, bu da kodu çok karmaşık hale getirebilir (gördüm bu geçmişte). Basit bir uygulama için, bu yaklaşım iyi olur.
Dezavantajları nedeniyle, bu fabrika yaklaşımı daha büyük sistemler için vardır, başka bir yaklaşım yararlı olabilir ve bu, kabın veya altyapı kodunun / Kompozisyon Kökünün iş birimini yönetmesine izin verdiğiniz yaklaşımdır . Sorunuzun söz konusu olduğu stil bu.
Konteynerin ve / veya altyapının bunu halletmesine izin vererek, uygulama mantığınız iş mantığını basit ve temiz (yalnızca Tek Sorumluluk) tutan bir UoW örneği oluşturmak, (isteğe bağlı olarak) oluşturmak ve imha etmek zorunda kalmadan kirlenmez. Bu yaklaşımla ilgili bazı zorluklar vardır. Örneğin, vakayı taahhüt ettiniz mi ve imha ettiniz mi?
Bir iş biriminin imha edilmesi, web isteğinin sonunda yapılabilir. Bununla birlikte, birçok insan yanlış bir şekilde , bunun aynı zamanda iş birimini yürütmenin yeri olduğunu varsayar. Ancak, uygulamada bu noktada, iş biriminin gerçekten işlenmesi gerektiğinden emin olamazsınız. Örneğin, iş katmanı kodu çağrı kaydında daha fazla yakalanan bir istisna atarsa, kesinlikle İşlem yapmak istemezsiniz.
Gerçek çözüm yine bir tür kapsamı açıkça yönetmek, ancak bu sefer Kompozisyon Kökü içinde yapın. Komut / işleyici deseninin arkasındaki tüm iş mantığını soyutlayarak, bunu yapmanıza izin veren her komut işleyicisinin etrafına sarılabilen bir dekoratör yazabilirsiniz. Misal:
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();
}
}
Bu, bu altyapı kodunu yalnızca bir kez yazmanızı sağlar. Herhangi bir katı DI kabı, böyle bir dekoratörü, tüm ICommandHandler<T>
uygulamaların etrafına tutarlı bir şekilde sarılacak şekilde yapılandırmanıza izin verir .