【问题标题】:Is it possible to join an unrelated table in an EF Core Include query是否可以在 EF Core Include 查询中加入不相关的表
【发布时间】:2020-11-28 19:22:49
【问题描述】:

我有一个 EF Core 代码优先项目并创建了下表

[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 方案,因为我需要 ClassAClassBClassC,因为数据库中有自己的唯一表。

每当我从Activity 表中查询条目时,我都会使用Type 属性来指示Item 是否是ClassAClassBClassC 的实例。

当我形成查询以填充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 字段明确指定要加入的表,而不使用 Table-Per-Hierarchy 方案?

【问题讨论】:

  • 如果你想在一个查询中获取数据,你必须对每个表进行左连接,这有点违背了ActivityIdType 的目的。相反,您可以为每个可使用相应导航属性为空的表设置一个 FK,然后您将能够将它们全部包含在内,并且您必须找出不为空的类型。基本上,关系数据库不能完全模拟继承。
  • 或者,您可能需要考虑一种解决方案,其中您有一个活动表,其中包含确定每个活动之间存在差异的信息。

标签: c# linq entity-framework-core ef-core-3.1


【解决方案1】:

因此,您有一个活动表,以及一个从该表中过滤零个或多个活动的谓词。您只对通过过滤器的第一个 Activity 感兴趣。可能根本没有可能通过过滤器的 Activity,因此是 OrDefault。

这个获取的活动有一个(字符串?)属性类型。此属性的值描述了您是否应该查看表 ClassA / ClassB / ClassC。

获取的Activity还有一个属性ActivityId。您想要第一个(或默认)ActivityItem(ClassA / ClassB / ClassC),它的 Id 等于此 ActivityId。

您的设计使属性ActivityId 不是此Activity 所属的ActivityItem 的正确外键。实际上,您的外键是[Type, ActivityId] 的组合。

因此,您需要一种将所有ClassA / ClassB / ClassC 连接成一个序列的方法,记住Type。之后你可以加入[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();

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-12-27
    • 1970-01-01
    • 2017-09-08
    • 2019-08-05
    相关资源
    最近更新 更多