Duplicate Entity Check Before Inserting Into Collection / DB

Typical Use Case

 
Functional
 
You have a collection of Patients. Before inserting a new Patient, you need to check if there already exists a past record of the same Patient. The Patient is considered already existing if there is a matching Government provided ID (for example SSN, Passport, etc.) Or a combination of First Name, Last Name, and DOB.
 
Note
The above functional requirement is not an absolute one in the real world. It is just for illustration purposes.
 
Let's say we have a very simple domain entity class of the Patient. For brevity purpose, I am not decorating the attributes of Required or null coalescing
  1. public class Patient
  2. {  
  3.     public long PatientID { getset; }  
  4.     public string FirstName { getset; }  
  5.     public string LastName { getset; }  
  6.     public DateTime DOB { getset; }  
  7.     public string GovtID { getset; }  
  8. }  
Now to build our custom comparison for the duplicate check, we would make the <Patient> class inherit from the interface of IEquatable.
  1. public class Patient : IEquatable<Patient>  
This will enforce to implement and override the Equals method. I have added another property for returning the related error message in case there is a duplicate record found. In order to enhance the "equal to" and "not equal to" operators, override them as well. Thus the class will now look like,
  1. public class Patient : IEquatable<Patient>    
  2. {    
  3.     public long PatientID { getset; }    
  4.     public string FirstName { getset; }    
  5.     public string LastName { getset; }    
  6.     public DateTime DOB { getset; }    
  7.     public string GovtID { getset; }   
  8.     //This property will contain value only if there is a duplicate found.  
  9.     public string DuplicateEntityFoundMessage { getset; }    
  10.   
  11.     //The custom EQUALS method is amended to provide the custom comparison  
  12.     // First comparison is made on the GovtID and the 2nd one on a combimation of Names and DOB  
  13.     // The returning message is different in both cases.  
  14.     public bool Equals(Patient other)    
  15.     {    
  16.         bool response = false;    
  17.         other.DuplicateEntityFoundMessage = string.Empty;    
  18.     
  19.         if (other == null)    
  20.             response = false;    
  21.     
  22.         if (this.GovtID == other.GovtID)    
  23.         {    
  24.             response = true;    
  25.             other.DuplicateEntityFoundMessage = "Patient already exists with the same GovtID";    
  26.         }    
  27.         else if (this.FirstName == other.FirstName && this.LastName == other.LastName && this.DOB == other.DOB)    
  28.         {    
  29.             response = true;    
  30.             other.DuplicateEntityFoundMessage = "Patient already exists with the same First Name, Last Name and DOB";    
  31.         }    
  32.     
  33.         return response;    
  34.     }    
  35.     
  36.     //Overriding the operator ==  
  37.     public static bool operator == (Patient person1, Patient person2)    
  38.     {    
  39.         if (((object)person1) == null || ((object)person2) == null)    
  40.             return Object.Equals(person1, person2);    
  41.     
  42.         return person1.Equals(person2);    
  43.     }    
  44.     //Overriding the operator !=  
  45.     public static bool operator != (Patient person1, Patient person2)    
  46.     {    
  47.         if (((object)person1) == null || ((object)person2) == null)    
  48.             return !Object.Equals(person1, person2);    
  49.     
  50.         return !(person1.Equals(person2));    
  51.     }    
  52. }   
Now to the client class. Here I have taken a simple Console program to illustrate the processing. Also, I am creating four subject patients to test the changes that we made in the Entity class.
  1. //patient 1: This will be the first patient added to the collection.  
  2. Patient patient1 = new Patient() { FirstName = "John",  
  3.                                    LastName = "Doe",  
  4.                                    GovtID = "ASDF12345",  
  5.                                    DOB = Convert.ToDateTime("12-Mar-1998")  
  6.                                  };  
  7. //patient 2: This patient will also be added as none of the comparison criteria matches  
  8. Patient patient2 = new Patient() { FirstName = "David",  
  9.                                    LastName = "De",  
  10.                                    GovtID = "ASDF123458",  
  11.                                    DOB = Convert.ToDateTime("12-Mar-1998")  
  12.                                  };   
  13. //patient 3: This patient has the same Govt. ID as the patient 1. So should not be added to the collection.  
  14. Patient patient3 = new Patient() { FirstName = "Sam",  
  15.                                    LastName = "Dickson",  
  16.                                    GovtID = "ASDF12345",  
  17.                                    DOB = Convert.ToDateTime("12-Mar-1995")  
  18.                                  };   
  19. //patient 4: This patient has the same Names and DOB as patient 1. So should not be added to the collection.  
  20. Patient patient4 = new Patient() { FirstName = "John",  
  21.                                    LastName = "Doe",  
  22.                                    GovtID = "ASDF1234523",  
  23.                                    DOB = Convert.ToDateTime("12-Mar-1998")  
  24.                                  };   
Let's complete the rest of the code to just test our concepts.
  1. class Program    
  2. {    
  3.     static void Main(string[] args)    
  4.     {    
  5.         Patient patient1 = new Patient() { FirstName = "John", LastName = "Doe", GovtID = "ASDF12345", DOB = Convert.ToDateTime("12-Mar-1998") };    
  6.         Patient patient2 = new Patient() { FirstName = "David", LastName = "De", GovtID = "ASDF123458", DOB = Convert.ToDateTime("12-Mar-1998") };    
  7.         Patient patient3 = new Patient() { FirstName = "Sam", LastName = "Dickson", GovtID = "ASDF12345", DOB = Convert.ToDateTime("12-Mar-1995") };    
  8.         Patient patient4 = new Patient() { FirstName = "John", LastName = "Doe", GovtID = "ASDF1234523", DOB = Convert.ToDateTime("12-Mar-1998") };    
  9.     
  10.         //Now create a collection of Patients    
  11.     
  12.         List<Patient> allPatients = new List<Patient>();    
  13.         if (allPatients.Contains(patient1))    
  14.             Console.WriteLine("Patient 1:" + patient1.DuplicateEntityFoundMessage);    
  15.         else    
  16.             allPatients.Add(patient1);   
  17.   
  18.         if (allPatients.Contains(patient2) == true)    
  19.             Console.WriteLine("Patient 2:" + patient2.DuplicateEntityFoundMessage);    
  20.         else    
  21.             allPatients.Add(patient2);   
  22.   
  23.         if (allPatients.Contains(patient3))    
  24.             Console.WriteLine("Patient 3:" + patient3.DuplicateEntityFoundMessage);    
  25.         else    
  26.             allPatients.Add(patient3);   
  27.   
  28.         if (allPatients.Contains(patient4))    
  29.             Console.WriteLine("Patient 4:" + patient4.DuplicateEntityFoundMessage);    
  30.         else    
  31.             allPatients.Add(patient4);    
  32.          
  33.         //the following is just an additional piece of code to demonstrate the != and == operator that we have overridden.  
  34.         if (patient1 != patient2)    
  35.         {    
  36.             Console.WriteLine("patient 1 and 2 are not same");    
  37.     
  38.         }    
  39.         if (patient1 == patient3)    
  40.         {    
  41.             Console.WriteLine("patient 1 and patient 3: " + patient3.DuplicateEntityFoundMessage);    
  42.     
  43.         }    
  44.         Console.ReadLine();    
  45.     }    
  46. }   
The final output on the console,
  1. Patient 3:Patient already exists with the same GovtID    
  2. Patient 4:Patient already exists with the same First Name, Last Name and DOB    
  3. patient 1 and 2 are not same    
  4. patient 1 and patient 3: Patient already exists with the same GovtID   
Patient 1 and Patient 2 have been successfully added to the collection.
 

Conclusion

 
There is a number of ways to achieve the above objective. You can use the Lambda Where<> on the Collection List or even used simple if's in the client itself. However, in the real world of entities where you have a large number of permutations and combinations of attributes that can be a candidate for the comparison, the code gets messier when you do it in the client-side code. Another aspect is code quality. It reduces the cognitive complexity of the client function and achieves the single responsibility design principle for the class/method.
  1. Professional looking code
  2. Improves testability. Thus reducing Cyclomatic Complexity
  3. Improves Readability: Thus reducing Cognitive Complexity
  4. Implements Single Responsibility design principle. (from S.O.L.I.D)
 Thank You! stay safe, stay blessed !!