【问题标题】:How to query IQueryable with Include - ThenInclude?如何使用 Include - ThenInclude 查询 IQueryable?
【发布时间】:2021-02-27 19:03:35
【问题描述】:

在 .NET Core 2.2 中,我坚持过滤 IQueryable 构建为:

_context.Ports.Include(p => p.VesselsPorts)
              .ThenInclude(p => p.Arrival)
              .Include(p => p.VesselsPorts)
              .ThenInclude(p => p.Departure)
              .OrderBy(p => p.PortLocode);

在多对多关系中。实体模型如:

public class PortModel
{
        [Key]
        public string PortLocode { get; set; }
        public double? MaxKnownLOA { get; set; }
        public double? MaxKnownBreadth { get; set; }
        public double? MaxKnownDraught { get; set; }
        public virtual ICollection<VesselPort> VesselsPorts { get; set; }
}

public class VesselPort
{
        public int IMO { get; set; }
        public string PortLocode { get; set; }
        public DateTime? Departure { get; set; }
        public DateTime? Arrival { get; set; }
        public VesselModel VesselModel { get; set; }
        public PortModel PortModel { get; set; }
}

基于这个this SO answer,我设法创建了这样的LINQ:

_context.Ports.Include(p => p.VesselsPorts).ThenInclude(p => p.Arrival).OrderBy(p => p.PortLocode)
.Select(
                    p => new PortModel
                    {
                        PortLocode = p.PortLocode,
                        MaxKnownBreadth = p.MaxKnownBreadth,
                        MaxKnownDraught = p.MaxKnownDraught,
                        MaxKnownLOA = p.MaxKnownLOA,
                        VesselsPorts = p.VesselsPorts.Select(vp => vp.Arrival > DateTime.UtcNow.AddDays(-1)) as ICollection<VesselPort>
                    }).AsQueryable();

但我需要的是找到所有端口记录,其中: VesselsPorts.Arrival &gt; DateTime.UtcNow.AddDays(-1) 数量大于 int x = 5 值(例如)。而且我不知道该怎么做:/

【问题讨论】:

  • 你试过Where吗?
  • 是的,您不能在 .NET Core 2.2 中在 Include 或 IncludeThen 上使用 Where
  • 你试过_context.Ports.Include(p =&gt; p.VesselsPorts) .Where(p=&gt;p.VesselsPorts.Count(vp =&gt; vp.Arrival &gt; DateTime.UtcNow.AddDays(-1)) &gt; 5)
  • 您正在投影到PortModel,因此无论如何都会忽略Includes。而Include 与过滤主查询无关。专注于如何过滤端口。我认为类似于_context.Ports.Where(p =&gt; p.VesselsPorts.Any(vp =&gt; vp.Arrival &gt; DateTime.UtcNow.AddDays(x))。
  • @GertArnold 看起来你可能是对的。现在我只是想知道如何查询所有ports vaving (vp =&gt; vp.Arrival &gt; DateTime.UtcNow.AddDays(-1)) 至少Count &gt;= 5,例如。

标签: c# linq iqueryable asp.net-core-2.2


【解决方案1】:

感谢 @GertArnold 的评论,我最终得到了查询:

ports = ports.Where(p => p.VesselsPorts.Where(vp => vp.Arrival > DateTime.UtcNow.AddDays(-1)).Count() > x);

【讨论】:

    【解决方案2】:

    当使用实体框架时,人们倾向于使用Include 而不是Select 来节省一些打字时间。这样做很少是明智的。

    DbContext 拥有一个ChangeTracker。在 DbContext 的生命周期内从任何表中获取的每个完整行都存储在 ChangeTracker 中,以及一个克隆。您将获得对副本的引用。 (或者可能是对原件的引用)。如果您更改所获得数据的属性,它们会在 ChangeTracker 中的副本中更改。在 SaveChanges 期间,会将原始数据与副本进行比较,以查看是否必须保存数据。

    因此,如果您要获取大量数据并使用include,那么每个获取的项目都会被克隆。这可能会大大减慢您的查询速度。

    除了这种克隆,您可能会获取比实际计划使用更多的属性。数据库管理系统在组合表格和在表格中搜索行方面得到了极大的优化。较慢的部分之一是将所选数据传输到本地进程。

    例如,如果您有一个包含学校和学生的数据库,并且具有明显的一对多关系,那么每个学生都会有一个指向他就读的学校的外键。

    因此,如果您要求 School [10] 和他的 2000 个学生,那么每个学生的外键值为 [10]。如果您使用Include,那么您将传输相同的值 10 超过 2000 次。多么浪费处理能力!

    在实体框架中,查询数据时,始终使用Select选择属性,并且只选择您实际计划使用的属性。如果您打算更改获取的项目,请仅使用 Include。

    当然不要使用Include 来节省您的打字时间!

    要求:把港口和他们的船只给我

    var portsWithTheirVessels = dbContext.Ports
    .Where(port => ...)       // if you don't want all Ports
    
    .Select(port => new
    {
        // only select the properties that you want:
        PortLocode = port.PortLoCode,
        MaxKnownLOA = port.MaxKnownLOA,
        MaxKnownBreadth = prot.MaxKnownBreadth,
        MaxKnownDraught = ports.MaxKnownDraught,
    
        // The Vessels in this port:
        Vessels = port.VesselsPort.Select(vessel => new
        {
           // again: only the properties that you plan to use
           IMO = vessel.IMO,
           ...
    
           // do not select the foreign key, you already know the value!
           // PortLocode = vessle.PortLocode,
        })
        .ToList(),
    });
    

    实体框架知道您的一对多关系,并且知道如果您使用虚拟 ICollection 它应该执行 (Group-)Join。

    有些人更喜欢自己进行 Group-Join,或者他们使用不支持使用 ICollection 的实体框架版本。

    var portsWithTheirVessels = dbContext.Ports.GroupJoin(dbContext.VesselPorts,
    
    port => port.PortLocode,             // from every Port take the primary key
    vessel => vessel.PortLocode,         // from every Vessel take the foreign key to Port
    
    // parameter resultSelector: take every Port with its zero or more Vessels to make one new
    (port, vesselsInThisPort) => new
    {
        PortLocode = port.PortLoCode,
        ...
    
        Vessels = vesselsInThisPort.Select(vessel => new
        {
            ...
        })
        .ToList(),
    });
    

    替代方案:

    var portsWithTheirVessels = dbContext.Ports.Select(port => new
    {
        PortLocode = port.PortLoCode,
        ...
    
        Vessels = dbContext.VesselPorts.Where(vessel => vessel.PortLocode == port.PortLocode)
            .Select(vessel => new 
            {
                ...
            }
            .ToList(),
    

    });

    实体框架也会将此转换为 GroupJoin。

    【讨论】:

      猜你喜欢
      • 2018-09-15
      • 1970-01-01
      • 2012-01-08
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2020-09-22
      相关资源
      最近更新 更多