Compare two lists of object for new, changed, updated on a specific property

I've been trying and failing for a while to find a solution to compare to lists of objects based on a property of the objects. I've read other similar solutions but they were either not suitable (or I didn't understand the answer!).

Code is C#

I have a model that represents an image

public class AccommodationImageModel
{
    public int Id { get; set; }
    public string Path { get; set; }
    public string Caption { get; set; }
    public string Description { get; set; }
    public bool CoverImage { get; set; }
    public bool Visible { get; set; }     
}

I have two lists of this model. One is the existing list, another is an updated list. I need to compare the two lists to see which have been removed, updated or are new.

I don't need to compare the whole object, just compare them on their property Id.

List<AccommodationImageModel> masterList;
List<AccommodationImageModel> compareList;
New

If compareList contains any AccommodationImageModel with Id=0 then they are new because new entries do not have an Id assigned yet.

To be deleted

If masterList contains any AccommodationImageModel with Ids that are Not in compareList then they are to be deleted, because they have been removed from the compareList and should be removed from the masterList. Therefore I need a list of the ones that need to be deleted.

To be updated

If newList and masterList have Id's that are the same then they are to be updated. Therefore I need a list of the ones that share the same ID, so I can them update them. I'm not too concerned if these models are identical and don't need updating as there will only be a small number per list so it doesn't matter much if they get updated even if they haven't changed.

Each of the three results needs to be returned as a List of AccommodationImageModel so that I can then carry out the appropriate update, remove, add.

Edit

I've added 3 test methods below with my chosen solution from ATM, showing its working implementation.

Test methods
[TestMethod]
    public void Test_Deleted_Image()
    {
        // set up the masterList
        List<AccommodationImageModel> masterList = new List<AccommodationImageModel>();
        masterList.Add(new AccommodationImageModel { Id = 1 });
        masterList.Add(new AccommodationImageModel { Id = 2 });

        // set up the compare list
        List<AccommodationImageModel> compareList = new List<AccommodationImageModel>();
        compareList.Add(new AccommodationImageModel { Id = 1 });
        compareList.Add(new AccommodationImageModel { Id = 3 });
        compareList.Add(new AccommodationImageModel { Id = 0 });

        // get the deleted models
        List<AccommodationImageModel> result = masterList.Where(c => !compareList.Any(d => d.Id == c.Id)).ToList();

        // result should hold first model with id 2
        Assert.AreEqual(2, result.FirstOrDefault().Id);
    }

    [TestMethod]
    public void Test_Added_Image()
    {
        // set up the masterList
        List<AccommodationImageModel> masterList = new List<AccommodationImageModel>();
        masterList.Add(new AccommodationImageModel { Id = 1 });
        masterList.Add(new AccommodationImageModel { Id = 2 });

        // set up the compare list
        List<AccommodationImageModel> compareList = new List<AccommodationImageModel>();
        compareList.Add(new AccommodationImageModel { Id = 1 });
        compareList.Add(new AccommodationImageModel { Id = 3 });
        compareList.Add(new AccommodationImageModel { Id = 0 });

        // get the added models
        List<AccommodationImageModel> result = compareList.Where(c => c.Id == 0).ToList();

        // result should hold first model with id 0
        Assert.AreEqual(0, result.FirstOrDefault().Id);
    }

    [TestMethod]
    public void Test_Updated_Image()
    {
        // set up the masterList
        List<AccommodationImageModel> masterList = new List<AccommodationImageModel>();
        masterList.Add(new AccommodationImageModel { Id = 1 });
        masterList.Add(new AccommodationImageModel { Id = 2 });

        // set up the compare list
        List<AccommodationImageModel> compareList = new List<AccommodationImageModel>();
        compareList.Add(new AccommodationImageModel { Id = 1 });
        compareList.Add(new AccommodationImageModel { Id = 3 });
        compareList.Add(new AccommodationImageModel { Id = 0 });

        // get the updated models
        List<AccommodationImageModel> result = masterList.Where(c => compareList.Any(d => c.Id == d.Id)).ToList();

        // result should hold first model with id 1
        Assert.AreEqual(1, result.FirstOrDefault().Id);
    }

Answers


Simple Linq

New

List<AccommodationImageModel> new = compareList.Where(c=>c.id==0).ToList();

To be deleted

List<AccomodationImageModel> deleted = masterList.Where(c => !compareList.Any(d => c.id == d.id)).ToList();

To be updated

List<AccomodationImageModel> toBeUpdated = masterList.Where(c => compareList.Any(d => c.id == d.id)).ToList();

Assuming that two models with the same Id are considered the same model, you can write a IEqualityComparer like this:

public class AccommodationImageModelComparer : IEqualityComparer<AccommodationImageModel>
{
    public bool Equals(AccommodationImageModel x, AccommodationImageModel y)
    {
        if(x == null && y == null)
           return true;

        return x.Id == y.Id;
    }

    public int GetHashCode(AccommodationImageModel model)
    {
        return model.Id.GetHashCode();
    }
}

You can then use Linq to get the lists that you want:

var comparer = new AccommodationImageModelComparer();

var newItems = compareList.Where (l => l.Id == 0).ToList();
var toBeDeleted = masterList.Except(compareList, comparer).ToList();
var toBeUpdated = masterList.Intersect(compareList, comparer).ToList();

The first one just filters the items with an Id of 0, which are conisdered new. The second query returns the items in the masterList which are not in the compareList. The last query returns the items which are in both lists. This code compiles but is untested.


One simple approach would be to override the == operator in yourAccomodationImageModel as such:

public static override bool operator ==(AccommodationImageModel a, AccommodationImageModel b)
{
    return a.Id == b.Id;
}

Then when comparing just check the master list against the comparer list and remove those in the master list that don't have an identical object in the comparer list:

List<AccomodationImageModel> rem = new List<AccomodationImageModel>;
List<AccomodationImageModel> newobj = new List<AccomodationImageModel>;
foreach(AccomodationImageModel a in compareList) {
          if(a.Id == 0) { // id == 0 => new item
                 newobj.Add(a); // add new item later
          } else {
                 // check those existing items as to whether they need to be updated or removed
                 bool match = false;
                 foreach(AccomodationImageModel b in masterList) {
                      if(a == b) match = true; // match found
                 }
                 if(!match) rem.Add(a); else Update(a); // will be removed or updated
          }
}
// now remove unmatched items
foreach(AccomodationImageModel a in rem) { masterList.Remove(a); }
foreach(AccomodationImageModel a in newobj) { AddNew(a); }

Note Update(AccomodationImageModel a) is your method to update a certain item and AddNew(AccomodationImageModel a) is your method of insterting a new item in the master list.

Also as you may have noteiced removing from and inserting to the master list should be done after you have looped the master list!


    ///If image is not in list then add the image to the list

public void AddNew (List<AccomodationImageModel> masterList, AccomodationImageModel theImage)
{
    masterList.Add(theImage);
}

    /// If Image is in List then change listitem with new one

public void Update (List<AccomodationImageModel> masterList, int OldOnesID, AccomodationImageModel theNew)
{
    masterList[OldOnesID] = theNew;
}

    /// If Image should delete then removes the image from list

public void Delete (List<AccomodationImageModel> imgList, AccomodationImageModel theImageToDelete)
{
    masterList.Remove(theImageToDelete);
}


    /// this method checks the image state and do the work

public void CheckState (List<AccommodationImageModel> masterList, AccomodationImageModel theImage, bool deleteIt)
{


       for(int i = 0; i < masterList.Count; i++)
       {

         if (deleteIt)
         {
            Delete(masterList, theImage);
         }

         else
         {
           if(theImage.ID == 0)
           {
             AddNew(masterList, theImage);
           }

           if(masterList[i].ID == theImage.ID)
           {
             Update(masterList, i, theImage);
           }
       }
}

If you prefer use 2 lists as a master and Temporary list, then you can simply iterate your temporary list and each of templist item use CheckState method..

Hope this helps..


Need Your Help

General Unicode/UTF-8 support for csv files in Python 2.6

python csv unicode utf-8 python-2.x

The csv module in Python doesn't work properly when there's UTF-8/Unicode involved. I have found, in the Python documentation and on other webpages, snippets that work for specific cases but you ha...

JavaScript switch with logical operators?

javascript switch-statement

for (var count = 1; count &lt; 6; count++) {