我希望为一个简单的集合场景编写linq
语句。我试图避免基于父子关系的集合中的重复项目。数据结构和示例代码如下
public class Catalog
{
public int CatalogId { get; set; }
public int ParentCatalogId { get; set; }
public string CatalogName { get; set; }
}
public class Model
{
public int CatalogId { get; set; }
public string ItemName { get; set; }
...
}
List<Catalog> Catalogs
:包含所有目录中任何级别的父子关系的完整列表,以及包含ParentCatalogid=null
List<Model> CollectionA
:包含子项的所有项目以及特定catalogId的父目录(直到其根目录)。
我需要从CollectionA创建一个CollectionB,它将包含所提供的catalogId的项目,包括所有父项的所有项目,如果项目存在于子目录中,我需要忽略父目录中的相同项目。这样,如果子项和父项中都有相同的项目,则不会有任何重复的项目。
就代码而言,我正在努力实现这样的目标
while (catalogId!= null)
{
CollectionB.AddRange(
CollectionA.Where(x => x.CatalogId == catalogId &&
!CollectionB.Select(y => y.ItemName).Contains(x.ItemName)));
// Starting from child to parent and ignoring items that are already in CollectionB
catalogId = Catalogs.
Where(x => x.Id == catalogId).
Select(x => x.ParentCatalogId).
FirstOrDefault();
}
我知道上面语句中linq中的Contains子句不起作用,只是把那个语句解释为我要做的事情。我可以使用foreach
循环执行此操作,但只想使用linq
。我正在寻找正确的linq声明来做到这一点。下面给出了样本数据,如果我能得到一些帮助,我将非常感激
Catalog
ID ParenId CatalogName
1 null CatalogA
2 1 Catalogb
3 1 CatalogC
4 2 CatalogD
5 4 CatalogE
CollectionA
CatalogId ItemName
5 ItemA
5 ItemB
4 ItemA
4 ItemC
2 ItemA
2 ItemC
1 ItemD
Expected output
CollectionB
5 ItemA
5 ItemB
4 ItemC
1 ItemD
答案 0 :(得分:1)
LINQ不是为了遍历分层数据结构而设计的,因为它已被考虑在:
但是如果您可以从子到根获得目录的层次结构,那么问题可以通过join和distinct - LINQ's Distinct() on a particular property来解决:
var modelsForE = (from catalog in flattenedHierarchyOfCatalogE
join model in models
on catalog.CatalogId equals model.CatalogId
select model).
GroupBy(model => model.ItemName).
Select(modelGroup => modelGroup.First()).
Distinct();
甚至更好 - 让Jon Skeet's answer适应不同。
它解决了重复问题,但又给我们留下了另一个问题:如何获得flattenedHierarchyOfCatalogE
?
PURE LINQ SOLUTION:
这不是一件容易的事,但纯粹的LINQ并非完全不可能。我们得到了How to search Hierarchical Data with Linq:
public static class LinqExtensions
{
public static IEnumerable<T> Flatten<T>(this T source, Func<T, IEnumerable<T>> selector)
{
return selector(source).SelectMany(c => Flatten(c, selector))
.Concat(new[] { source });
}
}
//...
var catalogs = new Catalog[]
{
new Catalog(1, 0, "CatalogA"),
new Catalog(2, 1, "Catalogb"),
new Catalog(3, 1, "CatalogC"),
new Catalog(4, 2, "CatalogD"),
new Catalog(5, 4, "CatalogE")
};
var models = new Model[]
{
new Model(5, "ItemA"),
new Model(5, "ItemB"),
new Model(4, "ItemA"),
new Model(4, "ItemC"),
new Model(2, "ItemA"),
new Model(2, "ItemC"),
new Model(1, "ItemD")
};
var catalogE = catalogs.SingleOrDefault(catalog => catalog.CatalogName == "CatalogE");
var flattenedHierarchyOfCatalogE = catalogE.Flatten((source) =>
catalogs.Where(catalog =>
catalog.CatalogId == source.ParentCatalogId));
然后从问题开头将flattenedHierarchyOfCatalogE
提供给查询。
警告:我已经为您的类添加了构造函数,因此以前的代码段可能无法在您的项目中编译:
public Catalog(Int32 catalogId, Int32 parentCatalogId, String catalogName)
{
this.CatalogId = catalogId;
this.ParentCatalogId = parentCatalogId;
this.CatalogName = catalogName;
} //...
需要考虑的事项
以前的解决方案没有任何问题(好吧,我个人可能已经考虑过使用LINQ的广泛使用,如Recursive Hierarchy - Recursive Query using Linq),但无论你喜欢哪种解决方案都可能有一个问题:它有效,但是它没有使用任何优化的数据结构 - 它只是直接搜索和选择。如果您的目录增长并且查询将更频繁地执行,那么性能可能会成为一个问题。
但是,即使性能不是问题,那么您的类的易用性也是如此。 Ids,外键适用于关系数据库,但在OO系统中非常难以处理。您可能希望为您的类考虑可能的object relational mapping(或创建他们的包装器(镜像),它们看起来像:
public class Catalog
{
public Catalog Parent { get; set; }
public IEnumerable<Catalog> Children { get; set; }
public string CatalogName { get; set; }
}
public class Model
{
public Catalog Catalog { get; set; }
public string ItemName { get; set; }
}
这些类更自包含,更易于使用和遍历其层次结构。我不知道您的系统是否是数据库驱动的,但您仍然可以查看一些object-relational mapping示例和技术。
P.S。: LINQ不是.NET工具库中的绝对工具。毫无疑问,它是适用于多种情况的非常有用的工具,但并非在所有可能的情况下都适用。如果工具无法帮助您解决问题,那么它应该被修改或搁置一会儿。
答案 1 :(得分:0)
您最有可能寻找SelectMany()
分机。下面是一个简短的例子,它可以用来选择所有孩子进行比较(以避免重复):
var col = new[] {
new { name = "joe", children = new [] {
new { name = "billy", age=1 },
new { name = "sally", age=4 }
}},
new { name = "bob", children = new [] {
new { name = "megan", age=10 },
new { name = "molly", age=7 }
}}
};
col.SelectMany(c => c.children).Dump("kids");
有关此扩展程序的堆栈溢出问题,请参阅the actual msdn documentation