Using Linq to merge to List - linq

I have the code like this.
public class Employee
{
public int ID { get; set; }
public string Name { get; set; }
public string NickName { get; set; }
}
var table1 = new List<Employee>() {
new Employee(){ ID=1, Name="A"},
new Employee(){ ID=2, Name="B"},
new Employee(){ ID=3, Name="C"},
new Employee(){ ID=5, Name="E"},
};
var table2 = new List<Employee>() {
new Employee(){ ID=1, NickName="NickA"},
new Employee(){ ID=2, NickName="NickB"},
new Employee(){ ID=3, NickName="NickC"},
new Employee(){ ID=4, NickName="NickD"}
};
I want to merge two lists above to something like this:
{ID=1, Name="A", NickName="NickA"}
{ID=2, Name="B", NickName="NickB"}
{ID=3, Name="C", NickName="NickC"}
{ID=4, Name=null, NickName="NickD"}
{ID=5, Name="E", NickName=null}
How can I do it using LinQ. Please help me out!

You can group by ID, and pick the "first of" value for each field, like this:
var res = table1.Concat(table2).GroupBy(e => e.ID)
.Select(g => new Employee {
ID = g.Key
, Name = g.Select(e => e.Name).FirstOrDefault(s => s != null)
, NickName = g.Select(e => NickName).FirstOrDefault(s => s != null)
});
Note that if you take this approach it does not matter from what table the Name or the NickName comes: you could mix assignments in your lists, and let LINQ group them back for you.

Something like:
var results = table1.Select(t1 => {
var t2 = table2.First(x => x.ID == t1.ID);
return new Employee {
ID = t1.ID,
Name = t1.Name,
NickName = t2.NickName
};
}).ToList();
If table2 is sufficiently large (tens of thousands of entries or more) consider (ie. performance measure) building a Dictionary or sort the array to provide quicker lookup by ID (in the latter case using the BinarySearch method with the custom IComparer just comparing ids). The cost of setting up this intermediate structure is non-trivial, and modern computers can search a few thousand simple rows very quickly.

Related

Linq - Join collection with child collection of other collection

I have a collection of users, each with a child collection of memberships. And then I also have a club collection. The child collection of each user is populated with memberships containing a club ID only. What I want is for each membership to also contain the club entity matching the club ID.
My entities:
public class User
{
public string Name { get; set; }
public IEnumerable<Membership> Memberships { get; set; }
}
public class Membership
{
public Guid ClubId { get; set; }
public Club Club { get; set; }
}
public class Club
{
public Guid Id { get; set; }
public string Name { get; set; }
}
Populated with data:
var club1Id = new Guid("FFEB77B9-1616-463B-B36E-F95F7E255FDE");
var club2Id = new Guid("7A6ECD38-5AEB-418B-9DD5-FBD777CE190C");
var users = new List<User>
{
new User
{
Name = "Patrick Stewart",
Memberships = new List<Membership>
{
new Membership { ClubId = club1Id },
new Membership { ClubId = club2Id }
}
},
new User
{
Name = "Brent Spiner",
Memberships = new List<Membership>
{
new Membership { ClubId = club1Id }
}
},
new User
{
Name = "Wesley Crusher",
Memberships = null
}
};
var clubs = new List<Club>
{
new Club
{
Id = club1Id,
Name = "Officers Club"
},
new Club
{
Id = club2Id,
Name = "Captains Club"
}
};
And then I need a LINQ query that adds the matched Club objects to the Membership objects.
So that when I print out users like this:
foreach (var user in users)
{
Console.WriteLine($"User: {user.Name}");
if (user.Memberships != null)
{
foreach (var membership in user.Memberships)
{
Console.WriteLine($" Membership: {membership.Club?.Name}");
}
}
}
It will look like this:
User: Patrick Stewart
Membership: Officers Club
Membership: Captains Club
User: Brent Spiner
Membership: Officers Club
User: Wesley Crusher
I want to do it in one go with a LINQ expression, so that I don't have to involve for-loops.
I ended up solving it with this LINQ expression:
users = users.Select(user => new {
user,
Memberships = user.Memberships?
.GroupJoin(clubs, membership => membership.ClubId, club => club.Id, (membership, club) => new { membership, club})
.SelectMany(mc => mc.club.DefaultIfEmpty(), (mc, club) =>
{
mc.membership.Club = club;
return mc.membership;
})
})
.Select(x =>
{
x.user.Memberships = x.Memberships;
return x.user;
})
.ToList();
LINQ is not intended to be used for modifying data, rather querying data and/or creating new data from data.
Instead, use a nested foreach loop.
First, create a Dictionary to make finding Club objects easier:
var clubDict = clubs.ToDictionary(c => c.Id);
Then, use the clubDict to modify each Membership to include the matching Club entity:
foreach (var user in users.Where(u => u.Memberships != null))
foreach (var membership in user.Memberships)
membership.Club = clubDict[membership.ClubId];

Linq query to select any in list against a list

Using EF Core code-first, and I want to find any record with a similar list of a foreign entities to the entity I already have.
public class ClownModel {
public int Id { get; set; }
public List<CarModel> Cars { get; set; }
}
public class CarModel {
public int Id { get; set; }
}
var MyClown = new ClownModel() { /*add properties*/ }
//or maybe an existing record selected from database, just some ClownModel instance
Basically, "Select all the ClownModels where they have any Cars.Id that are in my MyClown.Cars"
Assuming that ClownModel has unique CarModel Id's, you can use the following query:
Matches All Ids
var ids = MyClown.Cars.Select(c => c.Id).ToList();
var query =
from cm in ctx.ClownModel
where cm.Cars.Where(c => ids.Contains(c.Id)).Count() == ids.Count
select cm;
Matches Any Ids
var ids = MyClown.Cars.Select(c => c.Id).ToList();
var query =
from cm in ctx.ClownModel
where cm.Cars.Where(c => ids.Contains(c.Id)).Any()
select cm;

LINQ to return list of Object filtered on a property of a Child object in nested List<>

I'm looking for some help with a LINQ query to filter on a property/enum of a custom object which is in a nested List, and want to maintain the parent object in return list.
For example/clarity/sample code, I have a parent object, which has in it a List based on class and enum below:
public class Stage {
public String Name { get; set;}
public List<Evaluation> MyEvaluations { get; set;}
}
public class Evaluation {
public float Result { get; set; }
public enumResultType ResultType { get; set; }
}
public enum enumResultType {
A,B,C
}
One can simulate sample data along those lines with something like:
List<Stage> ParentList = new List<Stage>();
Stage Stage1 = new Stage() { Name = "Stage1",
MyEvaluations = new List<Evaluation>() {
new Evaluation() { ResultType = enumResultType.A, Result=5 },
new Evaluation() { ResultType = enumResultType.B, Result=10},
new Evaluation() { ResultType = enumResultType.B, Result=11},
new Evaluation() { ResultType = enumResultType.C, Result=5}
}};
Stage Stage2 = new Stage() { Name = "Stage2",
MyEvaluations = new List<Evaluation>() {
new Evaluation() { ResultType = enumResultType.A, Result=10},
new Evaluation() { ResultType = enumResultType.B, Result=20},
new Evaluation() { ResultType = enumResultType.C, Result=20}}};
ParentList.Add(Stage1);
ParentList.Add(Stage2);
What I want to be able to do, via LINQ, is to select from the Parentlist object, all the items with only a filtered list where the ResultType in the Evaluations List matches a proper condition...
I don't want to repeat the parent object multiple times (seen selectmany), but rather a filtered down list of the MyEvaluations where the ResultType matches, and if this list has items (it would) return it with the parent.
I've played with:
ParentList.Select(x => x.MyEvaluations.FindAll(y => y.ResultType==enumResultType.B)).ToList();
however this returns only the inner list... whereas
ParentList.Where(x => x.MyEvaluations.Any(y => y.ResultType==enumResultType.B)).ToList();
returns ANY.. however I am missing how to get the list of MyEvaluations to be filtered down..
In my Example/sample data, I would like to query ParentList for all situations where ResultType = enumResultType.B;
So I would expect to get back a list of the same type, but without "Evaluation" which are equal to ResultType.A or .C
Based on dummy data, I would expect to be getting something which would have:
returnList.Count() - 2 items (Stage1 / Stage2) and within that Stage1 --> foreach (item.Result : 10, 11 Stage2 --> foreach (item.Result : 20
Can this be done without going to projections in new anonymous types as I would like to keep the list nice and clean as used later on in DataBinding and I iterate over many ResultTypes?
Feel like I'm missing something fairly simple, but fairly new to LINQ and lambda expressions.
Did you try these approaches already? Or is this not what you're looking for ?
//creating a new list
var answer = (from p in ParentList
select new Stage(){
Name = p.Name,
MyEvaluations = p.MyEvaluations.Where(e => e.ResultType == enumResultType.B).ToList()
}).ToList();
//in place replacement
ParentList.ForEach(p => p.MyEvaluations = p.MyEvaluations.Where(e => e.ResultType == enumResultType.B).ToList());

Combining 2 lists and and remove duplicates .Output in a third list .My attempts do not work

I always seem to have a problem when I need to compare 2 list and produce a 3rd list which include all unique items.I need to perform this quite often.
Attempt to reproduce the issue with a noddy example.
Am I missing something?
Thanks for any suggestions
The wanted result
Name= Jo1 Surname= Bloggs1 Category= Account
Name= Jo2 Surname= Bloggs2 Category= Sales
Name= Jo5 Surname= Bloggs5 Category= Development
Name= Jo6 Surname= Bloggs6 Category= Management
Name= Jo8 Surname= Bloggs8 Category= HR
Name= Jo7 Surname= Bloggs7 Category= Cleaning
class Program
{
static void Main(string[] args)
{
List<Customer> listOne = new List<Customer>();
List<Customer> listTwo = new List<Customer>();
listOne.Add(new Customer { Category = "Account", Name = "Jo1", Surname = "Bloggs1" });
listOne.Add(new Customer { Category = "Sales", Name = "Jo2", Surname = "Bloggs2" });
listOne.Add(new Customer { Category = "Development", Name = "Jo5", Surname = "Bloggs5" });
listOne.Add(new Customer { Category = "Management", Name = "Jo6", Surname = "Bloggs6" });
listTwo.Add(new Customer { Category = "HR", Name = "Jo8", Surname = "Bloggs8" });
listTwo.Add(new Customer { Category = "Sales", Name = "Jo2", Surname = "Bloggs2" });
listTwo.Add(new Customer { Category = "Management", Name = "Jo6", Surname = "Bloggs6" });
listTwo.Add(new Customer { Category = "Development", Name = "Jo5", Surname = "Bloggs5" });
listTwo.Add(new Customer { Category = "Cleaning", Name = "Jo7", Surname = "Bloggs7" });
List<Customer> resultList = listOne.Union(listTwo).ToList();//**I get duplicates why????**
resultList.ForEach(customer => Console.WriteLine("Name= {0} Surname= {1} Category= {2}", customer.Name, customer.Surname, customer.Category));
Console.Read();
IEnumerable<Customer> resultList3 = listOne.Except(listTwo);//**Does not work**
foreach (var customer in resultList3)
{
Console.WriteLine("Name= {0} Surname= {1} Category= {2}", customer.Name, customer.Surname, customer.Category);
}
**//Does not work**
var resultList2 = (listOne
.Where(n => !(listTwo
.Select(o => o.Category))
.Contains(n.Category)))
.OrderBy(n => n.Category);
foreach (var customer in resultList2)
{
Console.WriteLine("Name= {0}
Surname= {1}
Category= {2}",
customer.Name,
customer.Surname,
customer.Category);
}
Console.Read();
}
}
public class Customer
{
public string Name { get; set; }
public string Surname { get; set; }
public string Category { get; set; }
}
Couldn't you do this by using the Concat and Distinct LINQ methods?
List<Customer> listOne;
List<Customer> listTwo;
List<Customer> uniqueList = listOne.Concat(listTwo).Distinct().ToList();
If necessary, you can use the Distinct() overload that takes an IEqualityComparer to create custom equality comparisons
The crux of the problem is the Customer object doesn't have a .Equals() implementation. If you override .Equals (and .GetHashCode) then .Distinct would use it to eliminate duplicates. If you don't own the Customer implementation, however, adding .Equals may not be an option.
An alternative is to pass a custom IEqualityComparer to .Distinct(). This lets you compare objects in different ways depending on which comparer you pass in.
Another alternative is to GroupBy the fields that are important and take any item from the group (since the GroupBy acts as .Equals in this case). This requires the least code to be written.
e.g.
var result = listOne.Concat(listTwo)
.GroupBy(x=>x.Category+"|"+x.Name+"|"+x.Surname)
.Select(x=>x.First());
which gets your desired result.
As a rule I use a unique delimiter to combine fields so that two items that should be different don't unexpectedly combine to the same key. consider: {Name=abe, Surname=long} and {Name=abel, Surname=ong} would both get the GroupBy key "abelong" if a delimiter isn't used.
The best option is implement the interface IEqualityComparer and use it within Union or Distinct method as I wrote at the end of this article
http://blog.santiagoporras.com/combinar-listas-sin-duplicados-linq/
Implementation of IEqualityComparer
public class SaintComparer : IEqualityComparer<Saint>
{
public bool Equals(Saint item1, Saint item2)
{
return item1.Name == item2.Name;
}
public int GetHashCode(Saint item)
{
int hCode = item.Name.Length;
return hCode.GetHashCode();
}
}
Use of comparer
var unionList = list1.Union(list2, new SaintComparer());
I had a similar problem where I had two very large lists with random strings.
I made a recursive function which returns a new list with unique strings. I compared two lists with 100k random strings(it may or may not exist duplicates) each with 6 characters of abcdefghijklmnopqrstuvwxyz1234567890 and it was done in about 230 ms. I only measured the given function.
I hope this will give value to someone.
Image of test run
makeCodesUnique(List<string> existing, List<string> newL)
{
// Get all duplicate between two lists
List<string> duplicatesBetween = newL.Intersect(existing).ToList();
// Get all duplicates within list
List<string> duplicatesWithin = newL.GroupBy(x => x)
.Where(group => group.Count() > 1)
.Select(group => group.Key).ToList();
if (duplicatesBetween.Count == 0 && duplicatesWithin.Count == 0)
{
// Return list if there are no duplicates
return newL;
}
else
{
if (duplicatesBetween.Count != 0)
{
foreach (string duplicateCode in duplicatesBetween)
{
newL.Remove(duplicateCode);
}
// Generate new codes to substitute the removed ones
List<string> newCodes = generateSomeMore(duplicatesBetween.Count);
newL.AddRange(newCodes);
makeCodesUnique(existing, newL);
}
else if (duplicatesWithin.Count != 0)
{
foreach (string duplicateCode in duplicatesWithin)
{
newL.Remove(duplicateCode);
}
List<string> newCodes = generateSomeMore(duplicatesWithin.Count);
new.AddRange(newCodes);
makeCodesUnique(existing, newL);
}
}
return newL;
}

How to dynamically choose two fields from a Linq query as a result

If you have a simple Linq query like:
var result = from record in db.Customer
select new { Text = record.Name,
Value = record.ID.ToString() };
which is returning an object that can be mapped to a Drop Down List, is it possible to dynamically specify which fields map to Text and Value?
Of course, you could do a big case (switch) statement, then code each Linq query separately but this isn't very elegant. What would be nice would be something like:
(pseudo code)
var myTextField = db.Customer["Name"]; // Could be an enumeration??
var myValueField = db.Customer["ID"]; // Idea: choose the field outside the query
var result = from record in db.Customer
select new { Text = myTextField,
Value = myValueField };
Right way to do this is with closures.
Func<Customer, string> myTextField = (Customer c) => c["Name"];
Func<Customer, int> myValueField = (Customer c) => c["ID"];
var result = from record in db.Customer
select new { Text = myTextField(record),
Value = myValueField(record) };
The one limitation is that your definition of myTextField always needs to return a string.
You could try something like
class Customer
{
public int ID { get; set; }
public string Name { get; set; }
public string Surname { get; set; }
}
var dict = new Dictionary<string, Func<Customer, string>>
{ { "ID", (Customer c) => c.ID.ToString() },
{ "Name", (Customer c) => c.Name},
{ "Surname", (Customer c) => c.Surname } };
List<Customer> rows = new List<Customer>();
rows.Add(new Customer { ID = 1, Name = "Foo", Surname = "Bar"});
var list = from r in rows
select new { Text = dict["ID"](r), Value = dict["Name"](r) };
To try to access the properties dynamically, you could try something like
var dict = new Dictionary<string, Func<Customer, string>>
{ { "ID", (Customer c) => c.GetType().GetProperty("ID").GetValue(c,null).ToString() },
{ "Name", (Customer c) => c.GetType().GetProperty("Name").GetValue(c,null).ToString()},
{ "Surname", (Customer c) => c.GetType().GetProperty("Surname").GetValue(c,null).ToString() } };

Resources