manymanymore
manymanymore

Reputation: 3108

Error EF Core PostgreSQL during an entity insert

I am getting an error during an entity insert. I am using the Entity Framework Core with the PostgreSQL.

Here is a piece of code which produces an error:

public async Task Add(AddVideoDto dto)
{
    var videoModel = mapper.Map<Video>(dto);
    await context.Videos.AddAsync(videoModel);
    await context.SaveChangesAsync();
}

Here is the error log:

fail: Microsoft.EntityFrameworkCore.Database.Connection[20004]                                            et/oklike/oklikebe (master)
      An error occurred using the connection to database 'oklikedb' on server 'tcp://127.0.0.1:5432'.     
fail: Microsoft.AspNetCore.Server.Kestrel[13]        
      Connection id "0HLVLRDVR67DK", Request id "0HLVLRDVR67DK:00000001": An unhandled exception was thrown by the application.
System.InvalidOperationException: Reset() called on connector with state Connecting   
   at Npgsql.NpgsqlConnector.Reset()
   at Npgsql.ConnectorPool.Release(NpgsqlConnector connector)
   at Npgsql.NpgsqlConnection.<Close>g__FinishClose|76_1(NpgsqlConnection connection, 
NpgsqlConnector connector)
   at Npgsql.NpgsqlConnection.Close(Boolean wasBroken, Boolean async)
   at Npgsql.NpgsqlConnection.CloseAsync()
   at Npgsql.NpgsqlConnection.DisposeAsync()
   at Microsoft.EntityFrameworkCore.Storage.RelationalConnection.DisposeAsync()       
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.ServiceProviderEngineScope.<>c__DisplayClass15_0.<<DisposeAsync>g__Await|0>d.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.ServiceProviderEngineScope.<>c__DisplayClass15_0.<<DisposeAsync>g__Await|0>d.MoveNext()
--- End of stack trace from previous location where exception was thrown ---
   at Microsoft.AspNetCore.Http.Features.RequestServicesFeature.<DisposeAsync>g__Awaited|9_0(RequestServicesFeature servicesFeature, ValueTask vt)
   at Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http.HttpProtocol.FireOnCompletedAwaited(Task currentTask, Stack`1 onCompleted)

I am sure that I set up a connection to my db correctly. I verified that in the following way: I have another piece of code:

public async Task<List<GetVideoDto>> GetAll()
{
    var videoModels = await context
        .Videos
        .ToListAsync();
    return mapper.Map<List<GetVideoDto>>(videoModels);
}

And this piece of code works just fine. I manually inserted a value in my database and checked that it is returned by the await context.Videos.ToListAsync(); by debugging and by Postman. Also I can apply migrations to a database successfully.

So, the error seems to tell me that my piece of code tries to open a connection before closing it. But I can not understand how this could be possible.

I am very well aware of the state machine behind the async/await, so the context.SaveChangesAsync(); in my case will definitely run only after the context.Videos.AddAsync(videoModel); has completed.

UPDATE

I was able to better pin down the issue. The error is thrown due to this line:

await context.SaveChangesAsync();

So, I am not getting an error if I use the SaveChanges instead of the SaveChangesAsync. Does that mean that if I want to preserve the performance benefit of the SaveChangesAsync I should make the context to be not a singleton (as it is by default), but scoped?

Here is how I am adding the context right now:

services.AddDbContext<DataContext>(opt => opt.UseNpgsql(Configuration.GetConnectionString("DefaultConnection")));

I mean here is my entire Startup.ConfigureServices:

public void ConfigureServices(IServiceCollection services)
    {
        services.AddDbContext<DataContext>(opt =>
            opt.UseNpgsql(Configuration.GetConnectionString("DefaultConnection"))
        );

        services.AddCors();

        services.AddControllers();

        services.AddAutoMapper(typeof(Startup));

        services.AddScoped<IVideoService, VideoService>();
    }

And by the performance benefit of the SaveChangesAsync I mean that my thread won`t be idle waiting for the SaveChanges to complete, but will go back to the CLR thread pool.

I am strongly feeling that there should be a DDD principle which targets specifically the case of a correct SaveChangesAsync usage. But I can not find it (probably there is no one).

Upvotes: 1

Views: 1325

Answers (1)

Smoaty
Smoaty

Reputation: 46

Add entities using plain old Add() rather than trying to add them async. Its not a long running operation just adding to an in memory collection so there is no benefit to trying to make the Add operation async.

When you are adding to your DbSet you are doing just that, it's the actual saving that benefits from being Async, as that's what you can await.

If you read the docs on AddAsync you will also see you should the following:-

This method is async only to allow special value generators, such as the one used by 'Microsoft.EntityFrameworkCore.Metadata.SqlServerValueGenerationStrategy.SequenceHiLo', to access the database asynchronously. For all other cases the non async method should be used

.

I typically do this, and await the call to save in my controllers or whatever.

        public void AddThing(Thing thingToAdd)
        {
            _context.Things.Add(thingToAdd);
        }

        public async Task<bool> SaveChangesAsync()
        {
            return (await _context.SaveChangesAsync() > 0);
        }

Upvotes: 1

Related Questions