【问题标题】:How to convert Linq query for joining 3 tables to Linq method and use Include如何将用于连接 3 个表的 Linq 查询转换为 Linq 方法并使用 Include
【发布时间】:2020-09-20 05:36:43
【问题描述】:

我看过一些类似的帖子,但我无法弄清楚如何解决我的问题。我在 ASP.NET Core 项目中有三个表。在我的 Prescriptions 和 Prescribers 表中有 one-to-many 关系,在我的 Prescribers 和 Marketers 表中有 one-to-many 关系。我想查询 Prescriptions 表并获取外键 PrescriberId,然后查询 Prescribers 表并获取外键 MarketerId 并返回 MarketerId。到目前为止,我有这个 linq

'var presciber = from p in _context.Prescriptions
                 join f in _context.Prescribers on p.PrescriberId equals f.Id
                 join m in _context.Marketers on f.MarketerId equals m.Id
                 where p.FolderStatusId == 3
                 select new { m.Id };

我希望能够使用.Include,这样我就可以包含来自Marketers 表的其他数据,因此我可以显示Marketer Name,而不是显示MarketerId,而不是在视图中显示为空。所以想把这个查询转换成方法,这个我试过了

var prescriber = _context.Prescriptions
    .Join(_context.Prescribers,
    p => p.PrescriberId,
    f => f.Id,
    (p, f) => new { Prescriptions = p, Prescribers = f })
    .Join(_Context.Marketers,
    f => f.Prescribers.MarketerId,
    m => m.Id,
    (f, m) => new { Prescribers = f, Marketers = m })
    .Where(d => d.FolderStatusId == 3);//This line gets the error

Where 子句中我收到错误

<anonmyous type: <anonymous type:Prescription Prescriptions, Prescriber Prescribers> Prescribers, Marketer Marketers> does not contain a definition for FolderStatusId

这是我的课程

public class Prescriptions {
  public int Id {get;set;}
  public int FolderStatusId {get;set;}
  public int PrescriberId { get; set; }
  public string Medication { get; set; }
  public virtual Prescriber Prescriber { get; set; }
}

这里是 Prescribers 类

public class Prescriber {
    public int Id { get; set; }
    public int MarketerId { get; set; }
    public string Name { get; set; }
    public virtual Marketer Marketer {get;set;}
    public virtual ICollection<Prescription> Prescriptions { get; set; }
}

这是营销人员类

public class Marketer {
    public int Id { get;set; }
    public string Name { get; set; }
    public virtual ICollection<Prescriber> Prescribers { get; set; }
}

我的问题是如何更正此 Linq 方法以及在哪里添加 .Include

【问题讨论】:

  • 您能否添加您的实体定义。
  • Include 用于导航属性而不是其他列,因此您只需在选择中添加所需的所有列。说到导航属性,我建议考虑使用它们而不是 Joins coding.abel.nu/2012/06/dont-use-linqs-join-navigate
  • 我添加了模型类,我将查看导航
  • 你有导航属性,那你为什么join
  • 我愿意学习任何更好的解决方案。我使用了join,因为我认为这是要走的路,但我不一定仅限于join

标签: c# asp.net linq


【解决方案1】:

我的建议是使用对读者有意义的标识符。这将有助于您了解问题的原因:

var result = _context.Prescriptions
.Join(_context.Prescribers,
prescription => prescription.PrescriberId,
prescriber => prescriber.Id,
(prescription, prescriber) => new
{ 
    Prescription = prescription,
    Prescriber = prescriber,
})
// End of first join

.Join(_Context.Marketers,
firstJoinResult => firstJoinResult.Prescriber.MarketerId,
marketer => marketer.Id,

(firstJoinResult, marketer) => new
{
    Prescription = prescription,
    Prescriber = prescriber,
    Marketer = marketer,
})
// end of second join

.Where(secondJoinResult => secondJoinResult.FolderStatusId == 3);
//This line gets the error

您的第二个连接结果没有属性 FolderStatusId。在您的原始代码中,您使用标识符 d 来引用第二个连接结果。只有PrescriptionsFolderStatusId。所以你的Where 应该是这样的:

.Where(secondJoinResult => secondJoinResult.Prescription.FolderStatusId == 3);

还有改进的余地

在连接之前使用 Where

如果你在开始加入之前使用Where,那么很多Prescriptions根本不必加入:

var result = _context.Prescriptions
    .Where(prescription => prescription.FolderStatusId == 3);
    .Join(_context.Prescribers,
        prescription => prescription.PrescriberId,
        prescriber => prescriber.Id,
        ... // etc

仅选择您计划使用的属性

所以Marketer 有零个或多个Prescribers,每个Prescriber 有零个或多个Prescriptions,就像你说的:一对多关系。

Marketer [14] 的每个 Prescription 都有一个值为 14 的外键 MarketerId。如果 Marketer [14] 有 1000 个 Prescriptions,那么您将传输 Marketer [14] 与其中一个 Prescriptions 的组合的 1000 倍营销人员 ID [14]。

除了您将传输 1000 次相同的营销人员数据这一事实之外,您还将传输相同的 Prescription.MarketerId 值,您已经知道该值:它具有值 [14]。多么浪费处理能力!

解决方案:仅选择您计划使用的属性:

    var result = _context.Prescriptions
    .Where(prescription => prescription.FolderStatusId == 3);
    .Join(_context.Prescribers,
        prescription => prescription.PrescriberId,
        prescriber => prescriber.Id,
        ... // etc
        (firstJoinResult, marketer) => new
        {
            Marketer = new
            {
                // Select only the Marketer properties that you plan to use
                Id = marketer.Id,
                Name = markter.Name,
                ...
            }

            Subscriber = new
            {
                // Again: only the properties that you plan to use:
                Id = firstJoinResult.Subscriber.Id,
                Name = markter.Name,

                // No need for this, you  know the value:
                // MarketerId = firstJoinResult.Subscriber.MarketerId,
            },

            Prescription = new { ... },
       });

在实体框架中始终使用 Select 来查询数据并仅选择您计划使用的属性。如果您打算更改包含的数据,请仅使用 Include。

原因是,实体框架只能更新完全获取的项目。

考虑一个 GroupJoin

我在上一章已经提到:Marketer [14] 的相同值会被多次转移。

当您想要项目及其子项时,例如学校及其学生、客户及其订单以及营销人员及其处方者,请考虑使用 GroupJoin 而不是 Join。

var result = _context.Marketers
    .GroupJoin(_context.Prescribers,
        marketer => marketer.Id,
        prescriber => prescriber.MarketerId,

        // parameter ResultSelector:
        // get all Marketers with their prescribers to make one new
        (marketer, prescribersOfThisMarketer) => new
        {
            Id = marketer.Id,
            Name = marketer.Name,
            ...

            Prescribers = prescribersOfThisPrescription.GroupJoin(
                _context.Prescriptions.Where(prescription => prescription.FolderStatusId == 3),
                prescriber => prescriber.Id,
                prescription => prescription.PrescriberId,

                // from every prescriber with all its prescriptions, make one new
                (prescriber, prescriptionsOfThisPrescriber) => new
                {
                    Id = prescriber.Id,
                    Name = prescriber.Name,
                    ...

                    Prescriptions = prescriptionsOfThisPrescriber.Select(presription => new
                    {
                         Id = prescription.Id,
                         ...
                    })
                    .ToList(),
                })
                .ToList(),
        });

所以不是

Marketer  Subscriber
   A          10
   A          11
   B          20
   A          28
   C          12
   B          13

你得到:

  • 营销人员 A 和他的订阅者 10、11、28
  • 营销人员 B 和他的订阅者 20、13
  • 营销人员 C 和他的订阅者 12
  • 没有任何订阅者的营销人员 D

这通常看起来更像您想要的结果。

注意:GroupJoin 也将返回没有子项目的项目!,例如没有任何订阅者的营销人员。通常你想要这个,但如果你不想要它们,使用 Where 将它们过滤掉:

.Where(marketer => marketer.Subscribers.Any());

最具革命性的:使用 ICollection!

有人告诉我 EF-core 不支持这个。我知道完整的实体框架确实可以:使用 ICollections 而不是 group-join。

要求给我所有营销人员的(一些属性),每个营销人员(所有的一些属性)他的处方者,每个人都有他的处方。

var marketers = dbContext.Marketers.Select(marketer => new
{
    Id = marketer.Id,
    Name = marketer.Name,
    ...

    Prescribers = marketer.Prescribers.Select(prescriber => new
    {
        Id = prescriber.Id,
        Name = prescriber.Name,
        ...

        Prescriptions = prescriber.Prescriptions
            .Where(prescription => prescription.FolderStatusId == 3)
            .Select(prescription => new
            {
                 Id = prescription.Id,
                 ...
            })
            .ToList(),
    })
    .ToList(),
});

这感觉比 (Group-)Join 更自然。实体框架知道表之间的关系并将其转换为正确的(Group-)Join。

【讨论】:

  • 这太好了,谢谢!远远超出我的预期。
猜你喜欢
  • 1970-01-01
  • 2019-12-17
  • 2016-05-27
  • 2021-07-11
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多