หนึ่ง DbContext ต่อคำขอทางเว็บ…เพราะอะไร


398

ฉันอ่านบทความจำนวนมากที่อธิบายถึงวิธีการตั้งค่า Entity Framework DbContextเพื่อให้มีการสร้างและใช้เพียงหนึ่งรายการต่อการร้องขอ HTTP ของเว็บโดยใช้เฟรมเวิร์ก DI ต่างๆ

ทำไมนี่เป็นความคิดที่ดีตั้งแต่แรก? คุณได้รับประโยชน์อะไรบ้างจากการใช้วิธีการนี้ มีสถานการณ์บางอย่างที่จะเป็นความคิดที่ดีหรือไม่? มีสิ่งใดบ้างที่คุณสามารถทำได้โดยใช้เทคนิคนี้ที่คุณไม่สามารถทำได้เมื่อสร้างอินสแตนซ์DbContextของการเรียกเมธอดที่เก็บ?


9
Gueddari ในmehdi.me/ambient-dbcontext-in-ef6เรียกอินสแตนซ์ DbContext ตามวิธีที่เก็บเรียก antipattern ข้อความอ้างอิง: "ด้วยการทำเช่นนี้คุณจะเสียความสามารถทุกอย่างที่ Entity Framework มอบให้ผ่านทาง DbContext ซึ่งรวมถึงแคชระดับที่ 1 แผนที่ข้อมูลเฉพาะตัวหน่วยการทำงานและการติดตามการเปลี่ยนแปลงและความสามารถในการโหลดแบบขี้เกียจ ." บทความที่ยอดเยี่ยมพร้อมคำแนะนำที่ยอดเยี่ยมสำหรับการจัดการวงจรชีวิตของ DBContexts คุ้มค่าที่จะอ่านแน่นอน
Christoph

คำตอบ:


565

หมายเหตุ: คำตอบนี้พูดคุยเกี่ยวกับองค์กร Framework ของDbContextแต่ก็เป็นที่ใช้บังคับกับการเรียงลำดับของหน่วยงานในการดำเนินการเช่นใด LINQ กับ SQL ของDataContextและ ISessionNHibernate

เริ่มต้นด้วยการสะท้อนเอียน: การมี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>การใช้งานทั้งหมดในลักษณะที่สอดคล้องกัน


2
ว้าว - ขอบคุณสำหรับคำตอบอย่างละเอียด ถ้าฉันสามารถโหวตได้สองครั้งฉันก็ทำได้ ข้างต้นคุณพูดว่า "... ไม่มีความตั้งใจที่จะให้การดำเนินงานทั้งชุดดำเนินไปในบริบทเดียวกันในกรณีนั้นวิถีชีวิตแบบชั่วคราวนั้นไม่เป็นไร ... " "ชั่วคราว" หมายถึงอะไรโดยเฉพาะ
แอนดรู

14
@Andrew: 'Transient' เป็นแนวคิดการพึ่งพาการพึ่งพาซึ่งหมายความว่าหากมีการกำหนดค่าบริการให้เป็นแบบชั่วคราวอินสแตนซ์ใหม่ของบริการจะถูกสร้างขึ้นทุกครั้งที่มีการฉีดเข้าไปยังผู้ใช้บริการ
Steven

1
@ user981375: สำหรับการดำเนินการ CRUD คุณสามารถสร้างทั่วไปCreateCommand<TEnity>และทั่วไปCreateCommandHandler<TEntity> : ICommandHandler<CreateCommand<TEntity>>(และทำเช่นเดียวกันสำหรับการปรับปรุงและลบและมีGetByIdQuery<TEntity>แบบสอบถามเดียว) ถึงกระนั้นคุณควรถามตัวคุณเองว่ารุ่นนี้เป็นนามธรรมที่เป็นประโยชน์สำหรับการดำเนินงาน CRUD หรือไม่หรือเพียงแค่เพิ่มความซับซ้อนเท่านั้น ถึงกระนั้นคุณอาจได้รับประโยชน์จากความเป็นไปได้ในการเพิ่มข้อกังวลข้าม (ตัดผ่านตกแต่ง) โดยใช้โมเดลนี้ คุณจะต้องชั่งน้ำหนักข้อดีและข้อเสีย
Steven

3
+1 คุณจะเชื่อว่าฉันเขียนคำตอบทั้งหมดนี้ก่อนที่จะอ่านข้อความนี้จริงหรือ BTW IMO ฉันคิดว่ามันเป็นสิ่งสำคัญสำหรับคุณที่จะหารือเกี่ยวกับการกำจัดของ DbContext ในตอนท้าย (แม้ว่ามันจะยอดเยี่ยมที่คุณยังคงอยู่ผู้ไม่เชื่อเรื่องพระเจ้า)
Ruben Bartelink

1
แต่คุณไม่ผ่านบริบทการเรียนตกแต่ง, วิธีการเรียนตกแต่งสามารถทำงานร่วมกับบริบทเดียวกันกับที่ส่งผ่านไปยังTransactionCommandHandlerDecorator? ตัวอย่างเช่นถ้าคลาสที่ตกแต่งเป็นInsertCommandHandlerคลาสแล้วจะลงทะเบียนการแทรกอย่างไรกับบริบท (DbContext ใน EF)
Masoud

35

มีข้อเสนอแนะที่ขัดแย้งกันสองข้อโดย microsoft และหลายคนใช้ DbContexts ในลักษณะที่แตกต่างกันโดยสิ้นเชิง

  1. คำแนะนำอย่างหนึ่งคือ"กำจัด DbContexts ทันทีที่เป็นไปได้" เพราะการใช้ DbContext Alive ใช้ทรัพยากรที่มีค่าเช่นการเชื่อมต่อฐานข้อมูลเป็นต้น
  2. สถานะอื่น ๆ ที่หนึ่ง DbContext ตามคำขอนั้นได้รับการแนะนำอีกครั้ง

ความขัดแย้งเหล่านั้นต่อกันเพราะถ้าคำขอของคุณกำลังทำสิ่งที่ไม่เกี่ยวข้องกับ Db อยู่มาก DbContext ของคุณจะถูกเก็บไว้โดยไม่มีเหตุผล ดังนั้นจึงเป็นเรื่องเสียที่จะทำให้ DbContext ของคุณยังมีชีวิตอยู่ในขณะที่คำขอของคุณกำลังรอสิ่งที่สุ่มทำ ...

ผู้คนจำนวนมากที่ปฏิบัติตามกฎ 1มี DbContexts อยู่ใน"รูปแบบพื้นที่เก็บข้อมูล"และสร้างอินสแตนซ์ใหม่ตามการสืบค้นฐานข้อมูลดังนั้นX * DbContextต่อคำขอ

พวกเขาเพิ่งได้รับข้อมูลและกำจัดบริบทโดยเร็ว นี้คือการพิจารณาโดยหลายคนยอมรับการปฏิบัติ ในขณะที่สิ่งนี้มีประโยชน์ในการใช้ทรัพยากรฐานข้อมูลของคุณในเวลาที่น้อยที่สุด แต่เสียสละUnitOfWorkและCaching Candy ทั้งหมดที่ EF มีให้อย่างชัดเจน

การทำให้อินสแตนซ์อเนกประสงค์ของ DbContext ช่วยเพิ่มประโยชน์ของCaching ให้มากที่สุดแต่เนื่องจาก DbContext ไม่ปลอดภัยต่อเธรดและแต่ละคำขอของเว็บทำงานบนเธรดของตัวเอง DbContext ต่อคำขอจึงยาวที่สุดที่คุณสามารถเก็บไว้ได้

ดังนั้นคำแนะนำของทีม EF เกี่ยวกับการใช้ 1 Db บริบทต่อคำขอมันชัดเจนขึ้นอยู่กับข้อเท็จจริงที่ว่าในเว็บแอปพลิเคชัน UnitOfWork ส่วนใหญ่มีแนวโน้มที่จะอยู่ในคำขอเดียวและคำขอนั้นมีหนึ่งเธรด ดังนั้นหนึ่ง DbContext ต่อคำขอเป็นเหมือนประโยชน์ในอุดมคติของ UnitOfWork และ Caching

แต่ในหลายกรณีนี้ไม่เป็นความจริง ฉันพิจารณาการบันทึก UnitOfWork แยกจากกันดังนั้นจึงมี DbContext ใหม่สำหรับการโพสต์คำขอเข้าสู่ระบบในหัวข้อ asyncเป็นที่ยอมรับอย่างสมบูรณ์

ดังนั้นในที่สุดมันกลับกลายเป็นว่าอายุการใช้งานของ DbContext ถูก จำกัด ไว้ที่พารามิเตอร์ทั้งสองนี้ UnitOfWorkและเธรด


3
ในความเป็นธรรมการร้องขอ HTTP ของคุณควรจะเสร็จสิ้นค่อนข้างเร็ว (ไม่กี่มิลลิวินาที) หากพวกเขาจะไปนานกว่านั้นคุณอาจต้องการคิดเกี่ยวกับการประมวลผลพื้นหลังบางอย่างกับสิ่งที่ต้องการจัดตารางงานภายนอกเพื่อให้คำขอสามารถกลับมาทันที ที่กล่าวว่าสถาปัตยกรรมของคุณไม่ควรพึ่งพา HTTP เช่นกัน โดยรวมแล้วเป็นคำตอบที่ดี
บดขยี้

34

ไม่ใช่คำตอบเดียวที่นี่จริง ๆ ตอบคำถาม OP ไม่ได้ถามเกี่ยวกับการออกแบบ DbContext แบบซิงเกิล / ต่อแอปพลิเคชันเขาถามเกี่ยวกับการออกแบบคำขอต่อ (เว็บ) และผลประโยชน์ที่อาจเกิดขึ้น

ฉันจะอ้างอิงhttp://mehdi.me/ambient-dbcontext-in-ef6/เนื่องจาก Mehdi เป็นทรัพยากรที่ยอดเยี่ยม:

ประสิทธิภาพที่เป็นไปได้

แต่ละอินสแตนซ์ DbContext เก็บแคชระดับแรกของเอนทิตีทั้งหมดที่โหลดจากฐานข้อมูล เมื่อใดก็ตามที่คุณสอบถามเอนทิตีด้วยคีย์หลัก DbContext จะพยายามดึงข้อมูลจากแคชระดับแรกก่อนเริ่มต้นเพื่อสอบถามจากฐานข้อมูล ขึ้นอยู่กับรูปแบบการสืบค้นข้อมูลของคุณการใช้ DbContext เดียวกันอีกครั้งในธุรกรรมทางธุรกิจตามลำดับหลายครั้งอาจส่งผลให้มีการสอบถามฐานข้อมูลน้อยลงด้วยการใช้แคชระดับแรกของ DbContext

ช่วยให้โหลดขี้เกียจ

หากบริการของคุณส่งคืนเอนทิตีแบบถาวร (ตรงข้ามกับมุมมองการส่งคืนโมเดลหรือ DTO ประเภทอื่น ๆ ) และคุณต้องการใช้ประโยชน์จากการโหลดแบบสันหลังยาวในเอนทิตีเหล่านั้นอายุการใช้งานของอินสแตนซ์ DbContext ขอบเขตของธุรกรรมทางธุรกิจ หากวิธีการบริการจำหน่ายอินสแตนซ์ DbContext ที่ใช้ก่อนที่จะส่งคืนความพยายามในการโหลดคุณสมบัติ lazy บนเอนทิตีที่ส่งคืนจะล้มเหลว (ไม่ว่าจะใช้ lazy-loading หรือไม่ก็ตาม) ความคิดที่ดีคือการอภิปรายที่แตกต่างกัน ที่นี่) ในตัวอย่างเว็บแอปพลิเคชันของเราการโหลดสันหลังยาวโดยทั่วไปจะใช้ในวิธีการดำเนินการของตัวควบคุมในเอนทิตีที่ส่งคืนโดยชั้นบริการที่แยกต่างหาก ในกรณีนั้น,

โปรดทราบว่ามีข้อเสียเช่นกัน ลิงค์นั้นมีแหล่งข้อมูลอื่น ๆ มากมายที่จะอ่านในเรื่อง

เพียงโพสต์สิ่งนี้ในกรณีที่คนอื่นสะดุดกับคำถามนี้และจะไม่ได้รับคำตอบที่ไม่ตอบคำถาม


ลิงค์ที่ดี! การจัดการ DBContext อย่างชัดเจนดูเหมือนว่าเป็นวิธีที่ปลอดภัยที่สุด
aggsol

22

ฉันค่อนข้างแน่ใจว่าเป็นเพราะ DbContext ไม่ปลอดภัยเลย ดังนั้นการแบ่งปันสิ่งนี้จึงไม่ใช่ความคิดที่ดี


คุณหมายถึงการแบ่งปันในคำขอ HTTP นั้นไม่ใช่ความคิดที่ดีใช่หรือไม่
แอนดรู

2
ใช่แอนดรูนั่นคือสิ่งที่เขาหมายถึง การแบ่งปันบริบทนี้ใช้สำหรับแอปเดสก์ท็อปเธรดเดียวเท่านั้น
Elisabeth

10
สิ่งที่เกี่ยวกับการแบ่งปันบริบทสำหรับคำขอเดียว ดังนั้นสำหรับคำขอเดียวเราสามารถเข้าถึงที่เก็บที่แตกต่างกันและทำธุรกรรมกับพวกเขาโดยการแชร์หนึ่งและบริบทเดียวกันได้หรือไม่
Lyubomir Velchev

16

สิ่งหนึ่งที่ไม่ได้กล่าวถึงในคำถามจริงๆหรือการอภิปรายคือข้อเท็จจริงที่ว่า DbContext ไม่สามารถยกเลิกการเปลี่ยนแปลงได้ คุณสามารถส่งการเปลี่ยนแปลง แต่คุณไม่สามารถลบทรีการเปลี่ยนแปลงได้ดังนั้นหากคุณใช้บริบทคำขอต่อเนื่องคุณจะโชคไม่ดีถ้าคุณต้องการเปลี่ยนแปลงการเปลี่ยนแปลงไม่ว่าด้วยเหตุผลใด

ส่วนตัวฉันสร้างอินสแตนซ์ของ DbContext เมื่อจำเป็น - โดยปกติแล้วจะแนบไปกับส่วนประกอบทางธุรกิจที่มีความสามารถในการสร้างบริบทขึ้นมาใหม่หากจำเป็น ด้วยวิธีนี้ฉันสามารถควบคุมกระบวนการแทนที่จะบังคับให้อินสแตนซ์เดียวกับฉัน ฉันยังไม่ต้องสร้าง DbContext ที่การเริ่มต้นคอนโทรลเลอร์แต่ละครั้งไม่ว่าจะใช้งานจริงหรือไม่ จากนั้นถ้าฉันยังต้องการมีอินสแตนซ์ต่อคำขอฉันสามารถสร้างพวกเขาใน CTOR (ผ่าน DI หรือด้วยตนเอง) หรือสร้างพวกเขาตามที่ต้องการในแต่ละวิธีการควบคุม โดยส่วนตัวแล้วฉันมักจะใช้วิธีหลังเพื่อหลีกเลี่ยงการสร้างอินสแตนซ์ DbContext เมื่อไม่จำเป็นจริงๆ

มันขึ้นอยู่กับมุมที่คุณมองด้วย สำหรับฉันตัวอย่างต่อคำขอไม่เคยมีเหตุผล DbContext เป็นของ Http Request จริงหรือไม่ ในแง่ของพฤติกรรมที่ผิดสถานที่ องค์ประกอบทางธุรกิจของคุณควรสร้างบริบทไม่ใช่คำขอ Http จากนั้นคุณสามารถสร้างหรือละทิ้งองค์ประกอบทางธุรกิจของคุณได้ตามต้องการและไม่ต้องกังวลกับอายุการใช้งานของบริบท


1
นี่เป็นคำตอบที่น่าสนใจและฉันเห็นด้วยกับคุณบางส่วน สำหรับฉัน DbContext ไม่จำเป็นต้องเชื่อมโยงกับคำขอทางเว็บ แต่มันจะถูกพิมพ์ลงใน 'คำขอ' หนึ่งเดียวเสมอใน: 'ธุรกรรมทางธุรกิจ' และเมื่อคุณเชื่อมโยงบริบทกับธุรกรรมทางธุรกิจการยกเลิกการเปลี่ยนแปลงจะกลายเป็นสิ่งที่แปลกมาก แต่ไม่ได้มีอยู่ในขอบเขตคำขอเว็บไม่ได้หมายความว่าองค์ประกอบทางธุรกิจ (BCs) ควรจะสร้างบริบท; ฉันคิดว่านั่นไม่ใช่ความรับผิดชอบของพวกเขา แต่คุณสามารถใช้การกำหนดขอบเขตโดยใช้มัณฑนากรทั่ว BC ของคุณ วิธีนี้คุณสามารถเปลี่ยนการกำหนดขอบเขตได้โดยไม่ต้องเปลี่ยนรหัส
Steven

1
ในกรณีนั้นการฉีดเข้าไปในวัตถุทางธุรกิจควรจัดการกับการจัดการตลอดชีวิต ในมุมมองของฉันวัตถุธุรกิจเป็นเจ้าของบริบทและควรควบคุมอายุการใช้งาน
Rick Strahl

กล่าวโดยย่อคุณหมายความว่าอย่างไรเมื่อคุณพูดว่า "ความสามารถในการสร้างบริบทใหม่หากจำเป็น" คุณมีความสามารถในการย้อนกลับของคุณเองหรือไม่? คุณสามารถอธิบายตาดได้ไหม?
tntwyckoff

2
โดยส่วนตัวแล้วฉันคิดว่ามันลำบากเล็กน้อยที่จะบังคับให้ DbContext เริ่มต้นที่นั่น ไม่มีการรับประกันว่าคุณจะต้องกดฐานข้อมูล บางทีคุณอาจจะเรียกบริการของบุคคลที่สามที่เปลี่ยนสถานะทางด้านนั้น หรือบางทีคุณอาจมีฐานข้อมูล 2 หรือ 3 ตัวที่คุณทำงานด้วยในเวลาเดียวกัน คุณจะไม่สร้าง DbContexts จำนวนมากในตอนเริ่มต้นในกรณีที่คุณใช้มัน ธุรกิจรู้ข้อมูลที่ทำงานด้วยดังนั้นมันจึงเป็นข้อมูลนั้น เพียงใส่ TransactionScope ไว้ที่จุดเริ่มต้นหากจำเป็น ฉันไม่คิดว่าการโทรทั้งหมดจำเป็นต้องมี มันใช้ทรัพยากร
Daniel Lorenz

นั่นคือคำถามว่าคุณอนุญาตให้คอนเทนเนอร์ควบคุมอายุการใช้งานของ dbcontext ซึ่งควบคุมอายุการใช้งานของการควบคุมพาเรนต์หรือไม่ พูดว่าถ้าฉันต้องการบริการที่เรียบง่ายเพียงฉีดเข้าไปในตัวควบคุมของฉันฉันจะไม่สามารถใช้การฉีด constuctor เนื่องจากความหมายตามคำขอ
davidcarr

10

ฉันเห็นด้วยกับความคิดเห็นก่อนหน้า เป็นการดีที่จะบอกว่าถ้าคุณจะแบ่งปัน DbContext ในแอพเธรดเดียวคุณจะต้องมีหน่วยความจำเพิ่มขึ้น ตัวอย่างเช่นเว็บแอปพลิเคชันของฉันบน Azure (อินสแตนซ์ขนาดเล็กพิเศษหนึ่งรายการ) ต้องการหน่วยความจำอีก 150 MB และฉันมีผู้ใช้ประมาณ 30 คนต่อชั่วโมง แอปพลิเคชันแบ่งปัน DBContext ในคำขอ HTTP

นี่คือภาพตัวอย่างจริง: มีการปรับใช้แอปพลิเคชันในเวลา 12PM


อาจเป็นความคิดที่จะแบ่งปันบริบทสำหรับหนึ่งคำขอ หากเราเข้าถึงที่เก็บที่แตกต่างกันและ - คลาส DBSet และต้องการให้การดำเนินการกับพวกมันเป็นธุรกรรมที่ควรเป็นทางออกที่ดี ลองดูที่โครงการโอเพ่นซอร์ส mvcforum.comฉันคิดว่ามันเกิดขึ้นในการนำรูปแบบการออกแบบของหน่วยงานไปใช้
Lyubomir Velchev

3

สิ่งที่ฉันชอบเกี่ยวกับมันคือมันจัดหน่วยของงาน (ตามที่ผู้ใช้เห็น - เช่นส่งหน้า) กับหน่วยของงานในความหมาย ORM

ดังนั้นคุณสามารถทำธุรกรรมการส่งทั้งหน้าซึ่งคุณไม่สามารถทำได้หากคุณเปิดเผยวิธีการ CRUD กับการสร้างแต่ละบริบทใหม่


3

อีกเหตุผลหนึ่งที่ไม่ได้ใช้ DbContext แบบซิงเกิลแม้แต่ในแอพพลิเคชั่นผู้ใช้แบบเธรดเดี่ยวเดียวก็เป็นเพราะรูปแบบการระบุตัวตนที่ใช้ หมายความว่าทุกครั้งที่คุณดึงข้อมูลโดยใช้เคียวรีหรือโดย id มันจะเก็บอินสแตนซ์เอนทิตีที่ดึงมาไว้ในแคช ครั้งต่อไปที่คุณดึงเอนทิตีเดียวกันมันจะให้อินสแตนซ์แคชของเอนทิตีถ้ามีหากมีการแก้ไขใด ๆ ที่คุณทำในเซสชันเดียวกัน นี่เป็นสิ่งจำเป็นดังนั้นเมธอด SaveChanges จะไม่จบลงด้วยอินสแตนซ์เอนทิตีที่แตกต่างกันหลายรายการของเรกคอร์ดฐานข้อมูลเดียวกัน มิฉะนั้นบริบทจะต้องรวมข้อมูลจากอินสแตนซ์นิติบุคคลทั้งหมด

เหตุผลที่เป็นปัญหาคือ DbContext แบบซิงเกิลสามารถกลายเป็นระเบิดเวลาที่ในที่สุดก็สามารถแคชฐานข้อมูลทั้งหมด + ค่าใช้จ่ายของวัตถุ. NET ในหน่วยความจำ

มีวิธีแก้ไขพฤติกรรมนี้โดยใช้คำสั่ง Linq กับ.NoTracking()วิธีส่วนขยายเท่านั้น และทุกวันนี้พีซีก็มี RAM มากมาย แต่โดยปกติแล้วนั่นไม่ใช่พฤติกรรมที่ต้องการ


สิ่งนี้ถูกต้อง แต่คุณต้องถือว่า Garbage Collector ทำงานได้ทำให้ปัญหานี้เสมือนจริงมากกว่าของจริง
tocqueville

3
ตัวรวบรวมขยะจะไม่รวบรวมอินสแตนซ์ของวัตถุใด ๆ ที่ถือครองโดยวัตถุคงที่ / ซิงเกิลที่ใช้งานอยู่ พวกเขาจะจบลงในยุคที่ 2 ของกอง
Dmitry S.

1

ปัญหาอีกประการที่ต้องระวังเกี่ยวกับ Entity Framework โดยเฉพาะคือเมื่อใช้การรวมกันของการสร้างเอนทิตีใหม่ขี้เกียจโหลดและใช้เอนทิตีใหม่เหล่านั้น (จากบริบทเดียวกัน) หากคุณไม่ได้ใช้ IDbSet.Create (เทียบกับใหม่) การโหลด Lazy บนเอนทิตีนั้นจะไม่ทำงานเมื่อถูกดึงออกมาจากบริบทที่สร้างขึ้นมาตัวอย่าง:

 public class Foo {
     public string Id {get; set; }
     public string BarId {get; set; }
     // lazy loaded relationship to bar
     public virtual Bar Bar { get; set;}
 }
 var foo = new Foo {
     Id = "foo id"
     BarId = "some existing bar id"
 };
 dbContext.Set<Foo>().Add(foo);
 dbContext.SaveChanges();

 // some other code, using the same context
 var foo = dbContext.Set<Foo>().Find("foo id");
 var barProp = foo.Bar.SomeBarProp; // fails with null reference even though we have BarId set.
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.