Reputation: 822
I have 2 lists of the same type of object, Student
:
public class Student {
public string StudentId { get; set; }
public string Name { get; set; }
public string Address { get; set; }
public string PostCode { get; set; }
}
I have 2 lists of StudentData
objects - last year's list and this year's. I want to find the data where the Name, Address or Postcode of the student has changed. However, I am struggling with the correct LINQ query. Here's what I have tried so far:
public DataChange<StudentData>[] Compare(StudentData[] previous, StudentData[] current) {
Guard.AgainstNull(previous, nameof(previous));
Guard.AgainstNull(current, nameof(current));
var updatedName = previous
.Where(d => current.Select(d2 => d2.StudentId )
.Contains(d.StudentId )).Where(d => !current.Select(d2 => d2.Name)
.Contains(d.Name)).Where(d => current.Select(d2 => d2.Address)
.Contains(d.Address)).Select(DataChange<StudentData>.Updated).ToList();
var updatedAddress = previous.Where(d => current
.Select(d2 => d2.StudentId )
.Contains(d.StudentId ))
.Where(d => !current.Select(d2 => d2.Address)
.Contains(d.Address))
.Where(d => current.Select(d2 => d2.Name)
.Contains(d.Name)).Select(DataChange<StudentData>.Updated).ToList();
var updatedNameAndAddress = previous
.Where(d => current.Select(d2 => d2.StudentId )
.Contains(d.StudentId )).Where(d => !current.Select(d2 => d2.Address)
.Contains(d.Address)).Where(d => !current.Select(d2 => d2.Name).Contains(d.Name))
.Select(DataChange<StudentData>.Updated).ToList();
}
The reason I've done it this way is to cater for these scenarios:
However, this does not pass the test case where a student's name has been updated from say "Fred Blogs" to "Fred Blogger", or the address from "123 Address" to "123 updated".
Can anyone please point me in the right direction to get the correct LINQ query / a smoother way of doing this please?
Upvotes: 0
Views: 594
Reputation: 450
Using GroupJoin:
return current.GroupJoin(
previous,
c => c.StudentId,
p => p.StudentId,
(c, ps) => {
if (ps?.Any() != true)
return DataChange<StudentData>.New();
// otherwise, ps should contain single value
// calculate the change in any appropriate way
return DataChange<StudentData>.FromOldAndNew(ps.First(), c);
}
)
// assuming null is returned for similar values
.Where(x => x is not null) // C# 9 feature, otherwise use !(x is null) or (x != null)
.ToArray();
Upvotes: 0
Reputation: 37020
If you want to find students where the name is different but not the address, then the logic is fairly simple:
StudentId
This can be repeated for the address being different but not the name, and for different name AND different address. In code it would look something like:
List<StudentData> updatedNames = previous.Where(prev => current.Any(cur =>
prev.StudentId == cur.StudentId && // Same student
prev.Name != cur.Name && // Different name
prev.Address == cur.Address)) // Same address
.ToList();
List<StudentData> updatedaddresses = previous.Where(prev => current.Any(cur =>
prev.StudentId == cur.StudentId && // Same student
prev.Name == cur.Name && // Same name
prev.Address != cur.Address)) // Different address
.ToList();
List<StudentData> updatedNameAndAddresses = previous.Where(prev => current.Any(cur =>
prev.StudentId == cur.StudentId && // Same student
prev.Name != cur.Name && // Different name
prev.Address != cur.Address)) // Different address
.ToList();
Upvotes: 1
Reputation: 9499
What's wrong with something like this?
public DataChange<StudentData>[] Compare(StudentData[] previous, StudentData[] current) {
Guard.AgainstNull(previous, nameof(previous));
Guard.AgainstNull(current, nameof(current));
var updated = previous.Where(x => {
var newOne = current.FirstOrDefault(n => n.StudentId == x.StudentId);
if (newOne == null) {
return true; // this is considered a change, not found in 'current', but practically it is a 'delete'
}
return x.Address != newOne.Address || x.Name != newOne.Name;
}).Select(DataChange<StudentData>.Updated).ToList();
}
Upvotes: 2