EFCore 2.0 : A second operation started on this context before a previous operation completed

91 Views Asked by At

I understand what error is saying but unable to implement solution. I need to make a call to database (just to select) inside a method which would be called in thread.

I have searched through multiple answer for this and tried adding ServiceLifetime.Transient but got same result. Looks like something wrong in my DI.

I also have tried to pass Context in constructor to PriceSheetAsyncRepository but no luck.

public class PriceSheetRepository : IPriceSheetRepository
{
    private readonly Pricing_PortalContext _context;
    private readonly ILogger _logger;

    public PriceSheetRepository(Pricing_PortalContext context, ILogger<PriceSheetRepository> logger)
    {
        _context = context;
        _logger = logger;
    }
    public async Task<bool> ProcessPriceSheetImport(long ImportId)
    {
        var pricesheetAsyncRepo = new PriceSheetAsyncRepository(_logger);

        foreach (var i in RequestList)
        {
            tasks.Add(Task.Run(async () => await pricesheetAsyncRepo.ValidatePublishAsync(i, _context)));
        }
        await Task.WhenAll(tasks);
    }
}


public class PriceSheetAsyncRepository
{
    private readonly ILogger _logger;

    public PriceSheetAsyncRepository(ILogger logger)
    {
        _logger = logger;

    }

    public Task<bool> ValidatePublishAsync(PriceSheetImportModel i, MyDBContext _context)
    {
        return Task.Run(() =>
        {
            i.IsValid = true;
            i.LinePart = i.LinePart?.ToUpper();

            if (effDate != null && _context.MyTablePublish.Where(x => x.A == i.A).Any())
            {
                i.IsValid = false;
                i.Message = i.Message + "Duplicate published record, ";
            }
        }

     }
}

Can any one please point out what Am I doing wrong here?

2

There are 2 best solutions below

2
Jeff On BEST ANSWER

Simply put - Entity Frameworks contexts are not thread safe and don’t allow multiple operations to run in parallel.

Transient works if you actually have different instances of your context. But in your case, you’re just using the context on multiple concurrent Tasks.

Either make your code single threaded (for loop with await inside instead of adding to a list of tasks and using Task.WhenAll) or create a new instance of the context per Task you want to use it with.

An example of the latter might look like this (note the removal of Task.Run - it's hurting you, not helping)

// in container builder
services.AddTransient<Pricing_PortalContext>();
services.AddTransient<Func<Pricing_PortalContext>>(sp => sp.GetRequiredService<Pricing_PortalContext>());

// inject a Func
public PriceSheetRepository(Func<Pricing_PortalContext> contextFactory, ILogger<PriceSheetRepository> logger)

// create a new func per task
tasks.Add(async () => await pricesheetAsyncRepo.ValidatePublishAsync(i, _contextFactory()));

0
Svyatoslav Danyliv On

While there is correct answer to your problem, looks like you are trying to kill fly with the hammer. Summoning a bunch of tasks for your problem is overkill.

public async Task ProcessPriceSheetImport(long ImportId)
{
    var ids = RequestList.Select(x => x.A);

    var existent = new HasSet<int>(await context.MyTablePublish.Where(x => ids.Contains(x.A)).Select(x => x.A).ToList());

    foreach (var i in RequestList)
    {
        if (existent.Contains(i.A))
        {
            i.IsValid = false;
            i.Message = i.Message + "Duplicate published record, ";
        }
    }
}