Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

EF + AutoFac + async "The connection's current state is connecting"

I have a WebApi controller that has services injected by AutoFac in the OWIN Startup class

builder.Register(c => new MyEntities()).InstancePerRequest();

I have also tried

builder.Register(c => new MyEntities()).InstancePerLifetimeScope();

In a controller action I call a service method to create a new record, pass the id created to an external api through HttpClient to get some more data, then update the new record with some return data.

[HttpPost, Route("")] 
public async Task<IHttpActionResult> MyControllerAction(MyModel model)
{
    var id = await _MyService.CreateNewThing(model.SomeId);
    var externalData = await CallExternalApiThroughHttpClient(id);
    await _MyService.UpdateNewThing(id, externalData);
    return Ok();
}

service code

public class MyService : IMyService 
{
    private MyEntities _context;

    public MyService(MyEntities context)
    {
        _context = context;
    }

    public async Task<int> CreateNewThing(int someId)
    {
        var thing = new Thing
        {
            SomeId = someId
        };

        _context.Things.Add(thing);

        await _context.SaveChangesAsync();

        return thing.Id;
    }

    public async Task UpdateNewThing(int id, string externalDataField)
    {
        var thing = await _context.Things.SingleOrDefaultAsync(o => o.Id == id);

            if (thing == null)
            {
                throw new ServiceNotFoundException("Thing " + transactionId + " not found");
            }

            thing.ExternalDataField= externalDataField;

            await _context.SaveChangesAsync();
    }
}

But I get an InvalidOperationException in UpdateNewThing var thing = await _context.Things.SingleOrDefaultAsync(o => o.Id == id);

System.InvalidOperationException: The connection was not closed. The connection's current state is connecting.

It seems like I have to give up either injecting the context, async/await or use something like a contextfactory; unless anyone can spot something simple I have missed that would let me continue with this design.

like image 668
PMC Avatar asked Oct 30 '22 13:10

PMC


1 Answers

Your code looks fine in a single-threaded context. However, DbContext is not thread safe, and I suspect what is happening is you're executing CreateNewThing() on one thread, and the task scheduler is in this case executing UpdateNewThing() on a different thread.

Either way, a better metaphor is to use a context factory, which you inject into your IMyService in this case, and then for every IMyService method you create a new MyEntities context in a using() block.

DbContext's are cheap to create and this is how they are intended to be used; long-lived contexts are almost always incorrect usage.

Edit 1 - example context factory as requested. I tend to implement a generic factory that can create multiple contexts, but that's probably moving outside the scope of this question.

public interface IMyEntitiesFactory
{
    MyEntities Create();
}

public class MyEntitiesFactory : IMyEntitiesFactory
{
    MyEntities IMyEntitiesFactory.Create()
    {
        return new MyEntities();
    }
}

// For use with unit tests; e.g. pass a mock object to the constructor.
public class TestMyEntitiesFactory : IMyEntitiesFactory
{
    private readonly MyEntities _value;

    public TestMyEntitiesFactory(MyEntities value)
    {
        _value = value;
    }

    MyEntities IMyEntitiesFactory.Create()
    {
        return _value;
    }
}
like image 108
sellotape Avatar answered Nov 15 '22 06:11

sellotape