简体   繁体   English

是否可以在 EF Core Include 查询中加入不相关的表

[英]Is it possible to join an unrelated table in an EF Core Include query

I have an EF Core code first project and have created the following table我有一个 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 is an abstract class that doesn't map to its own table, but I have three separate models that all inherit from it. ActivityItem是一个抽象的 class ,它不会 map 到它自己的表中,但我有三个独立的模型都继承自它。

//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;}
}

In this particular instance I'm explicitly avoiding the Table-Per-Hierarchy scheme because I need to have ClassA , ClassB , and ClassC as there own unique tables in the database.在这个特定的例子中,我明确避免使用 Table-Per-Hierarchy 方案,因为我需要ClassAClassBClassC ,因为数据库中有自己的唯一表。

Whenever I'm querying an entry from my Activity table I use the Type property to indicate if the Item is either an instance of ClassA , ClassB , or ClassC .每当我从Activity表中查询条目时,我都会使用Type属性来指示ItemClassAClassB还是ClassC的实例。

Is there a way that I can still use the Include method when I'm forming my queries to populate the Item navigation property?当我形成查询以填充Item导航属性时,有没有办法仍然可以使用Include方法?

Current Solution当前解决方案

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);

Desired Solution所需的解决方案

var activity = _context.Activity.Include(p => p.Item).Where(...).FirstOrDefault();

I know EF Core will write Join queries on the related table when you use the Include method, but in my case there is no related table since ActivityItem isn't represented in the database.我知道当您使用Include方法时,EF Core 会在相关表上编写联接查询,但在我的情况下,由于数据库中没有表示ActivityItem ,因此没有相关表。 Is there a way to explicitly specify which table to Join based on my custom Type field without resorting to the Table-Per-Hierarchy scheme?有没有一种方法可以根据我的自定义Type字段明确指定要加入的表,而无需使用 Table-Per-Hierarchy 方案?

So you have a table of Activities, and a predicate that filters zero or more Activities from this table.因此,您有一个活动表,以及一个从该表中过滤零个或多个活动的谓词。 You are only interested in the first Activity that passes the filter.您只对通过过滤器的第一个 Activity 感兴趣。 There might be no Activity at all that might pass the filter, hence the OrDefault.可能根本没有可能通过过滤器的活动,因此是 OrDefault。

This fetched Activity has a (string?) property Type.这个获取的 Activity 有一个(字符串?)属性类型。 The value of this property depicts whether you should look in table ClassA / ClassB / ClassC.此属性的值描述了您是否应该查看表 ClassA/ClassB/ClassC。

The fetched Activity also has a property ActivityId.获取到的 Activity 还有一个属性 ActivityId。 You want the first (or default) ActivityItem (ClassA / ClassB / ClassC), that has an Id equal to this ActivityId.您想要第一个(或默认)ActivityItem(ClassA / ClassB / ClassC),它的 Id 等于此 ActivityId。

Your design makes that property ActivityId is not a proper foreign key to the ActivityItem that this Activity belongs to.您的设计使该属性ActivityId不是此Activity所属的ActivityItem的正确外键。 In fact your foreign key is a combination of [Type, ActivityId] .实际上,您的外键是[Type, ActivityId]的组合。

So what you need, is a method that concatenates all your ClassA / ClassB / ClassC into one sequence, remembering the Type .因此,您需要一种将所有ClassA / ClassB / ClassC成一个序列的方法,记住Type After that you can join on [Type, Id] .之后,您可以加入[Type, Id]

If you have to do this only once, you can use the following.如果您只需要这样做一次,则可以使用以下方法。 If you have to do this to solve several problems, consider creating extension methods.如果您必须这样做来解决几个问题,请考虑创建扩展方法。

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,
    }));

Now you can just do an inner join:现在你可以做一个内部连接:

// 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();

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM