Abp vNext 自定义 Ef Core 仓储引发异常
问题
在使用自定义 ef core 仓储和 abp vnext 注入的默认仓储时,通过两个 repository 进行 join 操作,提示 cannot use multiple dbcontext instances within a single query execution. ensure the query uses a single context instance.
。这个异常信息翻译成中文的大概意思就是,你不能使用两个 dbcontext 里面的 dbset 进行 join 查询。
如果将自定义仓储改为 irepository<tentity,tkey>
进行注入,是可以与 _courserepostory
进行关联查询的。
我在 xxxentityframeworkcoremodule
的配置,以及自定义仓储 efcorestudentrepository
代码如下。
xxxentityframeworkcoremodule
代码:
public class xxxentityframeworkcoremodule : abpmodule { public override void configureservices(serviceconfigurationcontext context) { context.services.addabpdbcontext<xxxdbcontext>(op => { op.adddefaultrepositories(); }); configure<abpdbcontextoptions>(op => op.usepostgresql()); } }
efcorestudentrepository
代码:
public class efcorestudentrepository : efcorerepository<ixxxdbcontext, student, long>, istudentrepository { public efcorestudentrepository(idbcontextprovider<ixxxdbcontext> dbcontextprovider) : base(dbcontextprovider) { } public task<int> getcountwithstudentlidasync(long studentid) { return dbset.countasync(x=>x.studentid == studentid); } }
原因
原因在异常信息已经说得十分清楚了,这里我们需要了解两个问题。
- 什么原因导致两个仓储内部的 dbcontext 不一致?
- 为什么 abp vnext 自己实现的仓储能够进行关联查询呢?
首先我们得知道,仓储内部的 dbcontext
是怎么获取的。我们的自定义仓储都会继承 efcorerepository
,而这个仓储是实现了 iquerable<t>
接口的,最终它会通过一个 idbcontextprovider<tdbcontext>
获得一个可用的 dbcontext
。
public class efcorerepository<tdbcontext, tentity> : repositorybase<tentity>, iefcorerepository<tentity> where tdbcontext : iefcoredbcontext where tentity : class, ientity { public virtual dbset<tentity> dbset => dbcontext.set<tentity>(); dbcontext iefcorerepository<tentity>.dbcontext => dbcontext.as<dbcontext>(); // 这里可以看到,是通过 idbcontextprovider 来获得 dbcontext 的。 protected virtual tdbcontext dbcontext => _dbcontextprovider.getdbcontext(); protected virtual abpentityoptions<tentity> abpentityoptions => _entityoptionslazy.value; private readonly idbcontextprovider<tdbcontext> _dbcontextprovider; private readonly lazy<abpentityoptions<tentity>> _entityoptionslazy; // ... 其他代码。 }
下面就是 idbcontextprovider<tdbcontext>
内部的核心代码:
public class unitofworkdbcontextprovider<tdbcontext> : idbcontextprovider<tdbcontext> where tdbcontext : iefcoredbcontext { private readonly iunitofworkmanager _unitofworkmanager; private readonly iconnectionstringresolver _connectionstringresolver; // ... 其他代码。 public tdbcontext getdbcontext() { var unitofwork = _unitofworkmanager.current; if (unitofwork == null) { throw new abpexception("a dbcontext can only be created inside a unit of work!"); } var connectionstringname = connectionstringnameattribute.getconnstringname<tdbcontext>(); var connectionstring = _connectionstringresolver.resolve(connectionstringname); // 会构造一个 key,而这个 key 刚好是泛型类型的 fullname。 var dbcontextkey = $"{typeof(tdbcontext).fullname}_{connectionstring}"; // 内部是从一个字典当中,根据 dbcontextkey 获取 dbcontext。如果不存在的话则调用工厂方法创建一个新的 dbcontext。 var databaseapi = unitofwork.getoradddatabaseapi( dbcontextkey, () => new efcoredatabaseapi<tdbcontext>( createdbcontext(unitofwork, connectionstringname, connectionstring) )); return ((efcoredatabaseapi<tdbcontext>)databaseapi).dbcontext; } // ... 其他代码。 }
通过以上代码我们就可以知道,abp vnext 在仓储的内部是通过 idbcontextprovider<tdbcontext>
中的 tdbcontext
泛型,来确定是否构建一个新的 dbcontext
对象。
不论是 abp vnext 针对 irepository<tentity,tkey>
,还是我们自己实现的自定义仓储,它们最终的实现都是基于 efcorerepository<tdbcontext,tentity,tkey>
的。而我们 idbcontextprovider<tdbcontext>
的泛型,也是这个仓储基类提供的,后者的 tdbcontext
就是前者的泛型参数。
所以当我们在模块添加 dbcontext
的过城中,只要调用了 adddefaultrepositories()
方法,abp vnext 就会遍历你提供的 tdbcontext
所定义的实体,然后为这些实体建立默认的仓储。
在注入仓储的时候,找到了获得默认仓储实现类型的方法,可以看到这里它使用的是 defaultrepositorydbcontexttype
作为默认的 tdbcontext
类型。
protected virtual type getdefaultrepositoryimplementationtype(type entitytype) { var primarykeytype = entityhelper.findprimarykeytype(entitytype); // 重点在于构造仓储类型时,传递的 options.defaultrepositorydbcontexttype 参数,这个参数就是后面 efcorerepository 的 tdbcontext 泛型。 if (primarykeytype == null) { return options.specifieddefaultrepositorytypes ? options.defaultrepositoryimplementationtypewithoutkey.makegenerictype(entitytype) : getrepositorytype(options.defaultrepositorydbcontexttype, entitytype); } return options.specifieddefaultrepositorytypes ? options.defaultrepositoryimplementationtype.makegenerictype(entitytype, primarykeytype) : getrepositorytype(options.defaultrepositorydbcontexttype, entitytype, primarykeytype); }
最后我发现这个就是在模块调用 addabpcontext<tdbcontext>
所提供的泛型参数。
public abstract class abpcommondbcontextregistrationoptions : iabpcommondbcontextregistrationoptionsbuilder { // ... 其他代码 protected abpcommondbcontextregistrationoptions(type originaldbcontexttype, iservicecollection services) { originaldbcontexttype = originaldbcontexttype; services = services; defaultrepositorydbcontexttype = originaldbcontexttype; customrepositories = new dictionary<type, type>(); replaceddbcontexttypes = new list<type>(); } // ... 其他代码 } public class abpdbcontextregistrationoptions : abpcommondbcontextregistrationoptions, iabpdbcontextregistrationoptionsbuilder { public dictionary<type, object> abpentityoptions { get; } public abpdbcontextregistrationoptions(type originaldbcontexttype, iservicecollection services) : base(originaldbcontexttype, services) // 之类调用的就是上面的构造方法。 { abpentityoptions = new dictionary<type, object>(); } } public static class abpefcoreservicecollectionextensions { public static iservicecollection addabpdbcontext<tdbcontext>( this iservicecollection services, action<iabpdbcontextregistrationoptionsbuilder> optionsbuilder = null) where tdbcontext : abpdbcontext<tdbcontext> { // ... 其他代码。 var options = new abpdbcontextregistrationoptions(typeof(tdbcontext), services); // ... 其他代码。 return services; } }
所以,我们的默认仓储的 dbcontextkey
是 xxxdbcontext
,我们的自定义仓储继承 efcorerepository<ixxxdbcontext,tentity,tkey>
,所以它的 dbcontextkey
就是 ixxxdbcontext
。所以自定义仓储获取到的 dbcontext
就与自定义仓储的不一致了,从而提示上述异常。
解决
找到自定自定义仓储的定义,修改它 efcorereposiotry<tdbcontext,tentity,tkey>
的 tdbcontext
泛型参数,变更为 xxxdbcontext
即可。
public class efcorestudentrepository : efcorerepository<xxxdbcontext, student, long>, istudentrepository { public efcorestudentrepository(idbcontextprovider<xxxdbcontext> dbcontextprovider) : base(dbcontextprovider) { } public task<int> getcountwithstudentlidasync(long studentid) { return dbset.countasync(x=>x.studentid == studentid); } }
上一篇: 秋葵是凉性食物吗?秋葵吃多了会怎样?
下一篇: 洋槐蜜蜂蜜的真假,几种方法教大家如何鉴别