问题
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