避免在分层父子关系集合中重复

时间:2014-08-09 16:55:36

标签: c# linq duplicates hierarchical-data relation

我希望为一个简单的集合场景编写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

2 个答案:

答案 0 :(得分:1)

LINQ不是为了遍历分层数据结构而设计的,因为它已被考虑在:

但是如果您可以从子到根获得目录的层次结构,那么问题可以通过joindistinct - 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