entity framework5线程敏捷性

抛出EntityFramework代码内部的NullReferenceException(EF bug?),但我的问题是关于Entity Framework(v5)和WebAPI异步控制器动作。

一个repro很难在这里重新创建,但代码本质上做了以下事情:

public class AController : ApiController { private IUow _uow; //among other things, a DbContext // DI ctor public AController(IUow uow) { _uow = uow; } [HttpPost] public async Task 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 } } 

Commit()之前,就在DbContext.SaveChanges()之前,我们遍历所有DbChangeTracker.Entries()来设置一些公共属性。 但是Entries()在单个循环之前发生错误,在System.Data.Entity.Infrastructure.DbChangeTracker.Entries()内部有一个NullReferenceException

下面是调用堆栈。 这是所有的框架代码,感觉就像一个bug,但我的问题是,如果上面的async / await允许使用中间的DbContext调用。 在任何时候我们都不使用multithreading – async / await,因为我们可以使用async / await工具执行一些IO操作(一些Httpclient下载+一些异步磁盘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 

await之后有一个隐式线程切换,由IO完成引起。 AFAIK,EF5可能无法处理此问题,因为它使用线程本地存储。

在这种情况下,OTOH,EF6.x(特别是最新版本)应该可以正常工作。

相关: 如何在ASP.NET Web API中使用非线程安全的异步/等待API和模式?

更新以解决评论:

因为异步/等待基础设施应该注意并流动ExecutionContext(线程本地存储和其他“上下文”)。 我问这样我可以做出有根据的改变并保留异步/等待实现,以处理打破EF的任何特定事情。

EF5源代码不是开源的(与EF6不同),所以我不能100%肯定,但我怀疑EF5显式使用TLS(即ThreadStaticThreadLocal )。 ExecutionContext无法自动传递所有 TLS属性。 对现有代码来说,这将是一个巨大的突破性变化和安全威胁(更不用说技术上甚至可能无法实现这一点)。

ExecutionContext捕获并流动非常特定的线程属性子集 。 此子集未记录,但您可以在此处了解有关它的更多信息。

它是负责任的特定类实现,使其静态属性跨多个线程流动,有CallContext.LogicalSetData / CallContext.LogicalGetData 。 我相信这就是EF6在幕后所做的事情。