Вопрос

A NullReferenceException deep inside EntityFramework code is thrown (EF bug?), but my question is about Entity Framework (v5) and WebAPI asynchronous controller action.

A repro would be hard to recreate here, but the code in essence does the following:

public class AController : ApiController
{
    private IUow _uow; //among other things, a DbContext 
    // DI ctor
    public AController(IUow uow)
    {
        _uow = uow; 
    }

    [HttpPost]
    public async Task<HttpResponseMessage> Post(Model model)
    {
        Entity e = _uow.Entity.GetById(model.id);
        await IO_Ops_Async(model); 
        new ModelAdapter().UpdateEntity(entity, model);
        _uow.Commit(); <- EXCEPTION THROWN DURING THIS CALL - see below
        ... // do something with the return result
    }
}

Inside Commit(), just before DbContext.SaveChanges(), we loop through all DbChangeTracker.Entries() to set some common properties. But it is the Entries() that errors before a single loop with a NullReferenceException deep inside System.Data.Entity.Infrastructure.DbChangeTracker.Entries()

Below is the call stack. It's all Framework code and it feels like a bug, but my question really is if the above async/await use in-between DbContext calls is permitted. At no point we multi-thread - async/await is only used because there are a few IO operations that we can execute using the async/await facility (a couple of Httpclient downloads + some async disk I/O).

System.NullReferenceException: Object reference not set to an instance of an object.\r\n   
at System.Data.Objects.DataClasses.RelatedEnd.IncludeEntity(IEntityWrapper wrappedEntity, Boolean addRelationshipAsUnchanged, Boolean doAttach)\r\n   
at System.Data.Objects.DataClasses.EntityReference`1.Include(Boolean addRelationshipAsUnchanged, Boolean doAttach)\r\n   
at System.Data.Objects.DataClasses.RelatedEnd.WalkObjectGraphToIncludeAllRelatedEntities(IEntityWrapper wrappedEntity, Boolean addRelationshipAsUnchanged, Boolean doAttach)\r\n   
at System.Data.Objects.DataClasses.RelatedEnd.IncludeEntity(IEntityWrapper wrappedEntity, Boolean addRelationshipAsUnchanged, Boolean doAttach)\r\n   
at System.Data.Objects.DataClasses.EntityCollection`1.Include(Boolean addRelationshipAsUnchanged, Boolean doAttach)\r\n   
at System.Data.Objects.DataClasses.RelatedEnd.WalkObjectGraphToIncludeAllRelatedEntities(IEntityWrapper wrappedEntity, Boolean addRelationshipAsUnchanged, Boolean doAttach)\r\n   
at System.Data.Objects.DataClasses.RelatedEnd.IncludeEntity(IEntityWrapper wrappedEntity, Boolean addRelationshipAsUnchanged, Boolean doAttach)\r\n   
at System.Data.Objects.DataClasses.EntityReference`1.Include(Boolean addRelationshipAsUnchanged, Boolean doAttach)\r\n   
at System.Data.Objects.DataClasses.RelatedEnd.WalkObjectGraphToIncludeAllRelatedEntities(IEntityWrapper wrappedEntity, Boolean addRelationshipAsUnchanged, Boolean doAttach)\r\n   
at System.Data.Objects.DataClasses.RelatedEnd.Add(IEntityWrapper wrappedTarget, Boolean applyConstraints, Boolean addRelationshipAsUnchanged, Boolean relationshipAlreadyExists, Boolean allowModifyingOtherEndOfRelationship, Boolean forceForeignKeyChanges)\r\n   
at System.Data.Objects.ObjectStateManager.PerformAdd(IEntityWrapper wrappedOwner, RelatedEnd relatedEnd, IEntityWrapper entityToAdd, Boolean isForeignKeyChange)\r\n   
at System.Data.Objects.ObjectStateManager.PerformAdd(IList`1 entries)\r\n   
at System.Data.Objects.ObjectStateManager.DetectChanges()\r\n   
at System.Data.Entity.Internal.InternalContext.GetStateEntries(Func`2 predicate)\r\n   
at System.Data.Entity.Infrastructure.DbChangeTracker.Entries()\r\n
Это было полезно?

Решение

There's an implicit thread switch after await, caused by the IO completion. AFAIK, EF5 may not be able to handle this as it uses thread local storage.

OTOH, EF6.x (especially the latest release) should be working fine in this case.

Related: How to use non-thread-safe async/await APIs and patterns with ASP.NET Web API?

Updated to address the comment:

Because the async/await infrastructure supposedly take care and flows the ExecutionContext (thread-local storage among other "contexts"). I am asking so that I can make educated changes and preserve the async/await implementation taking care of whatever specific thing is breaking EF.

The EF5 source code is not open-sourced (unlike with EF6), so I cannot be 100% sure, but I suspect EF5 explicitly uses TLS (i.e., ThreadStatic or ThreadLocal<T>). There is no way all TLS properties could be automatically flowed by ExecutionContext. It would be a huge breaking change and security threat to the existing code (let alone it might not even be technically possible to implement this).

ExecutionContext captures and flows a very specific subset of thread properties. This subset is undocumented, but you can learn more about it here.

It's a responsibly of the specific class implementation to flow its static properties across multiple threads, there's CallContext.LogicalSetData/CallContext.LogicalGetData for that. I believe this is what EF6 does under the hood.

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top