Friday 11 June 2010

Some refactoring using E4 POCO with generics

Here is a story around refactoring some switch statements using polymorphism and some repetitive code using some generics to produce DRY code.

We has a smell in our EarthquakeSettingsLegacy class that had a switch statements that needs some refactoring to use a polymorphic approach

   private void GetVulnerability(ModelRun modelRun)
        {

            ModelType modelType;
            modelType = (ModelType)modelRun.ModelId;

            List<Ccp.Entities.VulnerabilityAggregate> vulnerabilityAggregates = databaseContext
                .Query<VulnerabilityAggregate>()
                .Where(v => v.ModelRunId.HasValue && v.ModelRunId.Value.Equals(modelRun.Id)).ToList();

            switch (modelType)
            {
                case ModelType.EarthquakeCanada:
                    GetCanadaVulnerability(modelRun, vulnerabilityAggregates);

                    break;
                case ModelType.EarthquakeMexico:
                    GetMexicoVulnerability(modelRun, vulnerabilityAggregates);
                    break;

                case ModelType.EarthquakeAfricaIndia:
                    GetAfricaToIndiaVulnerability(modelRun, vulnerabilityAggregates);
                    break;

                case ModelType.EarthquakeJapan:
                    GetJapanVulnerability(modelRun, vulnerabilityAggregates);
                    break;

                case ModelType.EarthquakeSouthAmerica:
                    GetSouthAmericaVulnerability(modelRun, vulnerabilityAggregates);
                    break;

                default:
                    return;
            }

The first step was to make the following more generic. The challenge is that we are using auto generate POCO classes, in other words we cannot simply make the classes inherit from a base class that we could use as a compile time type with generics. Another complication was that we need to use a compile time type as apposed to a run time type. Because Generics are place holders for types that are compiled at compile time.

        private void GetSouthAmericaVulnerability(ModelRun modelRun, List<Ccp.Entities.VulnerabilityAggregate> vulnerabilityAggregates)
        {
            List<Ccp.Entities.Earthquake.SouthAmericaVulnerabilityCatalogue> vaList = vulnerabilityAggregates
                .Select<Ccp.Entities.VulnerabilityAggregate, Ccp.Entities.Earthquake.SouthAmericaVulnerabilityCatalogue>(
                va => EntityMapper.Map<Ccp.Entities.VulnerabilityAggregate, Ccp.Entities.Earthquake.SouthAmericaVulnerabilityCatalogue>(va))
                .ToList();

            //Earthquake handles vulnerability functions as strings where as Tropical Cyclone handles them as integers. Therefore we need to
            //have some special code to map these together

            var result = from av in this.databaseContext.GetAll<VulnerabilityAggregate>().Where(z => z.ModelRunId.Value.Equals(modelRun.Id))
                         join vf in this.databaseContext.GetAll<VulnerabilityFunction>()
                         on av.VulnerabilityFunctionId equals vf.Id
                         select new { av.VulnerabilityFunctionId, vf.VulnerabilityFunctionString };

            var dic = result.Select(p => new { p.VulnerabilityFunctionId, p.VulnerabilityFunctionString })
                            .Distinct()
                            .AsEnumerable()
                            .ToDictionary(k => k.VulnerabilityFunctionId, v => v.VulnerabilityFunctionString);

            for (int i = 0; i < vulnerabilityAggregates.Count; i++)
            {
                String Vulnerability;
                int? VulnerabilityFunctionId = vulnerabilityAggregates[i].VulnerabilityFunctionId;
                if (dic.TryGetValue(VulnerabilityFunctionId.Value, out Vulnerability))
                {
                    List<string> vulnerabilityCoeficients;
                    vulnerabilityCoeficients = Vulnerability.Split(' ').ToList<string>();

                    vaList[i].A = Convert.ToDouble(vulnerabilityCoeficients[0]);
                    vaList[i].B = Convert.ToDouble(vulnerabilityCoeficients[1]);
                    vaList[i].C = Convert.ToDouble(vulnerabilityCoeficients[2]);
                    vaList[i].VulnerabilityFunction = Vulnerability;
                }
            }

            if (vulnerabilityAggregates.Count > 0)
            {
                using (this.databaseContextEarthquake.BeginTransaction())
                {
                    this.databaseContextEarthquake.DeleteAll<Ccp.Entities.Earthquake.SouthAmericaVulnerabilityCatalogue>();
                    this.databaseContextEarthquake.Add(vaList.ToArray());
                    this.databaseContextEarthquake.Save();

                    this.databaseContextEarthquake.CommitTransaction();
                }
            }
        }

We translated the above into the following generic function. To make the POCO DAL entities work TDestination needs to inherit from class and implement a IVulnerabilityParameter interface

        public void GetVulnerability<TDestination>(ModelRun modelRun, List<Ccp.Entities.VulnerabilityAggregate> vulnerabilityAggregates)
            where TDestination : class, IVulnerabilityParameters
        {
            List<TDestination> vaList = vulnerabilityAggregates
                .Select<Ccp.Entities.VulnerabilityAggregate, TDestination>(
                va => EntityMapper.Map<Ccp.Entities.VulnerabilityAggregate, TDestination>(va))
                .ToList();

            //Earthquake handles vulnerability functions as strings where as Tropical Cyclone handles them as integers. Therefore we need to
            //have some special code to map these together

            var result = from av in this.databaseContext.GetAll<VulnerabilityAggregate>().Where(z => z.ModelRunId.Value.Equals(modelRun.Id))
                         join vf in this.databaseContext.GetAll<VulnerabilityFunction>()
                         on av.VulnerabilityFunctionId equals vf.Id
                         select new { av.VulnerabilityFunctionId, vf.VulnerabilityFunctionString };

            var dic = result.Select(p => new { p.VulnerabilityFunctionId, p.VulnerabilityFunctionString })
                            .Distinct()
                            .AsEnumerable()
                            .ToDictionary(k => k.VulnerabilityFunctionId, v => v.VulnerabilityFunctionString);

            for (int i = 0; i < vulnerabilityAggregates.Count; i++)
            {
                String Vulnerability;
                int? VulnerabilityFunctionId = vulnerabilityAggregates[i].VulnerabilityFunctionId;
                if (dic.TryGetValue(VulnerabilityFunctionId.Value, out Vulnerability))
                {
                    List<string> vulnerabilityCoeficients;
                    vulnerabilityCoeficients = Vulnerability.Split(' ').ToList<string>();

                    vaList[i].A = Convert.ToDouble(vulnerabilityCoeficients[0]);
                    vaList[i].B = Convert.ToDouble(vulnerabilityCoeficients[1]);
                    vaList[i].C = Convert.ToDouble(vulnerabilityCoeficients[2]);
                    vaList[i].VulnerabilityFunction = Vulnerability;
                }
            }

            if (vulnerabilityAggregates.Count > 0)
            {
                using (this.databaseContextEarthquake.BeginTransaction())
                {
                    this.databaseContextEarthquake.DeleteAll<TDestination>();
                    this.databaseContextEarthquake.Add(vaList.ToArray());
                    this.databaseContextEarthquake.Save();

                    this.databaseContextEarthquake.CommitTransaction();
                }
            }
        }

The interface looks like:
namespace Ccp.Entities.Earthquake
{
    /// <summary>
    /// All the earthquake vulnerability catalogues need to implement this interface for the settings.
    /// </summary>
    public interface IVulnerabilityParameters
    {
        string VulnerabilityFunction { get; set; }
        Nullable<double> A { get; set; }
        Nullable<double> B { get; set; }
        Nullable<double> C { get; set; }
    }
}

Next we created to partial classes for each entity that we need to process.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace Ccp.Entities.Earthquake
{
    public partial class SouthAmericaVulnerabilityCatalogue : IVulnerabilityParameters
    {
    }
}

This looks a little strange but the implementation of IVulnerabilityParameters is in the partial class of the POCO DAL.

We can continue to refactor the 4 link statements into one using AsEnumarble. The difference between Linq and sql is that the where clause is not used in the same way. In SQL the where clause would explicitly join a foreign key to a primary key. In Linq we want to associate to another entity the relationship v.VulnerabilityFunction, in which case you need to include any property from the child entity. In the case below this is a VulnerabilityFunctionString (as apposed to Id). The emitted sql includes an outer join. In this case the relationship is 0-1 to many which means the outer join is not so bad

            List<Ccp.Entities.TropicalCyclone.VulnerabilityAggregate> vaList =
                databaseContext
                    .Query<VulnerabilityAggregate>()
                    .Where(v => v.ModelRunId.HasValue && v.ModelRunId.Value.Equals(modelRun.Id) && v.VulnerabilityFunction.VulnerabilityFunctionString != "")
                    .AsEnumerable()
                    .Select<Ccp.Entities.VulnerabilityAggregate, Ccp.Entities.TropicalCyclone.VulnerabilityAggregate>(
                        va =>
                            {
                                var vae = EntityMapper.Map<Ccp.Entities.VulnerabilityAggregate, Ccp.Entities.TropicalCyclone.VulnerabilityAggregate>(va);
                                vae.VfId = Convert.ToInt32(va.VulnerabilityFunction.VulnerabilityFunctionString);
                                return vae;
                            })
                    .ToList();

Here’s a book recommendations for creating linq queries:
http://www.amazon.de/LINQ-Pocket-Reference-OReilly/dp/0596519249/ref=sr_1_4?ie=UTF8&s=books-intl-de&qid=1276066598&sr=1-4