所以你有一个MailItems 的表和一个Attachments 的表。在MailItems 和Attachments 之间存在直接的一对多关系:每个MailItem 都有零个或多个Attachments,并且每个Attachment 恰好属于一个MailItem,即MailItem外键引用。
很多时候,人们想要“带有附件的邮件项”,他们不想要左外连接:
- 带有附件 A、C 的邮件项 1
- 带有附件 B、D、F 的 MailItem 2
- 没有附件的邮件项 3
- 带有附件 E 的邮件项 4
另一方面,您似乎更喜欢以下结果:
MailItem Attachment
1 A
2 B
1 C
3 <null>
4 E
2 D
2 F
很少需要这种解决方案。这就是为什么没有标准的 LINQ 方法。
要获取带有附件的 MailItem,请使用 GroupJoin。如果您想要左外连接,请在 GroupJoin 之后使用 SelectMany。
带有附件的邮件项
var mailItemsWithTheirAttacments = dbContext.MailItems.GroupJoin(
dbContext.Attachments, // GroupJoin MailItems with Attachments
mailitem => mailItem.MailItemId, // from every MailItem take the primary key
attachment => attachment.MailItemId, // from every Attachment take the foreign key
// parameter resultSelector: for every MainItem and all its Attachments
// make one new object
(mailItem, attachmentsOfThisMailItem) => new
{
// Select the MailItem properties that you plan to use
Id = mailItem.Id,
Sender = mailItem.Sender,
...
Attachments = attachmentsOfThisMailItem.Select(attachment => new
{
// Select only the Attachments properties that you plan to use:
Id = attachment.Id,
Format = attachment.Format,
...
// not needed, you already know the value:
// MailItemId = attachment.MailItemId,
})
.ToList(),
})
左外连接
var result = dbContext.MailItems.GroupJoin(dbContext.Attachments,
mailitem => mailItem.MailItemId,
attachment => attachment.MailItemId,
(mailItem, attachmentsOfThisMailItem) => new
{
MailItem = mailItem,
Attachments = attachmentsOfThisMailItem,
})
// make it a left outer join, using SelectMany
.SelectMany(groupJoinResult => groupJoinResult.Attachments,
(groupJoinResult, attachment) => new
{
MailId = groupJoinResult.MailItem.Id,
Sender = groupJoinResult.MailItem.Sender,
...
AttachmentId = attachment.Id,
Format = attachment.Format,
...
});
使用虚拟 ICollections
如果您遵循实体框架约定,您将拥有类似于以下的类:
class MailItem
{
public int Id {get; set; }
...
// every MailItem has zero or more Attachments (one-to-many)
public virtual ICollection<Attachment> Attachments {get; set;}
}
class Attachment
{
public int Id {get; set; }
...
// every Attachment belongs to exactly one MailItem, using foreign key:
public int MailItemId {get; set;}
public virtual MailItem MailItem {get; set;}
}
您可以使用其他标识符,但重要的部分是虚拟属性。
在实体框架中,表的列由非虚拟属性表示。虚拟属性表示表之间的关系(一对多,多对多,...)
外键是附件表中的一列。因此属性 Attachment.MailItemId 不是虚拟的。属性 Attachment.MailItem 表示关系,因此它是虚拟的。
如果您已将关系定义为属性,则可以使用它们为您执行 GroupJoin 或 Left Outer Join:
var mailItemsWithTheirAttachments = dbContext.MailItems.Select(mailItem => new
{
Id = mailItem.Id,
Sender = mailItem.Sender,
...
Attachments = mailItem.Attachments
.Where(attachment => ....) // only if you don't want all Attachments
.Select(attachment => new
{
Id = attachment.Id,
...
})
.ToList(),
});
实体框架知道关系,并会为你做正确的(组)加入。
恕我直言,这看起来比 GroupJoin 更自然,无论后面是否有 selectMany