Legacy Code and the missing LINQ.

Whilst porting a legacy Visual Basic 6 screen to .NET (I know, it still happens!) I came across a horrendously complex piece of code used to refresh a list of people (whose details may or may not have been verified by the user) with new data possibly. In essence quite a simple job, if the person had already been verified then we need to mark them as such after the refresh SO LONG AS their data has not changed in the meantime; as with all things there are of course more complexities but this is in a nutshell the problem which in terms of VB6 code costed large amounts of comparison coding and collections of state information, It is also true that we were building on a live system and so you inherit all the goodness and badness that you’ve coded thus far.

Now however things are different, we have the wonderful world of .NET and the awesome world of LINQ, a technology that in my experience kills of all that ‘coding driftwood’ that accrues over time and development cycles. So how hard could it be?

As it turns out, not hard at all, firstly we need to work out what the user has currently verified, a simple piece of linq should suffice here building up a ‘concrete’ list of the selections made by the user from the data within the existing dataset.

      var cache = (from ex in contactDS.contacts 
                    where ex.RowState == DataRowState.Modified 
                      && (ex.Verified == true || ex.Referred == true) 
                       select new SelectedVerificationItems() {verified = ex.Verified, 
                                                          referred = ex.Referred, 
                                                          id= ex.Executive_Code, 
                                                          updateID = ex.UpdateID,
                                                          dataUnlocked=ex.DataUnlocked}).ToList();

The reason we make the results concrete (by using the toList() function) is that the following line of code refreshes the same dataset and if we were to use ‘deferred execution’, as linq generally is, we would have lost the results with the reload. The psuedo code for the reload is as follows:-

      using (VerificationHandler handler = new VerificationHandler(connectionString)) {
        handler.getVerificationRecords(contactDS, ...Extra Params...);
      }

Once the edited data has been reloaded we need to build up a set of convergent data, that is, data from the selections the user has already made that also exist within the refreshed data (we’re not worried about data that was marked for verification but not bought back on the refresh). The following code uses the supremely useful join method to join two disparate collections of data together on a common key:-

      var convergences = contactDS.contacts.Join(
        cache,
        contact=> new SelectedVerificationItemsKey(){id= contact.Executive_Code, updateID=contact.UpdateID},
        selection=>new SelectedVerificationItemsKey(){id= selection.id, updateID=selection.updateID}, 
        (contact,selection)=> new {selection = selection, contact= contact},
        new SelectedVerifiedItemsComparator());

As you can see from the above code using the contacts data table we perform a join onto the cache list (argument 1) of selections made. The second and third arguments would ordinarily be a very simple lambda statement defining the fields to join onto (eg exec=> exec.id and selection=> selection.id respectively). However in this instance things are not quite that simple because as well as comparing id’s we also need to ensure that the updateid’s (which determine if another user has edited the details) match. We thus create a simple key object which contains both the ID and the Update ID. The fourth argument is another lambda statement detailing the objects to construct with the results of our join expression; in this instance we want an object containing the SelectionVerificationItems object and the DataRow pertaining to it. Finally the fifth argument, not always required in a join statement, is a SelectedVerifiedItemsComparator class derived from the frameworks  EqualityComparer abstract class. This is used  to compare the SelectionVerificationItemsKey objects and can be as simple or as complex as you require. The definitions for these classes are defined below:-

  public class SelectedVerificationItems {
    public int id { get; set; }
    public bool verified { get; set; }
    public bool referred { get; set; }
    public int updateID { get; set; }
    public bool dataUnlocked { get; set; }
  }

  public class SelectedVerificationItemsKey {
    public int updateID { get; set; }
    public int id { get; set; }
  }
  public class SelectedVerifiedItemsComparator:EqualityComparer<SelectedVerificationItemsKey> {
    public override int GetHashCode(SelectedVerificationItemsKey obj) {
      return obj.GetHashCode();
    }
    public override bool Equals(SelectedVerificationItemsKey x, SelectedVerificationItemsKey y) {
      return x.id == y.id && x.updateID == y.updateID;
    }
  }

As you can see the SelectedVerifiedItemsComparator neatly in one atomic operation compares the id and the update id, therefore if the data remains unedited by another user and the current user has verified the details previously we will return this row as verified. This just leaves the resynchronisation of this verification data performed thus in a nice tidy loop:-

      foreach (var convergence in convergences) {

        convergence.contact.unlocked = convergence.selection.unlocked;
        convergence.contact.verified = convergence.selection.verified;
        convergence.contact.referred = convergence.selection.referred;

     }

The result is code which will be eminently more readable, robust and will lend itself much more readily to refactoring should the need arise. I know it’s hardly rocket science but in truth I struggled to find good examples of the Join functions being used with the non default EqualityComparer and so this is my attempt to put this right.

  1. Pingback: My Homepage

Leave a Reply

Your email address will not be published. Required fields are marked *