我有一个EF Core code first项目,并创建了下表
[Table("Activity")]
public class Activity : BaseModel
{
[Key]
public int Id { get; set; }
public int ActivityId { get; set; }
public TransactionType Type { get; set; }
public virtual ActivityItem Item { get; set; }
}ActivityItem是一个抽象类,不映射到它自己的表,但是我有三个独立的模型,它们都继承自它。
//Not its own table in the database
public abstract class ActivityItem
{}
[Table("ClassA")]
public class ClassA : ActivityItem
{
public int Id {get; set;}
}
[Table("ClassB")]
public class ClassB : ActivityItem
{
public int Id {get; set;}
}
[Table("ClassC")]
public class ClassC : ActivityItem
{
public int Id {get; set;}
}在这个特定的实例中,我显式地避免了每个层次结构的Table-Per-Hierarchy方案,因为我需要让ClassA、ClassB和ClassC在数据库中拥有自己的唯一表。
每当查询Activity表中的条目时,我都会使用Type属性来指示Item是ClassA、ClassB还是ClassC的实例。
在形成查询以填充Item导航属性时,有没有办法仍然使用Include方法?
当前解决方案
var activity = _context.Activity.Where(...).FirstOrDefault();
if (activity.Type == "ClassA")
activity.Item = _context.ClassA.First(p => p.Id == activity.ActivityId);
else if (activity.Type == "ClassB")
activity.Item = _context.ClassB.First(p => p.Id == activity.ActivityId);
else if (activity.Type == "ClassC")
activity.Item = _context.ClassC.First(p => p.Id == activity.ActivityId);所需解决方案
var activity = _context.Activity.Include(p => p.Item).Where(...).FirstOrDefault();我知道当您使用Include方法时,EF Core将在相关表上编写连接查询,但在我的例子中没有相关表,因为数据库中没有表示ActivityItem。有没有一种方法可以显式地指定根据我的自定义Type字段连接哪个表,而不是求助于每个层次结构的表方案?
发布于 2020-08-10 23:20:51
因此,您有一个包含活动的表,以及一个从该表中过滤零个或多个活动的谓词。您只对第一个通过过滤器的活动感兴趣。可能根本没有任何活动可以通过筛选器,因此是OrDefault。
这个获取的活动有一个(字符串?)属性类型。此属性的值描述您是否应该查看表ClassA / ClassB / ClassC。
获取的活动还具有属性ActivityId。您需要第一个(或默认的) ActivityItem (ClassA / ClassB / ClassC),它的Id等于此ActivityId。
您的设计使属性ActivityId不是此Activity所属的ActivityItem的适当外键。实际上,您的外键是[Type, ActivityId]的组合。
因此,您需要的是一种方法,它可以记住Type,将所有ClassA / ClassB / ClassC连接到一个序列中。之后你就可以加入[Type, Id]了。
如果你只需要这样做一次,你可以使用下面的方法。如果您必须这样做才能解决几个问题,请考虑创建扩展方法。
var activityItems = dbContext.ClassAItems.Cast<ActivityItem>()
.Select(classAItem => new
{
Type = "ClassA",
Data = classAItem,
})
.Concat(dbContext.ClassBItems.Cast<ActivityItem>()
.Select(classBItem => new
{
Type = "ClassB",
Data = classBItem,
}))
.Concat(dbContext.ClassCItems.Cast<ActivityItem>()
.Select(classCItem => new
{
Type = "ClassC",
Data = classCItem,
}));现在你可以只做一个内连接:
// join the filtered activities with the activityitems:
var result = dbContext.Activities.Where(...)
.Join(activityItems,
activity => new // from each activity take [type, activityId]
{
Type = activity.Type
Id = activity.ActivityId,
}
activityItem => new // from each ActivityItems take [Type, Id]
{
Type = activityItem.Type,
Id = activityItem.Data.Id,
}
// when these keys match, use the Activity and the matching ActivityItem to make one new
// well, in this case, you are only interested in the Data of the ActivityItem
(activity, activityItem) => activityItem.Data)
// From the joined items you only want the first:
.FirstOrDefault();https://stackoverflow.com/questions/63319012
复制相似问题