EF Core如何选择具有多对多关系的实体

时间:2018-10-01 04:58:32

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

我有这样的表结构

  1. 用户
  2. user_profiles
  3. 个人资料

说明:

用户有许多用户个人资料,user_profile表将用户和个人资料表连接在一起(用户与个人资料表之间存在多对多关系)

user>一对多> user_profiles>一对一> profiles

user>许多user_profiles>一个配置文件

问题:

如何使用linq选择具有个人资料的用户。

样本:

var user=cbContext.user.include("user_profiles").include("profiles").Where(predicate).FirstOrDefault();

3 个答案:

答案 0 :(得分:5)

找到答案

dbContext.Users
  .Include(user => user.UserProfiles)
  .ThenInclude(userProfiles => userProfiles.Profile) 
  .Where(predicate)
  .FirstOrDefault();

答案 1 :(得分:3)

如果您具有完整的实体框架,则多对多的设计类似于:

class User
{
     public int Id {get; set;}

     // every User has zero or more Profiles (many-to-many)
     public virtual ICollection<Profile> Profiles {get; set;}

     ...
}
class Profile
{
     public int Id {get; set;}

     // every Profile belongs to zero or more Users (many-to-many)
     public virtual ICollection<User> Userss {get; set;}

     ...
}

如果您的班级设计如下,并且希望“用户……使用其个人资料”,则可以使用集合并选择您打算使用的属性:

using (var dbContext = new MyDbContext(...))
{
    var requestedUsers = dbContext.Users
        .Where(user => ...)                      // only if you don't want all Users
        .Select(user => new
        {    // Select only the properties you plan to use:
             Id = user.Id,
             Name = user.Name,
             ...
             Profiles = user.Profiles
                 .Where(profile => ...)         // only if you don't want all profiles
                 .Select(profile => new
                 {
                      Name = profile.Name,
                      ...
                 })
                 .ToList(),
        })

数据库查询的最慢部分之一是将所选数据从数据库管理系统传输到您的流程。因此,将传输的数据限制为实际打算使用的数据是明智的。

Include将选择所包含对象的所有属性,包括主键和外键。 Include a Collection将选择完整的收藏集,即使您只打算使用其中的几个。

  

建议:仅当您计划更改获取的数据时才使用Include。使用Select更快。 Select仅是您实际计划使用的属性

如果无法使用ICollection,请使用(组)加入

从某些人那里我了解到,当您使用EF核心时,您不能使用virtual ICollections。在这种情况下,您必须自己执行GroupJoin

dbContext.Users
    .Where(user => ...)
    .GroupJoin(dbContext.UserProfiles,         // GroupJoin the users with the UserProfiles
        user => user.Id                        // from every user take the Id
        userProfile => userProfile.UserId,     // from every userProfile take the UserId
        (user, userProfiles) =>  new           // when thay match,
        {                                      // take the user and its matching UserProfiles
            UserId = user.Id,                  // again: select only properties you plan to use
            UserName = user.Name,
            ...

            // for the Profiles, do a new Join with the Profiles
            Profiles = userProfiles.Join(dbContext.Profiles, // join with Profiles
               userProfile => userProfile => profileId       // from the userProfile take profileId
               profile => profile.Id,                        // from the Profile take the Id
               (userProfile, profile) => new                 // when they match, make an object
               {   // again: use only properties you plan to use
                   ProfileId = profile.Id,
                   ProfileName = profile.Name,
                   ...
               })
               .ToList(),
        });

注意:如果没有任何个人资料,您将不会获得用户!
这是内部联接。

如果您还希望用户没有个人资料,请使用Left-Outer-GroupJoin as described here on Stackoverflow向下滚动以找到排名最高的答案,这比所选答案要好得多

答案 2 :(得分:1)

除了您自己对lambda的回答和ThenInclude的使用(这是具有n到n关系的简单查询的首选版本)之外,您还可以使用字符串来指定包含内容。

您只需要这样写用点.分隔的属性的“路径”:

dbContext.Users
  .Include("UserProfiles.Profile")
  .Where(predicate)
  .FirstOrDefault();

它适用于1对1、1对许多以及许多对许多相同的关系。

当您将实体包含得很深(但是却丢失了编译时检查)时,这很有用