使用CallByName获取DbSet时触发MissingMemberException的原因及相关问题
这个问题的核心不是CallByName本身的问题,也不是EF接口的缺失,而是编译时类型信息丢失导致LINQ扩展方法无法被正确解析。让我一步步拆解:
为什么第一种方式能正常运行?
当你直接写db.stations时,编译器明确知道这个属性的类型是DbSet(Of station),而DbSet(Of T)实现了IQueryable(Of T)接口。LINQ的Where方法是System.Linq.Queryable类里的扩展方法,编译器会自动把myDbSet1.Where(...)转换成静态方法调用:Queryable.Where(myDbSet1, Function(record) record.id = "677010")——这个转换能完成的前提是,编译器能确认myDbSet1是IQueryable(Of station)类型。
为什么第二种方式会报错?
CallByName(db, tableName, CallType.Get)返回的是Object类型(VB默认的返回类型)。虽然在运行时这个对象确实是DbSet(Of station),但编译时编译器只知道它是Object:
Object本身没有Where实例方法- 扩展方法的调用依赖编译器在编译时找到匹配的静态方法,而
Object并没有实现IQueryable(Of T)(只有运行时的实际类型才实现),所以编译器无法识别可以调用Queryable.Where
你看到TypeOf myDbSet2 Is IQueryable返回True,是因为IQueryable是非泛型的接口,但LINQ的Where需要的是泛型的IQueryable(Of T),非泛型的IQueryable并没有对应的Where扩展方法。
解决方案
你需要把CallByName返回的Object转换成正确的泛型类型,让编译器能识别它是IQueryable(Of station),这样就能调用LINQ扩展方法了:
方案1:直接转换类型
如果提前知道实体类型,可以直接转换:
Dim tableName As String = "stations" Dim myDbSet2 = DirectCast(CallByName(db, tableName, CallType.Get), DbSet(Of station)) Dim query2 = myDbSet2.Where(Function(record) record.id = "677010")
方案2:使用EF的Set方法(更推荐)
其实不需要用CallByName,EF的DbContext本身提供了泛型的Set方法来获取对应实体的DbSet:
' 如果知道实体类型 Dim myDbSet2 = db.Set(Of station)() Dim query2 = myDbSet2.Where(Function(record) record.id = "677010")
方案3:动态表名+反射(如果表名完全动态)
如果表名是完全动态的,不知道对应的实体类型,可以用反射调用泛型的Set方法:
Dim tableName As String = "stations" ' 先根据表名找到对应的实体类型(假设实体类名和表名对应,比如station对应stations表) Dim entityType = Type.GetType($"YourNamespace.{tableName.TrimEnd("s"c)}") ' 反射调用DbContext的Set<T>方法 Dim setMethod = GetType(DbContext).GetMethod("Set").MakeGenericMethod(entityType) Dim myDbSet2 = setMethod.Invoke(db, Nothing) ' 如果需要执行LINQ查询,可以用动态类型或者继续反射调用扩展方法 ' 或者转换为IQueryable后用表达式树构建查询
总结
- 问题本质是编译时类型信息缺失,导致LINQ扩展方法无法被编译器解析
CallByName返回的Object类型无法触发扩展方法的语法糖转换- 解决的关键是让编译器获取到正确的泛型
IQueryable(Of T)类型信息
内容的提问来源于stack exchange,提问作者isedwards

