1

我有以下使用 Entity Framework Core 的实体:

public class Parent {
  public Int32 ParentId { get; set; }
  public virtual Collection<ParentChildren> ParentChildrens { get; set; }
}

public class ParentChildren {
  public Int32 ParentId { get; set; }
  public Int32 ChildrenId { get; set; }
  public virtual Parent Parent { get; set; }
  public virtual Children Children { get; set; }
}

public class Children {
  public Int32 ChildrenId { get; set; }
  public virtual Collection<ParentChildren> ParentChildrens { get; set; }
  public virtual Collection<ChildrenLocalization> ChildrenLocalizations { get; set; }
}

public class ChildrenLocalization {
  public Int32 ChildrenId { get; set; }
  public String Language { get; set; }
  public String Name { get; set; }
  public virtual Children Children { get; set; }
}

鉴于IQueryable<Parent>我需要,使用 Linq to Entities lambda 表达式:

  1. 让所有父母共有的孩子
  2. 对于每一个都从with中Children获得它的名字。ChildrenLocalizationLanguage="en"

所以我尝试了以下方法:

var result = context.Parents
  .SelectMany(y => y.ParentChildrens)
  .GroupBy(y => y.ParentId)
  .Where(y => 
     context.Parents
       .SelectMany(y => y.ParentChildrens)
       .Select(z => z.ChildrenId)
       .Distinct()
       .All(z => y.Any(w => w.ChildrenId == z)))
  .SelectMany(y => y)
  .Select(y => new {
    Id = y.ChildrenId,
    Name = y.Children.ChildrenLocalizations.Where(z => z.Language == "en").Select(z => z.Name).FirstOrDefault()
  })
  .GroupBy(x => x.Id)
  .Select(x => x.FirstOrDefault())
  .ToList();

这个查询给出了预期的结果,但它似乎太复杂了。

我无法改进它,例如,我需要添加最后一个 GroupBy 才能使其工作。

如何使我的查询更简单?

4

5 回答 5

3

由于您具有多对多关系,因此最好将查询基于(开始)结果实体(Children),从而避免GroupBy/Distinct如果您从另一端(Parent)开始查询。

所以给定

IQueryable<Parent> parents

并假设您可以访问上下文,查询可以编写如下:

var query = context.Set<Children>()
    .Where(c => parents.All(p => p.ParentChildrens.Select(pc => pc.ChildrenId).Contains(c.ChildrenId)))
    .Select(c => new
    {
        Id = c.ChildrenId,
        Name = c.ChildrenLocalizations.Where(cl => cl.Language == "en").Select(cl => cl.Name).FirstOrDefault()
    });

这很好地转换为单个 SQL。

你从unique Children开始。对于要求 (2),您只需使用导航属性。要求(1)更复杂(所有总是比任何更难实现),但我认为标准

parents.All(p => p.ParentChildrens.Select(pc => pc.ChildrenId).Contains(c.ChildrenId))

相当直观地代表了所有父母共有的孩子

于 2019-04-03T09:30:46.007 回答
1

如果我正确理解这一点,这可能会奏效。这将是一个单一的查询。

    var result =
            (from parent in context.Parents
            from pToC in parent.ParentChildrens
            where pToC.Children.ParentChildrens.Select(pc => pc.ParentId).Distinct().Count() == context.Parents.Count()
            from childLocation in pToC.Children.ChildrenLocalizations
            where childLocation.Language == "en"
            select new { pToC.Children.ChildrenId, childLocation.Name }).Distinct();
于 2019-04-02T23:18:41.500 回答
1

给定IQueryable<Parent> parents

parents
.SelectMany(p => p.ParentChildrens)
.Select(pc => pc.Children)
.Where(c => c.ParentChildrens
    .Select(pc => pc.ParentId)
    .OrderBy(i => i)
    .SequenceEqual(parents.Select(p => p.ParentId).OrderBy(i => i)))
.Select(c => new
{
    Id = c.ChildrenId,
    c.ChildrenLocalizations.FirstOrDefault(cl => cl.Language == "en").Name
})
于 2019-04-02T23:26:01.437 回答
1

假设您有 3 个父母,ID 为 10、11、12 假设您有 3 个孩子,ID 为 20、21、22

父子表:

ChildId | ParentId
  20         10
  20         11
  20         12
  21         10
  21         11
  22         10
  22         12

所以孩子 20 有父母 10/11/12;孩子 21 有父母 10/11;孩子 22 的父母是 10/12。

“让所有父母共同拥有的孩子”;如果这意味着:获取在其父母集合中具有每个可用父母的孩子,那么很容易看出您只想要孩子 20,并且您只想要这个孩子一次

因为所有的 Parent - Child 关系都是唯一的,所以我们知道如果有 X 个父母,我们想要拥有 X 个父母的孩子。

您不想要这些 Children 的所有属性,您只想“从带有 Language="en" 的 ChildrenLocalization 中获取其名称,这样的名称是否总是零个或一个?如果有更多,我们应该取哪一个?任何名称,或所有名称?

因为我们需要将自己限制为所有 ParentCount 等于父母数量的孩子,所以我们还需要计算每个孩子的父母数量

var childrenWithParentCount = dbContext.Children.Select(child => new
{
    // "get its name from ChildrenLocalization with Language="en"
    LocalizationName = child.ChildrenLocalizations
                            .Where(localization => localization.Language == "en")
                            .Select(localization => localizaition.Name)
                            .FirstOrDefault();

    // or if you want all names:
    LocalizationNames = child.ChildrenLocalizations
                             .Where(localization => localization.Language == "en")
                             .Select(localization => localizaition.Name)
                            .ToList;

    ParentCount = child.ParentChildren
                       .Select(parentChild => parentChild.ParentId)
                       .Count();
});

现在我们不想要所有这些孩子,我们只想要那些 ParentCount 等于父母数量的孩子

var childrenWithAllParents = childrenWithParentCount
    .Where(child => !child.ParentCount == dbContext.Parents.Count());

你注意到了吗,我只创建了 IQueryable 对象,还没有执行任何查询。要执行查询:

var result = childrenWithAllParents.ToList();

有些人喜欢用一个大的 LINQ 语句给别人留下深刻印象;好吧,这里是:

var result = dbContext.Children.Select(child => new
{
    LocalizationName = child.ChildrenLocalizations
          .Where(localization => localization.Language == "en")
          .Select(localization => localizaition.Name)
          .FirstOrDefault();

    ParentCount = child.ParentChildren
                       .Select(parentChild => parentChild.ParentId)
                       .Count();
})
.Where(child => !child.ParentCount == dbContext.Parents.Count())
.ToList();

幸运的是,您的数据库管理系统足够聪明,可以记住父母的数量,而不是对每个孩子重新计算一次。

于 2019-04-03T14:33:35.060 回答
-1

您需要将呼叫从分组中分离出来。

  List<Parent> result = context.Parents
                .Include(i => i.ParentChildrens)
                .ThenInclude(i => i.Children)
                .ThenInclude(i => i.ChildrenLocalizations)
                .ToList();

            var finalResult = result.SelectMany(c => c.ParentChildrens, (o, j) =>
            {
                return new
                {
                    Id = j.ChildrenId,
                    Parent = o.ParentId,
                    Name = j.Children.ChildrenLocalizations.First(c => c.Language == "en").Name
                };
            });
于 2019-04-02T23:08:23.627 回答