Search code examples
c#reflectionpropertyinfo

How to get property name and value from generic model with generic list?


Using the following model as an example.

public class FooModel
{
    public FooModel()
    {
        Bars= new List<BarModel>();
    }

    [ManyToMany]
    public IList<BarModel> Bars{ get; set; }
}
public class BarModel
{
    public int Id { get; set; }
}

I need to extrapolate the List<BarModel> from a fooModel object, and build up a Dictionary<string, object> from each BarModel in the list.


Let's say I create the following object.

var fooModel = new FooModel();
var bar1 = new BarModel {Id = 1};
var bar2 = new BarModel {Id = 2};
fooModel.Bars = new List<BarModel>{bar1,bar2};

And now I want to get all properties within Foo that have the [ManyToMany] attribute.

// First I call the method and pass in the model
DoSomething(fooModel);
// Next I extract some values (used elsewhere)
public DoSomething<TModel>(IModel model){

    var dbProvider = ...;
    var mapper = new AutoMapper<TModel>();
    var tableName = GetTableName( typeof( TModel ) );

    UpdateJoins( dbProvider, fooModel, tableName, mapper );
}
// Finally I begin dealing with the collection.
private static void UpdateJoins<TModel> ( IDbProvider dbProvider, TModel model, string tableName, IAutoMapper<TModel> mapper ) where TModel : class, new()
{
    foreach (
        var collection in
             model.GetType()
                  .GetProperties()
                  .Where( property => property.GetCustomAttributes( typeof( ManyToManyAttribute ), true ).Any() ) )
    {

        if ( !IsGenericList( collection.PropertyType ) )
            throw new Exception( "The property must be a List" );

        // Stuck Here - pseudo code
        //====================
        foreach (loop the collection)

             var collectionName = ...;  // Bar
             var nestedPropertyName = ...;  // Id
             var rightKey = collectionName + nestedPropertyName; // BarId
             var nestedPropertyValue = ...; // 1

    }
}

In the example above, the OUTER foreach is only going to run ONCE because there is only one Property within FooModel that is decorated with the [ManyToMany] attribute.

Therefore PropertyInfo property is a List<BarModel>

How do I do the above INNER foreach and extract the required data?


Solution

  • This may get you on the right track. The idea is if you encounter a [ManyToMany] / generic list you reflect it using recursive call to the same method and then flatten the returned values to form a unique key. You probably will need to tweak it to suit your problem. The below code returns a dictionary with formatted key strings built from collection names, indexes and property names. E.G:

    Bars[0].Id = 1
    Bars[1].Id = 2
    

    Code:

    //This is just a generic wrapper for the other Reflect method
    private static Dictionary<string, string> Reflect<TModel>(TModel Model)
    {
      return Reflect(Model.GetType(), Model);
    }
    
    private static Dictionary<string, string> Reflect(Type Type, object Object)
    {
      var result = new Dictionary<string, string>();
    
      var properties = Type.GetProperties();
    
      foreach (var property in properties)
      {
        if (
          property.GetCustomAttributes(typeof(ManyToManyAttribute), true).Any() &&
          property.PropertyType.GetGenericTypeDefinition() == typeof(IList<>))
        {
          var genericType = property.PropertyType.GetGenericArguments().FirstOrDefault();
          var listValue = (IEnumerable)property.GetValue(Object, null);
    
          int i = 0;
          foreach (var value in listValue)
          {
            var childResult = Reflect(genericType, value);
            foreach (var kvp in childResult)
            {
              var collectionName = property.Name;
              var index = i;
              var childPropertyName = kvp.Key;
              var childPropertyValue = kvp.Value;
    
              var flattened = string.Format("{0}[{1}].{2}", collectionName, i, childPropertyName);
              result.Add(flattened, childPropertyValue);
            }
    
            i++;
          }
        }
        else
        {
          result.Add(property.Name, property.GetValue(Object, null).ToString());
        }
      }
    
      return result;
    
    }