0

I have results (xml format, <Prop0>, <Prop1>, ...) coming from an API that I am saving into my database table. I have a mapping of the Props to my table columns. I would like to create an object on the fly with property names coming from the mapping and values coming from the API results.

I could use the mapping dictionary and the API results to directly save the values in the database, but all the insert functions (remote to my application) are designed to use List of objects.

I have explored System.Dynamic.ExpandoObject but one cannot add properties on the fly to it (from, say, a dictionary). Currently, I am using the following generic but strongly typed solution, for which all the attributes need to be present in the class.

internal T FetchPopulatedObject<T>(Dictionary<string, string> apiToTableMapping, Dictionary<string, string> apiResultsKeyVal, T objectToPopulate) where T : new()
{
  if (objectToPopulate == null)
    objectToPopulate = new T();

  PropertyInfo currProp;
  Type currActualType;
  string valToSet = string.Empty;
  int valToSetNumeric = 0;
  object finalValToSet = new object();

  foreach (KeyValuePair<string, string> currMap in apiToTableMapping)
  {
    valToSet = string.Empty;
    currProp = (typeof(T).GetProperty(currMap.Value));
    if (currProp == null)
    {
      log.Info("Could not find property for table column " + currMap.Value + ". Moving on.");
      continue;
    }
    bool valResult = apiResultsKeyVal.TryGetValue(currMap.Key, out valToSet);
    if (!valResult)
    {
      log.Info("Could not find value in API Results for property: " + currMap.Key + ".");
      continue;
    }

    currActualType = Nullable.GetUnderlyingType(currProp.PropertyType) ?? currProp.PropertyType;

    if (string.IsNullOrEmpty(valToSet))
    {
      log.Info("Property " + currProp.Name + " is of " + currProp.PropertyType.Name + " type but API attribute " +
        currMap.Key + " returned an incompatible or empty value " + valToSet + ". Skipping this field.");
      continue;
    }
    else
    {
      finalValToSet = Int32.TryParse(valToSet, out valToSetNumeric) ? Convert.ChangeType(valToSetNumeric, currActualType) : Convert.ChangeType(valToSet, currActualType);
    }

    currProp.SetValue(objectToPopulate, finalValToSet, null);
  }

  return objectToPopulate;

}

However, this is infeasible, since the API results keep changing and adding new attributes.

Can somebody suggest how to accomplish the same without having to have all the properties already in class T?

Mr Lister
  • 45,515
  • 15
  • 108
  • 150
Abubakar Mehmood
  • 938
  • 1
  • 10
  • 19
  • 1
    "I have explored System.Dynamic.ExpandoObject but one cannot add properties on the fly to it (from, say, a dictionary). " - yes you can - it implements `IDictionary` - just cast it to that and you can do `obj["Foo"] = 123;` etc - see example here: http://stackoverflow.com/a/43276768/23354 – Marc Gravell Apr 07 '17 at 13:37
  • @MarcGravell. Oh, yess! It didn't occur to me before even though I read it implements IDictionary. It serves my purpose, thanks a lot! I can mark it as an answer if you post it as one. – Abubakar Mehmood Apr 10 '17 at 09:53

2 Answers2

1

Other than Emit, I can only think of a dynamic compiled type, example:

       static Type GetMeAType( string myTypeName, string myStringPropertyName ) {

            string codeToCompile = "using System; public class " + myTypeName + " {public string " +
                                   myStringPropertyName + " { get; set; } }";
            string[] references = { "System.dll", "System.Core.dll" };

            CompilerParameters compilerParams = new CompilerParameters();

            compilerParams.GenerateInMemory = true;
            compilerParams.TreatWarningsAsErrors = false;
            compilerParams.GenerateExecutable = false;
            compilerParams.CompilerOptions = "/optimize";
            compilerParams.ReferencedAssemblies.AddRange( references );

            CSharpCodeProvider provider = new CSharpCodeProvider();
            CompilerResults compile = provider.CompileAssemblyFromSource( compilerParams, codeToCompile );
            Module dynamicModule = compile.CompiledAssembly.GetModules()[ 0 ];
            var type = dynamicModule.GetType( myTypeName );

            return type;

        }
user2822208
  • 13
  • 1
  • 4
0

The only way to dynamically create a type in C# is by using the System.Reflection.Emit namespace. You cannot use a generic in this case, because even though the type T is not specified, it still implies type-safety, which requires a type to be created during compile-time.