Merge contents of multiple lists of custom objects - C#

寵の児 提交于 2021-02-19 08:52:15

问题


I have a class Project as

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

and I have 3 lists

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

lst1 contains Person objects with ProjectId and ProjectName.

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

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

lst2 contains Person objects with ProjectId and Customer

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

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

lst3 contains Person objects with ProjectId and Address

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

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

Considering there are multiple such records in each list and ProjectId is Uniqe for each project, How can I merge/combine these list to get one list with merged objects

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

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

I found thse links similar and tried with it but could not meet the results

Create a list from two object lists with linq

How to merge two lists using LINQ?

Thank You.


回答1:


This could be done in a multi-step approach pretty simply. First, define a Func<Project, Project, Project> to handle the actual record merging. That is, you are defining a method with a signature equivalent to public Project SomeMethod(Project p1, Project p2). This method implements the merging logic you outlined above. Next, we concatenate the elements of the lists together before grouping them by ProjectId, using our merge delegate as the an aggregate function in the overload of GroupBy which accepts a result selector:

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)); 

Here's a quick and dirty test of the above logic along with output:

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));

--- output ---

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



回答2:


Using a Lookup you can do it like this:

        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();



回答3:


I belive the folloing is how LINQ Join works:

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
            });

This will not return any results where the ProjectID is not found in all three lists.

If this is a problem, I think you'd be better off doing this manually rather than using LINQ.




回答4:


I assume that list contains same number of items and are sorted by ProjectId.

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

If list are not sorted you can sort it first.

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

For merging the object

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;
    });

}



回答5:


Although overkill, I was tempted to make this an extension method:

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;
}

Usage would then be:

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});

Live example: http://rextester.com/ETIVB14254




回答6:


This is assuming that you want to take the first non-null value, or revert to the default value - in this case null for a string.

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)),
                            };
    }
}

You could also make this an extension method if needed.



来源:https://stackoverflow.com/questions/10798825/merge-contents-of-multiple-lists-of-custom-objects-c-sharp

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!