Search code examples
c#generics.net-corerefactoring

Is there any way to make common function out of these two blocks


I have two blocks; in each block, I am retrieving the data from a table using command and reader and transforming the data, and then updating the same. What I am looking to extract the common function by passing the table name, type, and transform function to that function from these two blocks

Below is the code for the same,

// Block #1

using var queryProjectSteamSystemsCommand = dbContext.Database.GetDbConnection().CreateCommand();
queryProjectSteamSystemsCommand.CommandText = @"SELECT ""Id""::varchar, ""InitialObject""::varchar from ""DesignHubProjectSteamSystems""";
using var steamSystemProjectsReader = queryProjectSteamSystemsCommand.ExecuteReader();

if (steamSystemProjectsReader.HasRows)
{
    while (steamSystemProjectsReader.Read())
    {
        var id = steamSystemProjectsReader.IsDBNull(0) ? Guid.Empty : Guid.Parse(steamSystemProjectsReader.GetString(0));
        var steamSystemJson = steamSystemProjectsReader.IsDBNull(1) ? "null" : steamSystemProjectsReader.GetString(1);
        var projSteamSystemInitialObj = JsonConvert.DeserializeObject<OldSteamSystem>(steamSystemJson);
        string json = JsonConvert.SerializeObject(TransformProjectSteamSystem(projSteamSystemInitialObj)).Replace("'", "''", StringComparison.Ordinal);
        migrationBuilder.Sql($"UPDATE \"DesignHubProjectSteamSystems\"  SET \"InitialObject\" = '{json}'::jsonb WHERE \"Id\" = '{id}'");
    }
}
steamSystemProjectsReader.Close();

// Block #2

using var queryProjectFuelSystemsCommand = dbContext.Database.GetDbConnection().CreateCommand();
queryProjectFuelSystemsCommand.CommandText = @"SELECT ""Id""::varchar, ""InitialObject""::varchar from ""DesignHubProjectFuelSystems""";
using var fuelSystemProjectsReader = queryProjectFuelSystemsCommand.ExecuteReader();

if (fuelSystemProjectsReader.HasRows)
{
    while (fuelSystemProjectsReader.Read())
    {
        var id = fuelSystemProjectsReader.IsDBNull(0) ? Guid.Empty : Guid.Parse(fuelSystemProjectsReader.GetString(0));
        var fuelSystemJson = fuelSystemProjectsReader.IsDBNull(1) ? "null" : fuelSystemProjectsReader.GetString(1);
        var projFuelSystemInitialObj = JsonConvert.DeserializeObject<OldFuelSystem>(fuelSystemJson);

        string json = JsonConvert.SerializeObject(TransformProjectFuelSystem(projFuelSystemInitialObj)).Replace("'", "''", StringComparison.Ordinal);
        migrationBuilder.Sql($"UPDATE \"DesignHubProjectFuelSystems\"  SET \"InitialObject\" = '{json}'::jsonb WHERE \"Id\" = '{id}'");
    }
}
fuelSystemProjectsReader.Close();

I cannot combine OldSteamSystem and OldFuelSystem two classes, which are different. I can't make any familiar interface and abstract class out of these two.

So, could anyone please let me know how to make a common function out of it?

Many thanks in advance!!!


Solution

  • Use generics, and ask for the table name and a method to transform the object.

    void DoTheThing<TSystem>(string tableName, Func<TSystem, TSystem> transform)
    {
        using var command = dbContext.Database.GetDbConnection().CreateCommand();
        command.CommandText = @$"SELECT ""Id""::varchar, ""InitialObject""::varchar from ""{tableName}""";
        // I know I complain about SQL injection, you get to fix this one...
    
        using var reader = command.ExecuteReader();
    
        if (reader.HasRows)
        {
            while (reader.Read())
            {
                var id = reader.IsDBNull(0) ? Guid.Empty : Guid.Parse(reader.GetString(0));
                var initialJson = reader.IsDBNull(1) ? "null" : reader.GetString(1);
                var initialObj = JsonConvert.DeserializeObject<TSystem>(initialJson);
                
                // this uses the "transform" parameter, IE a method your caller provides
                string transformedJson = JsonConvert.SerializeObject(transform(initialObj)).Replace("'", "''", StringComparison.Ordinal);
                migrationBuilder.Sql($"UPDATE \"{tableName}\"  SET \"InitialObject\" = '{transformedJson}'::jsonb WHERE \"Id\" = '{id}'");
                // again, SQL injection
            }
        }
    
        reader.Close();
    }
    

    If the transform method takes one type and returns a different type, then change the method signature to

    void DoTheThing<TSystem, TTransformed>(string tableName, Func<TSystem, TTransformed> transform)