我在EF Code First中有一个用户类,它包含很多属性,每个用户都有一个"联系人"集合,这些"联系人"是总用户群体的一个子集。另一个集合"ContactOfOthers"只是反向显示谁将该用户作为联系人,因为这是一种多对多的关系。
public class User {
[Key]
public string UserName { get; set; }
// Lots of other properties not relevant to this question...
[NotMapped]
public bool IsMyContact { get; set; }
public virtual ICollection<User> Contacts { get; set; }
public virtual ICollection<User> ContactOfOthers { get; set; }
}
我引入了一个未映射(未映射到DB)的属性,名为IsMyContact。这适用于用户查询一群用户时,我需要在视图中显示哪些用户已经在他们的联系人列表中。因此,如果用户是其"联系人"集合的一部分,则此属性应为true。它不应该保存到DB中,因为对于同一个用户,它可能不同,这取决于执行查询的用户。
有没有一种很好的方法可以在上下文中的查询中做到这一点?当然,这可能是通过执行两个查询,然后迭代主查询,寻找与用户的Contacts集合匹配的内容来强制执行的,但我想知道是否有更优雅的方法可以从一个查询中做到这一点,将运行时计算的列投影到该属性上?
我不知道如何在查询中直接填充User
中的IsMyContact
属性。但另一种方法可能是引入一个ViewModel,它封装了User
,此外还具有IsMyContact
标志:
public class UserViewModel
{
public bool IsMyContact { get; set; }
public User User { get; set; }
}
(类User
将不再具有IsMyContact
标志。)
然后,当您运行查询时,您可以投影到这种类型:
string me = "That's me"; // name of the user who is selecting
List<UserViewModel> list = context.Users
.Where(u => ...some filter...)
.Select(u => new UserViewModel
{
IsMyContact = u.ContactOfOthers.Any(c => c.UserName == me),
User = u
})
.ToList();
这样做的好处是:您只需要一次往返,并且不必加载整个Contacts
集合来确定IsMyContactFlag
(但如果您愿意,您可以)。
缺点:您需要这个额外的ViewModel类型。
这样做是可能的,但这不是一种"好方法",因为您无法返回User
类型的实例。您必须编写自定义linq到实体查询,并且必须解决两个问题:
- 不能将linq中的映射类型投影到实体
- 无法访问linq到实体中的未映射特性
因此,我的高级未经测试的想法是:
var query = from u in ctx.Users
where u.Id != id // don't include current user - you can add other condition
join c in ctx.Users
.Where(x => x.Id == id) // current user
.SelectMany(x => x.Contacts)
on u.Id equals c.Id into leftJoin
from y in leftJoin.DefaultIfEmpty()
select new
{
UserName = u.UserName,
IsMyContact = y != null
};
这应该是一个查询,它将加载成对的UserName
和用户是否联系的信息。如果您想要User
实例,则必须执行以下操作:
var users = query.AsEnumerable()
.Select(new User
{
// Project to list in linq-to-objects
});