获取 Children 对所有 Parents 通用
Get Children common to All Parents
我有以下使用 Entity Framework 核心的实体:
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 表达式:
- 得到所有人共有的ChildrenParents;
- 对于每个
Children
从 ChildrenLocalization
和 Language="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 才能使其正常工作。
如何使我的查询更简单?
您需要将通话从分组中拆分出来。
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
};
});
如果我理解正确,这可能会奏效。这将是一个查询。
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();
鉴于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
})
因为你有 many-to-many 关系,最好基于(开始)结果实体(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。
您从 独特 Children
开始。对于要求 (2),您只需使用导航 属性。要求(1)更复杂(all总是比any更难实现),但我认为标准
parents.All(p => p.ParentChildrens.Select(pc => pc.ChildrenId).Contains(c.ChildrenId))
非常直观地表示 child 对所有 parents 通用。
假设您有 3 个 Parent,ID 分别为 10、11、12
假设您有 3 个 Children,ID 为 20、21、22
ParentChild伦斯table:
ChildId | ParentId
20 10
20 11
20 12
21 10
21 11
22 10
22 12
所以 Child 20 有 Parents 10/11/12; Child 21 有 Parent 10/11; Child 22 有 Parents 10/12.
"Get the Children common to all Parents";如果这意味着:获取 Children 在其 Parents 集合中具有每个可用 Parent 的 Children,那么很容易看出您只想要 Child 20,并且你只想要这个 Child 一次
Because all Parent - Child relations are unique, we know that if there are X parents, that we want the Children that have exactly X parents.
您不需要这些 Children 的所有属性,您只想“从 ChildrenLocalization with Language="en" 获取其名称,总是有零个或一个这样的名字吗?如果有更多我们应该取哪个名字?任何名字,还是所有名字?
因为我们需要限制所有 children 的 ParentCount 等于 parents 的数量,我们还需要计算 [=48] 的数量=]s 每 child
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();
});
现在我们不需要所有这些 children,我们只想要那些 ParentCount 等于 Parents 的 children
var childrenWithAllParents = childrenWithParentCount
.Where(child => !child.ParentCount == dbContext.Parents.Count());
您是否注意到,我只创建了 IQueryable objects,我还没有执行任何查询。要执行查询:
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();
幸运的是,您的数据库管理系统足够聪明,可以记住 Parent 的数量,而不是每 Child.
重新计算一次
我有以下使用 Entity Framework 核心的实体:
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 表达式:
- 得到所有人共有的ChildrenParents;
- 对于每个
Children
从ChildrenLocalization
和Language="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 才能使其正常工作。
如何使我的查询更简单?
您需要将通话从分组中拆分出来。
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
};
});
如果我理解正确,这可能会奏效。这将是一个查询。
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();
鉴于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
})
因为你有 many-to-many 关系,最好基于(开始)结果实体(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。
您从 独特 Children
开始。对于要求 (2),您只需使用导航 属性。要求(1)更复杂(all总是比any更难实现),但我认为标准
parents.All(p => p.ParentChildrens.Select(pc => pc.ChildrenId).Contains(c.ChildrenId))
非常直观地表示 child 对所有 parents 通用。
假设您有 3 个 Parent,ID 分别为 10、11、12 假设您有 3 个 Children,ID 为 20、21、22
ParentChild伦斯table:
ChildId | ParentId
20 10
20 11
20 12
21 10
21 11
22 10
22 12
所以 Child 20 有 Parents 10/11/12; Child 21 有 Parent 10/11; Child 22 有 Parents 10/12.
"Get the Children common to all Parents";如果这意味着:获取 Children 在其 Parents 集合中具有每个可用 Parent 的 Children,那么很容易看出您只想要 Child 20,并且你只想要这个 Child 一次
Because all Parent - Child relations are unique, we know that if there are X parents, that we want the Children that have exactly X parents.
您不需要这些 Children 的所有属性,您只想“从 ChildrenLocalization with Language="en" 获取其名称,总是有零个或一个这样的名字吗?如果有更多我们应该取哪个名字?任何名字,还是所有名字?
因为我们需要限制所有 children 的 ParentCount 等于 parents 的数量,我们还需要计算 [=48] 的数量=]s 每 child
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();
});
现在我们不需要所有这些 children,我们只想要那些 ParentCount 等于 Parents 的 children
var childrenWithAllParents = childrenWithParentCount
.Where(child => !child.ParentCount == dbContext.Parents.Count());
您是否注意到,我只创建了 IQueryable objects,我还没有执行任何查询。要执行查询:
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();
幸运的是,您的数据库管理系统足够聪明,可以记住 Parent 的数量,而不是每 Child.
重新计算一次