Entity Framework Core: การดำเนินการที่สองเริ่มต้นในบริบทนี้ก่อนที่การดำเนินการก่อนหน้านี้จะเสร็จสมบูรณ์


98

ฉันกำลังทำงานในโครงการ ASP.Net Core 2.0 โดยใช้ Entity Framework Core

<PackageReference Include="Microsoft.EntityFrameworkCore" Version="2.0.1" />
  <PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="2.0.0" PrivateAssets="All" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="2.0.0"/>

และในวิธีการรายการของฉันฉันได้รับข้อผิดพลาดนี้:

InvalidOperationException: A second operation started on this context before a previous operation completed. Any instance members are not guaranteed to be thread safe.
Microsoft.EntityFrameworkCore.Internal.ConcurrencyDetector.EnterCriticalSection()

นี่คือวิธีการของฉัน:

    [HttpGet("{currentPage}/{pageSize}/")]
    [HttpGet("{currentPage}/{pageSize}/{search}")]
    public ListResponseVM<ClientVM> GetClients([FromRoute] int currentPage, int pageSize, string search)
    {
        var resp = new ListResponseVM<ClientVM>();
        var items = _context.Clients
            .Include(i => i.Contacts)
            .Include(i => i.Addresses)
            .Include("ClientObjectives.Objective")
            .Include(i => i.Urls)
            .Include(i => i.Users)
            .Where(p => string.IsNullOrEmpty(search) || p.CompanyName.Contains(search))
            .OrderBy(p => p.CompanyName)
            .ToPagedList(pageSize, currentPage);

        resp.NumberOfPages = items.TotalPage;

        foreach (var item in items)
        {
            var client = _mapper.Map<ClientVM>(item);

            client.Addresses = new List<AddressVM>();
            foreach (var addr in item.Addresses)
            {
                var address = _mapper.Map<AddressVM>(addr);
                address.CountryCode = addr.CountryId;
                client.Addresses.Add(address);
            }

            client.Contacts = item.Contacts.Select(p => _mapper.Map<ContactVM>(p)).ToList();
            client.Urls = item.Urls.Select(p => _mapper.Map<ClientUrlVM>(p)).ToList();
            client.Objectives = item.Objectives.Select(p => _mapper.Map<ObjectiveVM>(p)).ToList();
            resp.Items.Add(client);
        }

        return resp;
    }

ฉันหลงทางเล็กน้อยโดยเฉพาะอย่างยิ่งเพราะมันใช้งานได้เมื่อฉันเรียกใช้ในเครื่อง แต่เมื่อฉันปรับใช้กับเซิร์ฟเวอร์การจัดเตรียม (IIS 8.5) มันทำให้ฉันเกิดข้อผิดพลาดนี้และทำงานได้ตามปกติ ข้อผิดพลาดเริ่มปรากฏขึ้นหลังจากที่ฉันเพิ่มความยาวสูงสุดของรุ่นใดรุ่นหนึ่งของฉัน ฉันยังอัปเดตความยาวสูงสุดของ View Model ที่เกี่ยวข้อง และมีวิธีรายการอื่น ๆ อีกมากมายที่คล้ายกันมากและใช้งานได้

ฉันมีงาน Hangfire ทำงานอยู่ แต่งานนี้ไม่ได้ใช้เอนทิตีเดียวกัน นั่นคือทั้งหมดที่ฉันคิดว่าเกี่ยวข้อง ความคิดใด ๆ ที่อาจทำให้เกิดสิ่งนี้?


1
ตรวจสอบนี้
Berkay

2
@Berkay ฉันเห็นสิ่งนั้นและคำถามอื่น ๆ ที่คล้ายกันและลองทำดู วิธีการของฉันไม่ตรงกันและฉันทำให้มันซิงค์เพื่อหลีกเลี่ยงปัญหาเหล่านี้ ฉันยังพยายามลบการแมปและพยายามลบ. ToPagedList มันยังคงส่งข้อผิดพลาด
André Luiz

จะเป็นการดีที่จะได้เห็นการติดตามสแต็กแบบเต็ม
Evk

และเพื่อให้ทราบว่าผลการใช้งานหลายมีการใช้งาน
เจ

มีปัญหาเดียวกันฉันพบว่าฉันมีจำนวนเต็มว่างในตารางฐานข้อมูลของฉัน ทันทีที่ฉันตั้งค่าคุณสมบัติโมเดลเอนทิตีของฉันให้ตรงกับ int ที่เป็นโมฆะทุกอย่างก็เริ่มทำงานดังนั้นข้อความก็ทำให้ฉันเข้าใจผิด ... !
AlwaysLearning

คำตอบ:


98

ฉันไม่แน่ใจว่าคุณกำลังใช้ IoC และ Dependency Injection เพื่อแก้ไข DbContext ของคุณที่เคยใช้หรือไม่ หากคุณทำและใช้ IoC ดั้งเดิมจาก. NET Core (หรือ IoC-Container อื่น ๆ ) และคุณได้รับข้อผิดพลาดนี้ตรวจสอบให้แน่ใจว่าได้ลงทะเบียน DbContext ของคุณเป็นชั่วคราว ทำ

services.AddTransient<MyContext>();

หรือ

services.AddDbContext<MyContext>(ServiceLifetime.Transient);

แทน

services.AddDbContext<MyContext>();

AddDbContext เพิ่มบริบทเป็นขอบเขตซึ่งอาจทำให้เกิดปัญหาเมื่อทำงานกับหลายเธรด

นอกจากนี้การดำเนินการ async / awaitอาจทำให้เกิดลักษณะการทำงานนี้ได้เมื่อใช้นิพจน์ async lambda

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

คำอธิบายง่ายๆก็คือการDbContextใช้งานไม่ปลอดภัยต่อเธรด คุณสามารถอ่านเพิ่มเติมเกี่ยวกับเรื่องนี้ได้ที่นี่


2
เมื่อฉันใช้ชั่วคราวฉันได้รับข้อผิดพลาดในการเชื่อมต่อ (ปิดหรือทิ้ง) 'OmniService.DataAccess.Models.OmniServiceDbContext' System.ObjectDisposedException: ไม่สามารถเข้าถึงอ็อบเจ็กต์ที่ถูกกำจัด สาเหตุทั่วไปของข้อผิดพลาดนี้คือการกำจัดบริบทที่ได้รับการแก้ไขจากการฉีดการพึ่งพาจากนั้นพยายามใช้อินสแตนซ์บริบทเดียวกันที่อื่นในแอปพลิเคชันของคุณในภายหลัง สิ่งนี้อาจเกิดขึ้นหากคุณเรียก Dispose () บนบริบทหรือตัดบริบทในคำสั่งที่ใช้ ... ชื่อวัตถุ: 'AsyncDisposer'
David

5
สวัสดี @David! ฉันเดาว่าคุณกำลังใช้Task.Run(async () => context.Set...)โดยไม่ต้องรอหรือสร้างบริบทฐานข้อมูลที่กำหนดขอบเขตโดยไม่ต้องรอผล ซึ่งหมายความว่าบริบทของคุณอาจถูกกำจัดไปแล้วเมื่อเข้าถึง ถ้าคุณอยู่ในไมโครซอฟท์ DI Task.Runคุณต้องสร้างขอบเขตการพึ่งพาตัวเองภายใน ตรวจสอบลิงก์เหล่านี้ด้วย stackoverflow.com/questions/45047877/... docs.microsoft.com/en-us/dotnet/api/...
alsami

3
ดังที่ได้กล่าวไว้ก่อนหน้านี้หากคุณพลาดที่จะเรียกวิธีการ async ด้วยคำหลักรอคุณจะประสบปัญหานี้
Yennefer

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

3
@alsami คุณคือฮีโร่ของฉัน 6 ชั่วโมงของการแก้ไขจุดบกพร่องที่เจ็บปวด นี่คือทางออก หากมีผู้อื่นกำลังฉีด IHttpContextAccessor ลงใน DbContext และการอ้างสิทธิ์เป็นโมฆะนี่คือวิธีแก้ ขอบคุณผู้ชายมาก ๆ
jcmontx

62

ในบางกรณีข้อผิดพลาดนี้เกิดขึ้นเมื่อเรียกเมธอดasyncโดยไม่มีawaitคีย์เวิร์ดซึ่งสามารถแก้ไขได้โดยเพิ่มawaitก่อนการเรียกใช้เมธอด อย่างไรก็ตามคำตอบอาจไม่เกี่ยวข้องกับคำถามที่กล่าวถึง แต่สามารถช่วยแก้ไขข้อผิดพลาดที่คล้ายกันได้


5
เรื่องนี้เกิดขึ้นกับฉัน เปลี่ยนFirst()เป็นawait / FirstAsync()ทำงาน
Guilherme

โหวตขึ้น โปรดดูที่jimlynn.wordpress.com/2017/11/16/… Jim Lynn "ENTITY FRAMEWORK ERROR: A SECOND OPERATION STARTED on this CONTEXT before the PREVIOUS OPERATION COMPLETED.
granadaCoder

ขอบคุณสำหรับสิ่งนี้! นี่เป็นปัญหาของฉันอย่างแน่นอน ... ลืมเพิ่มการรอคอยใน async mdethod
AxleWack

เกิดขึ้นกับฉันเช่นกันและความคิดเห็นนี้ช่วยในการค้นหาว่าฉันลืมการรอคอยที่ขาดหายไปไหน เมื่อพบแล้วปัญหาได้รับการแก้ไข
Zion Hai

มันใช้งานได้สำหรับฉันขอบคุณมาก
zoha_sh

43

ข้อยกเว้นหมายถึง_contextการใช้สองเธรดในเวลาเดียวกัน ทั้งสองเธรดในคำขอเดียวกันหรือโดยสองคำขอ

_contextประกาศของคุณอาจจะคงที่หรือไม่? มันไม่ควร.

หรือคุณโทรGetClientsหลายครั้งในคำขอเดียวกันจากที่อื่นในรหัสของคุณ?

คุณอาจจะทำสิ่งนี้อยู่แล้ว แต่โดยหลักการแล้วคุณกำลังใช้การฉีดขึ้นต่อกันสำหรับของคุณDbContextซึ่งหมายความว่าคุณจะใช้AddDbContext()ใน Startup.cs ของคุณและตัวสร้างคอนโทรลเลอร์ของคุณจะมีลักษณะดังนี้:

private readonly MyDbContext _context; //not static

public MyController(MyDbContext context) {
    _context = context;
}

หากรหัสของคุณไม่ใช่แบบนี้โปรดแสดงให้เราเห็นและเราอาจช่วยเพิ่มเติมได้


1
น่าจะเป็นงานที่ฉันมี ฉันจัดการเพื่อแก้ปัญหาดูคำตอบของฉัน แต่ฉันกำลังทำเครื่องหมายว่าคุณเป็นคนที่ใช่
André Luiz

รหัสของฉันเป็นแบบนี้ทุกประการและเรามักจะติดตาม "การดำเนินการที่สองเริ่มต้นในบริบทนี้ก่อนที่การดำเนินการแบบอะซิงโครนัสก่อนหน้านี้จะเสร็จสมบูรณ์ใช้" รอ "เพื่อให้แน่ใจว่าการดำเนินการแบบอะซิงโครนัสใด ๆ เสร็จสมบูรณ์ก่อนที่จะเรียกวิธีการอื่นในบริบทนี้สมาชิกอินสแตนซ์ใด ๆ ไม่ได้ รับประกันว่าด้ายปลอดภัย - ที่ System.Data.Entity.Internal.ThrowingMonitor.EnsureNotEntered () ".
NMathur

@NMathur คุณใช้_contextวัตถุของคุณในกระทู้อื่นหรือไม่? ชอบข้างในTask.Run()ตัวอย่างไหม
Gabriel Luci

@GabrielLuci วิธีการทั้งหมดของฉันไม่เหมือนกับด้านล่างนี้จะทำให้เกิดปัญหา ความรู้ของฉันเกี่ยวกับหัวข้อนี้มีน้อยมาก คุณช่วยแนะนำได้ไหมว่าฉันควรอ่านรายละเอียดที่ไหนและอะไรเพื่อทำความเข้าใจพฤติกรรมเหล่านี้ งาน async สาธารณะ <List <Item>> GetItems (int orderId) {List <Item> items = await _contextItem.Where (x => x.OrderId == orderId) .ToListAsync (); คืนสินค้า; }
NMathur

@NMathur นั่นดูดี ตรวจสอบให้แน่ใจว่าคุณใช้awaitวิธี async อยู่เสมอ หากคุณไม่ใช้awaitคุณสามารถเข้าสู่มัลติเธรดโดยไม่ได้ตั้งใจ
Gabriel Luci

11
  • แก้ปัญหาของฉันโดยใช้โค้ดบรรทัดนี้ในไฟล์ Startup.cs ของฉัน
    การเพิ่มบริการชั่วคราวหมายความว่าทุกครั้งที่มีการร้องขอบริการอินสแตนซ์ใหม่จะถูกสร้างขึ้นเมื่อคุณทำงานกับ Dependency injection

           services.AddDbContext<Context>(options =>
                            options.UseSqlServer(_configuration.GetConnectionString("ContextConn")),
                 ServiceLifetime.Transient);
    

วิธีนี้จะทำให้เกิดปัญหาในกรณีที่จำนวนธุรกรรมโต้ตอบแบบทันทีของผู้ใช้หลายคนสูง
hakantopuz

11

ฉันมีปัญหาเดียวกันและปรากฎว่าบริการผู้ปกครองเป็นแบบเดี่ยว ดังนั้นบริบทจึงกลายเป็น singelton โดยอัตโนมัติเช่นกัน แม้ว่าจะถูกประกาศเป็น Per Life Time Scoped ใน DI ก็ตาม

การฉีดบริการที่มีช่วงอายุการใช้งานที่แตกต่างกันไปในอีก

  1. ห้ามฉีดบริการ Scoped & Transient ลงในบริการ Singleton (สิ่งนี้จะแปลงบริการชั่วคราวหรือขอบเขตเป็นซิงเกิลตันได้อย่างมีประสิทธิภาพ)

  2. ห้ามฉีดบริการชั่วคราวลงในบริการที่กำหนดขอบเขต (ซึ่งจะแปลงบริการชั่วคราวเป็นขอบเขต)


นี่คือปัญหาของฉันอย่างแน่นอน
Jonesopolis

นี่เป็นปัญหาของฉันเหมือนกัน ฉันกำลังลงทะเบียนคลาสตัวจัดการเป็นซิงเกิลตันและ DbContext เป็นชั่วคราว ฉันต้องใช้ ServiceProvider ภายในคลาส Handler เพื่อรับอินสแตนซ์ชั่วคราวจากคอนเทนเนอร์ DI ทุกครั้งที่ Handler ถูกตี
Daiana Sodré

6

ฉันคิดว่าคำตอบนี้ยังช่วยได้บางส่วนและประหยัดได้หลายครั้ง ฉันแก้ไขปัญหาที่คล้ายกันโดยเปลี่ยนIQueryableเป็นList(หรืออาร์เรย์คอลเลกชัน ... )

ตัวอย่างเช่น:

var list=_context.table1.where(...);

ถึง

var list=_context.table1.where(...).ToList(); //or ToArray()...

3
IMHO คำตอบนี้ไม่สมควรได้รับคะแนนลบเป็นการแสดงออกที่ไม่ดี .ToList () ช่วยแก้ปัญหาส่วนใหญ่ "การดำเนินการครั้งที่สอง ... " ได้อย่างแท้จริงเนื่องจากมีการบังคับให้มีการประเมินนิพจน์ทันที วิธีนี้จะไม่มีการดำเนินการตามบริบทในการจัดคิว
vassilag

1
นี่เป็นปัญหาในกรณีของฉัน ฉันมี xxx.Contains (z.prop) ในที่ซึ่งส่วนคำสั่งของแบบสอบถาม xxx ควรจะเป็นอาร์เรย์ int [] ที่แตกต่างกันซึ่งได้รับการแก้ไขจากข้อความค้นหาก่อนหน้านี้ น่าเสียดายที่เมื่อมีการค้นหาครั้งที่สอง xxx ก็ยังคงเป็น IQueryable การเพิ่ม xxx ToArray () ก่อนการค้นหาครั้งที่สองช่วยแก้ไขปัญหาของฉัน
Jason Butera

5

ฉันมีข้อผิดพลาดเดียวกัน มันเกิดขึ้นเพราะฉันเรียกเมธอดที่สร้างเป็นpublic async void ...public async Task ...แทน


2

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

Task task = Task.Run(() =>
    {
        using (var scope = serviceScopeFactory.CreateScope())
        {
            var otherOfferService = scope.ServiceProvider.GetService<IOfferService>();
            // everything was ok here. then I did: 
            productService.DoSomething(); // (from the main scope) and this failed because the db context associated to that service was already disposed.
            ...
        }
    }

ฉันควรจะทำสิ่งนี้:

var otherProductService = scope.ServiceProvider.GetService<IProductService>();
otherProductService.DoSomething();

บริบทจะไม่ถูกเปิดเผยเมื่อทุกอย่างในบล็อกการใช้งานเสร็จสิ้นการดำเนินการแล้วหรือไม่?
Sello Mkantjwa

เมื่อมีการกำจัดการกระทำทุกอย่างจะถูกกำจัดในขอบเขตนั้น หากคุณมีงานที่กำลังทำงานอยู่เบื้องหลังและงานนั้นยาวกว่าการดำเนินการคุณจะพบปัญหานี้เว้นแต่คุณจะสร้างขอบเขตใหม่สำหรับงานเช่นเดียวกับที่ฉันทำในตัวอย่าง ในทางกลับกันหากงานของคุณอาจใช้เวลานานหรือคุณต้องการแน่ใจ 100% ว่าจะทำงานได้คุณอาจต้องใช้คิว หากคุณใช้ Azure คุณสามารถใช้คิวบัสบริการได้
Francisco Goldenstein

2

สถานการณ์ของฉันแตกต่างออกไป: ฉันพยายามเริ่มต้นฐานข้อมูลกับผู้ใช้ 30 คนซึ่งเป็นของบทบาทเฉพาะดังนั้นฉันจึงเรียกใช้รหัสนี้:

for (var i = 1; i <= 30; i++)
{
    CreateUserWithRole("Analyst", $"analyst{i}", UserManager);
}

นี่คือฟังก์ชัน Sync ข้างในนั้นฉันโทรไป 3 ครั้ง:

UserManager.FindByNameAsync(username).Result
UserManager.CreateAsync(user, pass).Result
UserManager.AddToRoleAsync(user, roleName).Result

เมื่อฉันแทนที่.Resultด้วย.GetAwaiter().GetResult()ข้อผิดพลาดนี้ก็หายไป


2

Entity Framework Core ไม่สนับสนุนการดำเนินการแบบขนานหลายรายการที่รันบนDbContextอินสแตนซ์เดียวกัน ซึ่งรวมถึงการดำเนินการasyncแบบสอบถามแบบขนานและการใช้งานพร้อมกันอย่างชัดเจนจากหลายเธรด ดังนั้นจึงควรawait asyncเรียกใช้ทันทีหรือใช้DbContextอินสแตนซ์แยกต่างหากสำหรับการดำเนินการที่ดำเนินการควบคู่กัน


1

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

วิธีแก้ปัญหาหนึ่งตามที่กล่าวไว้ในเธรดนี้คือการเปลี่ยนอายุการใช้งานของ DbContext เป็นชั่วคราวโดยกำหนดเช่น

services.AddDbContext<DbContext>(ServiceLifetime.Transient);

แต่เนื่องจากฉันทำการเปลี่ยนแปลงในบริการที่แตกต่างกันหลายรายการและทำการเปลี่ยนแปลงพร้อมกันโดยใช้ไฟล์ SaveChanges()วิธีการแก้ปัญหานี้ไม่ได้ผลในกรณีของฉัน

เนื่องจากรหัสของฉันทำงานในบริการฉันจึงทำบางอย่างเช่น

using (var scope = Services.CreateScope())
{
   var entities = scope.ServiceProvider.GetRequiredService<IReadService>().GetEntities();
   var writeService = scope.ServiceProvider.GetRequiredService<IWriteService>();
   foreach (Entity entity in entities)
   {
       writeService.DoSomething(entity);
   } 
}

เพื่อให้สามารถใช้บริการได้เหมือนเป็นการร้องของ่ายๆ ดังนั้นเพื่อแก้ปัญหาฉันแค่แบ่งขอบเขตเดียวออกเป็นสองขอบเขตหนึ่งสำหรับแบบสอบถามและอีกอันสำหรับการดำเนินการเขียนดังนี้:

using (var readScope = Services.CreateScope())
using (var writeScope = Services.CreateScope())
{
   var entities = readScope.ServiceProvider.GetRequiredService<IReadService>().GetEntities();
   var writeService = writeScope.ServiceProvider.GetRequiredService<IWriteService>();
   foreach (Entity entity in entities)
   {
       writeService.DoSomething(entity);
   } 
}

เช่นนั้นมีการใช้งาน DbContext สองอินสแตนซ์ที่แตกต่างกันอย่างมีประสิทธิภาพ

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


1

ขั้นแรกให้โหวต (อย่างน้อยที่สุด) คำตอบของอัลซามิ นั่นทำให้ฉันมาถูกทางแล้ว

แต่สำหรับคนที่คุณทำ IoC นี่เป็นการดำน้ำที่ลึกขึ้นเล็กน้อย

ข้อผิดพลาดของฉัน (เช่นเดียวกับข้อผิดพลาดอื่น ๆ )

เกิดข้อผิดพลาดอย่างน้อยหนึ่งรายการ (การดำเนินการที่สองเริ่มต้นในบริบทนี้ก่อนที่การดำเนินการก่อนหน้านี้จะเสร็จสมบูรณ์ซึ่งมักเกิดจากเธรดที่แตกต่างกันโดยใช้ DbContext อินสแตนซ์เดียวกันสำหรับข้อมูลเพิ่มเติมเกี่ยวกับวิธีหลีกเลี่ยงปัญหาเธรดกับ DbContext โปรดดูที่ https://go.microsoft.com / fwlink /? linkid = 2097913. )

การตั้งค่ารหัสของฉัน "แค่พื้นฐาน" ...

public class MyCoolDbContext: DbContext{
    public DbSet <MySpecialObject> MySpecialObjects {        get;        set;    }
}

และ

public interface IMySpecialObjectDomainData{}

และ (โปรดทราบว่ากำลังฉีด MyCoolDbContext)

public class MySpecialObjectEntityFrameworkDomainDataLayer: IMySpecialObjectDomainData{
    public MySpecialObjectEntityFrameworkDomainDataLayer(MyCoolDbContext context) {
        /* HERE IS WHERE TO SET THE BREAK POINT, HOW MANY TIMES IS THIS RUNNING??? */
        this.entityDbContext = context ?? throw new ArgumentNullException("MyCoolDbContext is null", (Exception)null);
    }
}

และ

public interface IMySpecialObjectManager{}

และ

public class MySpecialObjectManager: IMySpecialObjectManager
{
    public const string ErrorMessageIMySpecialObjectDomainDataIsNull = "IMySpecialObjectDomainData is null";
    private readonly IMySpecialObjectDomainData mySpecialObjectDomainData;

    public MySpecialObjectManager(IMySpecialObjectDomainData mySpecialObjectDomainData) {
        this.mySpecialObjectDomainData = mySpecialObjectDomainData ?? throw new ArgumentNullException(ErrorMessageIMySpecialObjectDomainDataIsNull, (Exception)null);
    }
}

และในที่สุดคลาสมัลติเธรดของฉันถูกเรียกจากแอพคอนโซล (แอพ Command Line Interface)

    public interface IMySpecialObjectThatSpawnsThreads{}

และ

public class MySpecialObjectThatSpawnsThreads: IMySpecialObjectThatSpawnsThreads
{
    public const string ErrorMessageIMySpecialObjectManagerIsNull = "IMySpecialObjectManager is null";

    private readonly IMySpecialObjectManager mySpecialObjectManager;

    public MySpecialObjectThatSpawnsThreads(IMySpecialObjectManager mySpecialObjectManager) {
        this.mySpecialObjectManager = mySpecialObjectManager ?? throw new ArgumentNullException(ErrorMessageIMySpecialObjectManagerIsNull, (Exception)null);
    }
}

และการสะสม DI (อีกครั้งสำหรับแอปพลิเคชันคอนโซล (อินเทอร์เฟซบรรทัดคำสั่ง) ... ซึ่งแสดงพฤติกรรมที่แตกต่างจากเว็บแอปเล็กน้อย)

private static IServiceProvider BuildDi(IConfiguration configuration) {
    /* this is being called early inside my command line application ("console application") */

    string defaultConnectionStringValue = string.Empty; /* get this value from configuration */

    ////setup our DI
    IServiceCollection servColl = new ServiceCollection()
        ////.AddLogging(loggingBuilder => loggingBuilder.AddConsole())

        /* THE BELOW TWO ARE THE ONES THAT TRIPPED ME UP.  */
        .AddTransient<IMySpecialObjectDomainData, MySpecialObjectEntityFrameworkDomainDataLayer>()
    .AddTransient<IMySpecialObjectManager, MySpecialObjectManager>()

    /* so the "ServiceLifetime.Transient" below................is what you will find most commonly on the internet search results */
     # if (MY_ORACLE)
        .AddDbContext<ProvisioningDbContext>(options => options.UseOracle(defaultConnectionStringValue), ServiceLifetime.Transient);
     # endif

     # if (MY_SQL_SERVER)
        .AddDbContext<ProvisioningDbContext>(options => options.UseSqlServer(defaultConnectionStringValue), ServiceLifetime.Transient);
     # endif

    servColl.AddSingleton <IMySpecialObjectThatSpawnsThreads,        MySpecialObjectThatSpawnsThreads>();

    ServiceProvider servProv = servColl.BuildServiceProvider();

    return servProv;
}

สิ่งที่ทำให้ฉันประหลาดใจคือ (เปลี่ยนเป็น) ชั่วคราวสำหรับ

        .AddTransient<IMySpecialObjectDomainData, MySpecialObjectEntityFrameworkDomainDataLayer>()
    .AddTransient<IMySpecialObjectManager, MySpecialObjectManager>()

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

ประเด็นคือ ....... มันไม่ใช่แค่ DbContext (ของฉัน) ที่ต้องการ Transient ... แต่เป็น DI Graph ที่ใหญ่กว่า

เคล็ดลับการแก้จุดบกพร่อง:

บรรทัดนี้:

this.entityDbContext = context ?? throw new ArgumentNullException("MyCoolDbContext is null", (Exception)null);

ใส่จุดพักดีบั๊กไว้ที่นั่น หาก MySpecialObjectThatSpawnsThreads ของคุณสร้าง N จำนวนเธรด (เช่น 10 เธรดเช่น) ...... และบรรทัดนั้นถูกตีเพียงครั้งเดียว ... นั่นคือปัญหาของคุณ DbContext ของคุณกำลังข้ามเธรด

โบนัส:

ฉันขอแนะนำให้อ่าน URL / บทความด้านล่างนี้ (oldie but goodie) เกี่ยวกับความแตกต่างของเว็บแอปและคอนโซลแอป

https://mehdi.me/ambient-dbcontext-in-ef6/

นี่คือส่วนหัวของบทความในกรณีที่ลิงก์เปลี่ยนไป

การจัดการ DBCONTEXT วิธีที่ถูกต้องด้วย ENTITY FRAMEWORK 6: คู่มือเชิงลึก Mehdi El Gueddari

ฉันพบปัญหานี้กับ WorkFlowCore https://github.com/danielgerlag/workflow-core

  <ItemGroup>
    <PackageReference Include="WorkflowCore" Version="3.1.5" />
  </ItemGroup>

โค้ดตัวอย่างด้านล่าง .. เพื่อช่วยเหลือผู้ค้นหาทางอินเทอร์เน็ตในอนาคต

 namespace MyCompany.Proofs.WorkFlowCoreProof.BusinessLayer.Workflows.MySpecialObjectInterview.Workflows
    {
        using System;
        using MyCompany.Proofs.WorkFlowCoreProof.BusinessLayer.Workflows.MySpecialObjectInterview.Constants;
        using MyCompany.Proofs.WorkFlowCoreProof.BusinessLayer.Workflows.MySpecialObjectInterview.Glue;
        using MyCompany.Proofs.WorkFlowCoreProof.BusinessLayer.Workflows.WorkflowSteps;

        using WorkflowCore.Interface;
        using WorkflowCore.Models;

        public class MySpecialObjectInterviewDefaultWorkflow : IWorkflow<MySpecialObjectInterviewPassThroughData>
        {
            public const string WorkFlowId = "MySpecialObjectInterviewWorkflowId";

            public const int WorkFlowVersion = 1;

            public string Id => WorkFlowId;

            public int Version => WorkFlowVersion;

            public void Build(IWorkflowBuilder<MySpecialObjectInterviewPassThroughData> builder)
            {
                builder
                             .StartWith(context =>
                    {
                        Console.WriteLine("Starting workflow...");
                        return ExecutionResult.Next();
                    })

                        /* bunch of other Steps here that were using IMySpecialObjectManager.. here is where my DbContext was getting cross-threaded */


                    .Then(lastContext =>
                    {
                        Console.WriteLine();

                        bool wroteConcreteMsg = false;
                        if (null != lastContext && null != lastContext.Workflow && null != lastContext.Workflow.Data)
                        {
                            MySpecialObjectInterviewPassThroughData castItem = lastContext.Workflow.Data as MySpecialObjectInterviewPassThroughData;
                            if (null != castItem)
                            {
                                Console.WriteLine("MySpecialObjectInterviewDefaultWorkflow complete :)  {0}   -> {1}", castItem.PropertyOne, castItem.PropertyTwo);
                                wroteConcreteMsg = true;
                            }
                        }

                        if (!wroteConcreteMsg)
                        {
                            Console.WriteLine("MySpecialObjectInterviewDefaultWorkflow complete (.Data did not cast)");
                        }

                        return ExecutionResult.Next();
                    }))

                    .OnError(WorkflowCore.Models.WorkflowErrorHandling.Retry, TimeSpan.FromSeconds(60));

            }
        }
    }

และ

ICollection<string> workFlowGeneratedIds = new List<string>();
                for (int i = 0; i < 10; i++)
                {
                    MySpecialObjectInterviewPassThroughData currentMySpecialObjectInterviewPassThroughData = new MySpecialObjectInterviewPassThroughData();
                    currentMySpecialObjectInterviewPassThroughData.MySpecialObjectInterviewPassThroughDataSurrogateKey = i;

                    ////  private readonly IWorkflowHost workflowHost;
                    string wfid = await this.workflowHost.StartWorkflow(MySpecialObjectInterviewDefaultWorkflow.WorkFlowId, MySpecialObjectInterviewDefaultWorkflow.WorkFlowVersion, currentMySpecialObjectInterviewPassThroughData);
                    workFlowGeneratedIds.Add(wfid);
                }

0

ฉันได้รับข้อความเดียวกัน แต่มันไม่สมเหตุสมผลเลยในกรณีของฉัน ปัญหาของฉันคือฉันใช้คุณสมบัติ "NotMapped" โดยไม่ได้ตั้งใจ อาจหมายถึงข้อผิดพลาดของไวยากรณ์ Linq หรือคลาสโมเดลในบางกรณีเท่านั้น ข้อความแสดงข้อผิดพลาดดูเหมือนจะทำให้เข้าใจผิด ความหมายดั้งเดิมของข้อความนี้คือคุณไม่สามารถเรียก async บน dbcontext เดียวกันมากกว่าหนึ่งครั้งในคำขอเดียวกัน

[NotMapped]
public int PostId { get; set; }
public virtual Post Post { get; set; }

คุณสามารถตรวจสอบรายละเอียดได้ที่ลิงค์นี้ https://www.softwareblogs.com/Posts/Details/5/error-a-second-operation-started-on-this-context-before-a-previous-operation-completed


0

ฉันพบปัญหาเดียวกันเมื่อพยายามใช้ FirstOrDefaultAsync() ในวิธี async ในโค้ดด้านล่าง และเมื่อฉันแก้ไขFirstOrDefault()- ปัญหาได้รับการแก้ไขแล้ว!

_context.Issues.Add(issue);
        await _context.SaveChangesAsync();

        int userId = _context.Users
            .Where(u => u.UserName == Options.UserName)
            .FirstOrDefaultAsync()
            .Id;
...

2
มันไม่เกี่ยวข้องกับ FirstOrDefault () หรือ FirstOrDefaultAsync () เลยมันเกี่ยวกับการใช้งาน dbContext
sajadre

0

ฉันจัดการเพื่อรับข้อผิดพลาดนั้นโดยส่งIQueryableเข้าไปในวิธีการที่ใช้ 'รายการ' IQueryable นั้นเป็นส่วนหนึ่งของแบบสอบถามอื่นในบริบทเดียวกัน

public void FirstMethod()
{
    // This is returning an IQueryable
    var stockItems = _dbContext.StockItems
        .Where(st => st.IsSomething);

    SecondMethod(stockItems);
}

public void SecondMethod(IEnumerable<Stock> stockItems)
{
    var grnTrans = _dbContext.InvoiceLines
        .Where(il => stockItems.Contains(il.StockItem))
        .ToList();
}

เพื่อหยุดสิ่งนั้นฉันใช้วิธีการที่นี่และทำให้รายการนั้นเป็นจริงก่อนที่จะส่งต่อวิธีที่สองโดยเปลี่ยนการเรียกSecondMethodให้เป็นSecondMethod(stockItems.ToList()


วิธีนี้ช่วยแก้ปัญหาได้ แต่จะไม่ทำให้ประสิทธิภาพช้าลงมีทางเลือกอื่นอีกหรือไม่?
Dheeraj Kumar

0

ในกรณีของฉันฉันใช้ส่วนประกอบเทมเพลตใน Blazor

 <BTable ID="Table1" TotalRows="MyList.Count()">

ปัญหาคือการเรียกใช้เมธอด (Count) ในส่วนหัวของคอมโพเนนต์ ในการแก้ไขปัญหาฉันเปลี่ยนเป็นดังนี้:

int total = MyList.Count();

และหลังจากนั้น :

<BTable ID="Table1" TotalRows="total">

0

ฉันรู้ว่ามีการถามปัญหานี้เมื่อสองปีก่อน แต่ฉันเพิ่งพบปัญหานี้และการแก้ไขที่ฉันใช้ช่วยได้มาก

หากคุณกำลังทำสองแบบสอบถามที่มีบริบทเดียวกันคุณอาจต้องลบไฟล์AsNoTracking. หากคุณใช้AsNoTrackingคุณกำลังสร้างตัวอ่านข้อมูลใหม่สำหรับการอ่านแต่ละครั้ง เครื่องอ่านข้อมูลสองเครื่องไม่สามารถอ่านข้อมูลเดียวกันได้


0

ในกรณีของฉันฉันใช้การล็อกซึ่งไม่อนุญาตให้ใช้การรอคอยและไม่สร้างคำเตือนคอมไพเลอร์เมื่อคุณไม่รอการ async

ปัญหา:

lock (someLockObject) {
    // do stuff
    context.SaveChangesAsync();
}

// some other code somewhere else doing await context.SaveChangesAsync() shortly after the lock gets the concurrency error

การแก้ไข: รอ async ภายในล็อคโดยทำการบล็อกด้วย. Wait ()

lock (someLockObject) {
    // do stuff
    context.SaveChangesAsync().Wait();
}

0

อีกกรณีที่เป็นไปได้: หากคุณใช้การเชื่อมต่อโดยตรงอย่าลืมปิด if ฉันต้องการเรียกใช้แบบสอบถาม SQL โดยพลการและอ่านผลลัพธ์ นี่เป็นการแก้ไขอย่างรวดเร็วฉันไม่ต้องการกำหนดคลาสข้อมูลไม่ได้ตั้งค่าการเชื่อมต่อ SQL "ปกติ" ดังนั้นฉันจึงใช้การเชื่อมต่อฐานข้อมูลของ EFC ซ้ำเป็นvar connection = Context.Database.GetDbConnection() as SqlConnectionไฟล์. ให้แน่ใจว่าคุณโทรหาก่อนที่จะทำconnection.Close()Context.SaveChanges()


0

คุณสามารถใช้ SemaphoreSlim เพื่อบล็อกเธรดถัดไปที่จะพยายามเรียกใช้การเรียก EF นั้น

static SemaphoreSlim semSlim = new SemaphoreSlim(1, 1);

await semSlim.WaitAsync();
try
{
  // something like this here...
  // EmployeeService.GetList(); or...
  var result = await _ctx.Employees.ToListAsync();
}
finally
{
  semSlim.Release();
}

ได้โปรดไม่ มันไม่เคยมีความคิดที่ดีที่จะใช้ร่วมกันเช่นบริบทที่มีหลายหัวข้อแม้ในขณะที่ในการประมวลผลแบบอนุกรม
เกิร์ตอาร์โนลด์

-2

หากวิธีการของคุณส่งคืนบางสิ่งกลับมาคุณสามารถแก้ไขข้อผิดพลาดนี้ได้โดยวาง .Resultจนจบงานและ .Wait()หากไม่ส่งคืนอะไรเลย


-6

ฉันเพิ่งจัดการเพื่อให้มันใช้งานได้อีกครั้ง มันไม่ค่อยสมเหตุสมผล แต่มันใช้งานได้:

  1. ลบ Hangfire ออกจาก StartUp (ฉันกำลังสร้างงานที่นั่น)
  2. ลบฐานข้อมูล Hangfire
  3. รีสตาร์ทเซิร์ฟเวอร์

ฉันจะตรวจสอบเพิ่มเติมในภายหลัง แต่วิธีที่ฉันเรียกด้วย Hangfire ได้รับ DBContext และนั่นคือสาเหตุที่เป็นไปได้

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.