หมายเหตุ: คำตอบนี้พูดคุยเกี่ยวกับองค์กร Framework ของDbContext
แต่ก็เป็นที่ใช้บังคับกับการเรียงลำดับของหน่วยงานในการดำเนินการเช่นใด LINQ กับ SQL ของDataContext
และ ISession
NHibernate
เริ่มต้นด้วยการสะท้อนเอียน: การมีDbContext
แอพเดียวสำหรับแอปพลิเคชั่นทั้งหมดเป็นไอเดียที่ไม่ดี สถานการณ์เดียวที่เหมาะสมนี้คือเมื่อคุณมีแอปพลิเคชันแบบเธรดเดียวและฐานข้อมูลที่ใช้โดยอินสแตนซ์ของแอปพลิเคชันเดียว แต่เพียงผู้เดียว DbContext
ไม่ด้ายปลอดภัยและและตั้งแต่DbContext
ข้อมูลแคชจะได้รับเก่าสวยเร็ว ๆ นี้ สิ่งนี้จะช่วยให้คุณได้รับปัญหาทุกประเภทเมื่อผู้ใช้ / แอปพลิเคชั่นหลายตัวทำงานบนฐานข้อมูลนั้นพร้อมกัน (ซึ่งเป็นเรื่องธรรมดามาก) แต่ฉันคาดหวังว่าคุณรู้แล้วและต้องการรู้ว่าทำไมไม่เพียงแค่ฉีดอินสแตนซ์ใหม่ (เช่นมีไลฟ์สไตล์ชั่วคราว) ของDbContext
ผู้ที่ต้องการมัน (สำหรับข้อมูลเพิ่มเติมเกี่ยวกับสาเหตุที่คำสั่งเดียวDbContext
หรือแม้กระทั่งบริบทต่อเธรดไม่ดีให้อ่านคำตอบนี้ )
ให้ฉันเริ่มต้นด้วยการบอกว่าการลงทะเบียน a DbContext
ชั่วคราวสามารถทำงานได้ แต่โดยทั่วไปคุณต้องการมีอินสแตนซ์เดียวของหน่วยงานดังกล่าวภายในขอบเขตที่แน่นอน ในเว็บแอปพลิเคชันมันเป็นประโยชน์ในการกำหนดขอบเขตดังกล่าวในขอบเขตของคำขอเว็บ ดังนั้นการดำเนินชีวิตตามคำขอของเว็บ สิ่งนี้ช่วยให้คุณปล่อยให้วัตถุทั้งชุดทำงานภายในบริบทเดียวกัน กล่าวอีกนัยหนึ่งพวกเขาดำเนินการภายในธุรกรรมทางธุรกิจเดียวกัน
หากคุณไม่มีเป้าหมายที่จะให้ชุดปฏิบัติการดำเนินไปในบริบทเดียวกันในกรณีนั้นวิถีชีวิตแบบชั่วคราวนั้นดี แต่มีบางสิ่งให้ดู:
- เนื่องจากทุกอ็อบเจ็กต์ได้รับอินสแตนซ์ของตนเองทุกคลาสที่เปลี่ยนแปลงสถานะของระบบจำเป็นต้องเรียกใช้
_context.SaveChanges()
(มิฉะนั้นการเปลี่ยนแปลงจะสูญหายไป) นี้สามารถซับซ้อนรหัสของคุณและเพิ่มความรับผิดชอบที่สองรหัส (ความรับผิดชอบในการควบคุมบริบท) และเป็นการละเมิดที่หลักการ Single รับผิดชอบ
- คุณต้องตรวจสอบให้แน่ใจว่าเอนทิตี [ที่โหลดและบันทึกโดย
DbContext
] ไม่เคยออกจากขอบเขตของคลาสดังกล่าวเนื่องจากไม่สามารถใช้ในอินสแตนซ์บริบทของคลาสอื่นได้ สิ่งนี้อาจทำให้รหัสของคุณซับซ้อนอย่างมากเนื่องจากเมื่อคุณต้องการเอนทิตีเหล่านั้นคุณต้องโหลดอีกครั้งด้วยรหัสซึ่งอาจทำให้เกิดปัญหาประสิทธิภาพการทำงาน
- เนื่องจาก
DbContext
ใช้งานIDisposable
คุณอาจต้องการกำจัดอินสแตนซ์ที่สร้างขึ้นทั้งหมด หากคุณต้องการทำสิ่งนี้โดยทั่วไปคุณมีสองตัวเลือก คุณต้องกำจัดพวกมันด้วยวิธีเดียวกันทันทีหลังจากการโทรcontext.SaveChanges()
แต่ในกรณีนั้นตรรกะทางธุรกิจจะเป็นเจ้าของวัตถุที่มันถูกส่งผ่านจากภายนอก ตัวเลือกที่สองคือการกำจัดอินสแตนซ์ที่สร้างขึ้นทั้งหมดบนขอบเขตของการร้องขอ Http แต่ในกรณีนั้นคุณยังคงต้องการการกำหนดขอบเขตบางอย่างเพื่อให้คอนเทนเนอร์ทราบเมื่ออินสแตนซ์เหล่านั้นจำเป็นต้องถูกกำจัด
อีกทางเลือกหนึ่งคือไม่ต้องฉีดยาDbContext
เลย แต่คุณฉีดDbContextFactory
ที่สามารถสร้างตัวอย่างใหม่ (ฉันเคยใช้วิธีการนี้ในอดีต) ด้วยวิธีนี้ตรรกะทางธุรกิจจะควบคุมบริบทอย่างชัดเจน หากอาจมีลักษณะเช่นนี้:
public void SomeOperation()
{
using (var context = this.contextFactory.CreateNew())
{
var entities = this.otherDependency.Operate(
context, "some value");
context.Entities.InsertOnSubmit(entities);
context.SaveChanges();
}
}
ด้านบวกของสิ่งนี้คือคุณจัดการชีวิตของDbContext
อย่างชัดเจนและง่ายต่อการตั้งค่านี้ DbContext
นอกจากนี้ยังช่วยให้คุณใช้บริบทเดียวในขอบเขตบางอย่างที่มีข้อได้เปรียบที่ชัดเจนเช่นใช้รหัสในการทำธุรกรรมทางธุรกิจเดียวและความสามารถในการผ่านรอบหน่วยงานเนื่องจากพวกเขามาจากที่เดียวกัน
ข้อเสียคือคุณจะต้องผ่านDbContext
จากวิธีการวิธี (ซึ่งเรียกว่าวิธีการฉีด) โปรดทราบว่าในแง่นี้โซลูชันนี้เหมือนกับวิธี 'scoped' แต่ขณะนี้ขอบเขตถูกควบคุมในโค้ดแอปพลิเคชันเอง (และอาจทำซ้ำหลายครั้ง) มันเป็นแอพพลิเคชั่นที่รับผิดชอบในการสร้างและกำจัดหน่วยงาน เนื่องจากDbContext
สร้างขึ้นหลังจากสร้างกราฟการพึ่งพา Constructor Injection จึงไม่อยู่ในรูปภาพและคุณจำเป็นต้องเลื่อนไปที่ Method Injection เมื่อคุณต้องการส่งผ่านบริบทจากชั้นหนึ่งไปอีกชั้นหนึ่ง
การฉีดเมธอดไม่ได้เลวร้ายนัก แต่เมื่อตรรกะทางธุรกิจมีความซับซ้อนมากขึ้นและการมีส่วนร่วมของคลาสมากขึ้นคุณจะต้องผ่านมันจากวิธีหนึ่งไปยังอีกวิธีหนึ่งและคลาสหนึ่งไปอีกชั้นหนึ่ง ในอดีต) สำหรับแอปพลิเคชันที่เรียบง่ายวิธีการนี้จะทำได้ดีแม้ว่า
เนื่องจากข้อเสียวิธีการจากโรงงานนี้มีไว้สำหรับระบบที่ใหญ่กว่าวิธีอื่นอาจมีประโยชน์และนั่นคือวิธีที่คุณปล่อยให้คอนเทนเนอร์หรือรหัสโครงสร้างพื้นฐาน / รูตองค์ประกอบการจัดการหน่วยงาน นี่คือรูปแบบที่คำถามของคุณเกี่ยวกับ
โดยการให้คอนเทนเนอร์และ / หรือโครงสร้างพื้นฐานจัดการสิ่งนี้รหัสแอปพลิเคชันของคุณจะไม่ปนเปื้อนโดยต้องสร้าง (ทางเลือก) กระทำและกำจัดอินสแตนซ์ UoW ซึ่งทำให้ตรรกะทางธุรกิจง่ายและสะอาด (เพียงความรับผิดชอบเดียว) มีปัญหาบางอย่างกับวิธีนี้ ตัวอย่างเช่นคุณได้กระทำและกำจัดอินสแตนซ์แล้วหรือยัง
การทิ้งหน่วยของงานสามารถทำได้ในตอนท้ายของคำขอเว็บ อย่างไรก็ตามหลายคนสันนิษฐานว่าไม่ถูกต้องว่านี่เป็นสถานที่ในการส่งมอบหน่วยงาน อย่างไรก็ตาม ณ จุดนั้นในแอปพลิเคชันคุณก็ไม่สามารถระบุได้ว่าหน่วยงานควรได้รับมอบหมายจริง ๆ เช่นถ้ารหัสเลเยอร์ธุรกิจมีข้อยกเว้นที่ทำให้ callstack สูงขึ้นคุณจะไม่ต้องการคอมมิท
โซลูชันที่แท้จริงคือการจัดการขอบเขตบางประเภทอีกครั้ง แต่คราวนี้จะอยู่ภายใน Composition Root การสรุปตรรกะทางธุรกิจทั้งหมดที่อยู่เบื้องหลังรูปแบบคำสั่ง / ตัวจัดการคุณจะสามารถเขียนมัณฑนากรที่สามารถล้อมรอบตัวจัดการคำสั่งแต่ละตัวที่อนุญาตให้ทำเช่นนี้ได้ ตัวอย่าง:
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();
}
}
สิ่งนี้ทำให้มั่นใจได้ว่าคุณจะต้องเขียนโค้ดโครงสร้างพื้นฐานนี้เพียงครั้งเดียว คอนเทนเนอร์ DI ที่เป็นของแข็งใด ๆ ให้คุณกำหนดค่าตัวประดับเช่นนี้ให้พันรอบICommandHandler<T>
การใช้งานทั้งหมดในลักษณะที่สอดคล้องกัน