2012-05-29 42 views
0

我有一類項目作爲合併自定義對象的多個列表的內容 - C#

public class Project 
{ public int ProjectId { get; set; } 
    public string ProjectName { get; set; } 
    public string Customer { get; set; } 
    public string Address{ get; set; } 
} 

,我有3所列出

List<Project> lst1; List<Project> lst2; List<Project> lst3; 

lst1包含Person對象與專案編號和項目名。

ProjectId =1, ProjectName = "X", Customer = null, Address = null

ProjectId =2, ProjectName = "Y", Customer = null, Address = null

lst2包含Person對象與專案編號和客戶

ProjectId =1,ProjectName = null, Customer = "c1", Address = null

ProjectId =2,ProjectName = null, Customer = "c2", Address = null

lst3包含Person對象與專案編號和地址

ProjectId = 2, ProjectName = null, Customer =null, Address = "a2"

考慮到有多個這樣的記錄在每個列表和專案是潮頭對於每一個項目,我怎麼能合併/組合這些列表以獲得一個列表與合併對象

ProjectId=1, ProjectName="X", Customer="c1", address="a1"

ProjectId=2, ProjectName="Y", Customer="c2", address="a2"

我發現這些鏈接類似,並試圖用它,但不能滿足結果

Create a list from two object lists with linq

How to merge two lists using LINQ?

謝謝。

+0

在每個列表,是否有總是(除了專案編號)精確一個屬性不是null? – phg

+0

是否有一套固定的屬性,事先已知? – phg

+0

@phg:兩個問題都沒有。這僅僅是一個例子。 而且列表中可能不包含相同數量的記錄。 – quitprog

回答

2

這可以通過多步驟方法完成。首先,定義一個Func<Project, Project, Project>來處理實際的記錄合併。也就是說,您正在定義一個簽名相當於public Project SomeMethod(Project p1, Project p2)的方法。該方法實現了上面概述的合併邏輯。

Func<Project, Project, Project> mergeFunc = (p1,p2) => new Project 
    { 
     ProjectId = p1.ProjectId, 
     ProjectName = p1.ProjectName == null ? p2.ProjectName : p1.ProjectName, 
     Customer = p1.Customer == null ? p2.Customer : p1.Customer, 
     Address = p1.Address == null ? p2.Address : p1.Address  
    }; 

var output = lst1.Concat(lst2).Concat(lst3) 
       .GroupBy(x => x.ProjectId, (k, g) => g.Aggregate(mergeFunc)); 

這裏有一個快速和骯髒的測試:接下來,我們通過ProjectId將它們分組,用我們的合併代表如在GroupBy過載的聚合函數,它接受一個結果選擇前級連在一起的列表的元素與輸出一起上述邏輯:

List<Project> lst1; List<Project> lst2; List<Project> lst3; 
lst1 = new List<Project> 
    { 
     new Project { ProjectId = 1, ProjectName = "P1" }, 
     new Project { ProjectId = 2, ProjectName = "P2" }, 
     new Project { ProjectId = 3, ProjectName = "P3" } 
    }; 
lst2 = new List<Project> 
    { 
     new Project { ProjectId = 1, Customer = "Cust1"}, 
     new Project { ProjectId = 2, Customer = "Cust2"}, 
     new Project { ProjectId = 3, Customer = "Cust3"} 
    }; 
lst3 = new List<Project> 
    { 
     new Project { ProjectId = 1, Address = "Add1"}, 
     new Project { ProjectId = 2, Address = "Add2"}, 
     new Project { ProjectId = 3, Address = "Add3"} 
    }; 

Func<Project, Project, Project> mergeFunc = (p1,p2) => new Project 
    { 
     ProjectId = p1.ProjectId, 
     ProjectName = p1.ProjectName == null ? p2.ProjectName : p1.ProjectName, 
     Customer = p1.Customer == null ? p2.Customer : p1.Customer, 
     Address = p1.Address == null ? p2.Address : p1.Address  
    }; 

var output = lst1 
    .Concat(lst2) 
    .Concat(lst3) 
    .GroupBy(x => x.ProjectId, (k, g) => g.Aggregate(mergeFunc)); 

IEnumerable<bool> assertedCollection = output.Select((x, i) => 
    x.ProjectId == (i + 1) 
    && x.ProjectName == "P" + (i+1) 
    && x.Customer == "Cust" + (i+1) 
    && x.Address == "Add" + (i+1)); 

Debug.Assert(output.Count() == 3); 
Debug.Assert(assertedCollection.All(x => x == true)); 

--- ---輸出

IEnumerable<Project> (3 items) 
ProjectId ProjectName Customer Address 
1 P1 Cust1 Add1 
2 P2 Cust2 Add2 
3 P3 Cust3 Add3 
1

我假設列表包含相同數量的項目並按ProjectId排序。

List<Project> lst1; List<Project> lst2; List<Project> lst3 

如果列表沒有排序,您可以先排序。

list1.Sort(p => p.ProjectId); 
list2.Sort(p => p.ProjectId); 
list3.Sort(p => p.ProjectId); 

對於合併對象

List<Project> list4 = new List<Project>(); 
for(int i=1; i<list.Count; i++) 
{ 
    list4.Add(new Project 
    { 
     ProjectId = list1[i].ProjectId; 
     ProjectName = list1[i].ProjectName; 
     Customer = list2[i].Customer; 
     Address = list3[i].Address; 
    }); 

} 
+0

如果項目全部按正確的順序,這是一個很好的簡單方法。 – Rawling

+0

列表可能不包含相同數量的記錄。即使他們有相同的記錄ProjectId可能會有所不同。 – quitprog

+0

然後在哪些條件下你會合並它們? –

1

我相信在folloing是如何LINQ Join作品:

var mergedProjects = 
    lst1 
     .Join(lst2, 
      proj1 => proj1.ProjectID, 
      proj2 => proj2.ProjectID, 
      (proj1, proj2) => new { Proj1 = proj1, Proj2 = proj2 }) 
     .Join(lst3, 
      pair => pair.Proj1.ProjectID, 
      proj3 => proj3.ProjectID, 
      (pair, proj3) => new Project 
      { 
       ProjectID = proj3.ProjectID, 
       ProjectName = pair.Proj1.ProjectName, 
       Customer = pair.Proj2.Customer, 
       Address = proj3.Address 
      }); 

這將不會返回在ProjectID所有找到的任何結果三個名單

如果這是一個問題,我認爲你最好是手動完成而不是使用LINQ。

+0

儘管這解決了OP問題,但它的確相當狹隘。這個解決方案看起來有點脆弱。如果代碼在操作中明確地表示合併邏輯,而不是假定「projXX.PropXX」以特定方式映射(沒有downvote,BTW,只是一個觀察),那將會更好。 –

+0

@Josh問題非常具體關於來自給定列表的每個屬性,而不是每個列表中存在的所有ID。將所有具有給定ID的項目整理成一個匿名對象然後收集屬性作爲最後一步,而不是一個一個收集屬性會更清楚。如果您有其他屬性的其他列表,或者每個列表中有多個屬性,但這些屬性的擴展性不是很好,但這些在原始問題中沒有說明。 – Rawling

+0

總有1 ... N種方式來給貓皮,對吧?公平點你做。 –

1

雖然矯枉過正,我很想讓這個擴展方法:

public static List<T> MergeWith<T,TKey>(this List<T> list, List<T> other, Func<T,TKey> keySelector, Func<T,T,T> merge) 
{ 
    var newList = new List<T>(); 
    foreach(var item in list) 
    { 
     var otherItem = other.SingleOrDefault((i) => keySelector(i).Equals(keySelector(item))); 
     if(otherItem != null) 
     { 
      newList.Add(merge(item,otherItem)); 
     } 
    } 
    return newList; 
} 

然後用法是:

var merged = list1 
    .MergeWith(list2, i => i.ProjectId, 
     (lhs,rhs) => new Project{ProjectId=lhs.ProjectId,ProjectName=lhs.ProjectName, Customer=rhs.Customer}) 
    .MergeWith(list3,i => i.ProjectId, 
     (lhs,rhs) => new Project{ProjectId=lhs.ProjectId,ProjectName=lhs.ProjectName, Customer=lhs.Customer,Address=rhs.Address}); 

活生生的例子:http://rextester.com/ETIVB14254

+0

這對我來說非常合適。謝謝。只有一件事,爲什麼矯枉過正?我的意思是在表現方面還是什麼? – quitprog

+0

這太過分了,因爲它對於一個相對簡單的問題來說是一個複雜的解決方案(@Jamiec--你的意思是?)。而且,合併完成的方式將導致/可能導致多次執行相同的查詢表達式。最後,還有其他一些操作符(即'.Zip()')會複製一些(但不是全部)這個功能。 –

+1

@JoshE - 非常精確。我寫得很快,只知道會有更好的方法。 – Jamiec

2

使用查找,你可以做它是這樣的:

 List<Project> lst = lst1.Union(lst2).Union(lst3).ToLookup(x => x.ProjectId).Select(x => new Project() 
     { 
      ProjectId = x.Key, 
      ProjectName = x.Select(y => y.ProjectName).Aggregate((z1,z2) => z1 ?? z2), 
      Customer = x.Select(y => y.Customer).Aggregate((z1, z2) => z1 ?? z2), 
      Address = x.Select(y => y.Address).Aggregate((z1, z2) => z1 ?? z2) 
     }).ToList(); 
+0

完美地工作。謝謝。我想我需要搜索ToLookup的工作方式。 – quitprog

+0

不錯,我想知道如何用Aggregate做到這一點。 – Rawling

+0

雖然此解決方案有效,但您不需要在聚合之前投影屬性。看看我的答案,你是如何以更可讀的方式做同樣的事情的。查找也是可選的,在這種情況下大致等同於GroupBy。 –

0

這假設您想要取第一個非空值,或恢復爲默認值 - 在此情況下,字符串爲null。

private static IEnumerable<Project> GetMergedProjects(IEnumerable<List<Project>> projects) 
{ 
    var projectGrouping = projects.SelectMany(p => p).GroupBy(p => p.ProjectId); 
    foreach (var projectGroup in projectGrouping) 
    { 
     yield return new Project 
          { 
           ProjectId = projectGroup.Key, 
           ProjectName = 
            projectGroup.Select(p => p.ProjectName).FirstOrDefault(
             p => !string.IsNullOrEmpty(p)), 
           Customer = 
            projectGroup.Select(c => c.Customer).FirstOrDefault(
             c => !string.IsNullOrEmpty(c)), 
           Address = 
            projectGroup.Select(a => a.Address).FirstOrDefault(
             a => !string.IsNullOrEmpty(a)), 
          }; 
    } 
} 

如果需要,您也可以將其作爲擴展方法。