EF中如何通过OfType<T>按类型筛选包含对象并实现特定ThenInclude?
首先先明确你的实体结构:
public class MainObject { List<Base> myList; } public class Base { int MainObjectId; int x; } public class A : Base { int y; } public class B : Base { int z; AnotherObject anotherObject; }
你的需求是:根据MainObject.myList中元素的具体派生类型(A或B),分别加载对应的关联对象——其中B需要加载anotherObject,A不需要额外关联。
你尝试的两种方案为什么失败?
单查询多Include+OfType:
EF Core目前不支持对同一个导航属性(这里是myList)同时使用多个OfType<T>的Include。因为myList是一个统一的Base类型集合,EF Core无法在单个查询中同时处理针对该集合的两种不同类型过滤的关联加载,最终只会生效最后一个Include,或者导致查询生成异常。两个独立查询MainObject:
由于EF Core的上下文跟踪机制,第一次查询加载的MainObject会被上下文缓存。第二次查询同一个Id的MainObject时,EF Core会直接返回缓存中的对象,不会重新执行查询去加载B类型的关联,所以你无法得到包含完整A和B元素的集合。
你已实现的方案说明
你直接查询派生类型的方式是可行的,这种方式绕过了对MainObject导航属性的复杂处理,直接通过外键MainObjectId关联查询,能精准加载各自需要的关联:
var ListOfA = context.BaseObjects .OfType<A>() .Where(a => a.MainObjectId == id) .ToList(); var ListOfB = context.BaseObjects.Include(b => b.anotherObject) .OfType<B>() .Where(b => b.MainObjectId == id) .ToList();
之后你可以把这两个集合合并到MainObject的myList中,这种方式简单直接,也容易维护。
关于你的疑问解答
1. 能否通过单查询实现?
可以的!在EF Core 3.1及以上版本,你可以通过类型转换结合ThenInclude来实现单查询加载:
var result = context.MainObjects .Include(mo => mo.myList) // 先加载整个Base集合 .ThenInclude(b => ((B)b).anotherObject) // 仅对B类型的元素加载anotherObject .Where(mo => mo.Id == paramId) .FirstOrDefault();
这个查询会生成一个SQL语句,一次性加载MainObject、所有myList中的A和B元素,同时为B类型元素加载对应的anotherObject。EF Core会自动处理类型区分,将A和B实例正确填充到myList集合中。
2. 能否通过以MainObject开头的两个查询实现?
也可以,但需要禁用上下文跟踪,避免缓存导致第二次查询不生效,之后再合并集合:
// 禁用跟踪,确保每次查询都去数据库获取数据 var resultForA = context.MainObjects .AsNoTracking() .Include(mo => mo.myList.OfType<A>()) .Where(mo => mo.Id == paramId) .FirstOrDefault(); var resultForB = context.MainObjects .AsNoTracking() .Include(mo => mo.myList.OfType<B>()).ThenInclude(b => b.anotherObject) .Where(mo => mo.Id == paramId) .FirstOrDefault(); // 合并两个结果的myList if (resultForA != null && resultForB != null) { resultForA.myList.AddRange(resultForB.myList); }
不过这种方式需要两次数据库查询,且手动合并集合,效率和简洁性都不如你已实现的方案或单查询方案。
内容的提问来源于stack exchange,提问作者G.Dealmeida

