简体   繁体   中英

Using LINQ to compare 2 lists of objects for updated properties

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:

  1. Where the name has been updated but not the address
  2. Where the address has been updated but not the name

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?

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

If you want to find students where the name is different but not the address, then the logic is fairly simple:

  1. Find a match on StudentId
  2. Check if the names are the different
  3. Check if the addresses are the same

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

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

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM