1

I'm making a helper method where it would automatically set random values to a property of a given entity (class) so that I won't have to fill each property with a value when testing.

In my case, each entity inherits from BaseEntity class, which has the ID, CreatedBy, CreatedOn, etc... properties. basically this class has all the properties that are shared across all the entities.

What I'm trying to accomplish here is to separate the unique properties from the common ones.

Here's my code:

public static TEntity PopulateProperties<TEntity>(TEntity entity)
{
    try
    {
        // Since every entity inherits from EntityBase, there is no need to populate properties that are in EntityBase class
        // because the Core project populates them.
        // First of all, we need to get all the properties of EntityBase
        // and then exlude them from the list of properties we will automatically populate

        // Get all properties of EntityBase
        EntityBase entityBase = new EntityBase();
        List<PropertyInfo> entityBaseProperties = new List<PropertyInfo>();
        foreach (var property in entityBase.GetType().GetProperties())
        {
            entityBaseProperties.Add(property);
        }

        // Get all properties of our entity
        List<PropertyInfo> ourEntityProperties = new List<PropertyInfo>();
        foreach (var property in entity.GetType().GetProperties())
        {
            ourEntityProperties.Add(property);
        }

        // Get only the properties related to our entity
        var propertiesToPopulate = ourEntityProperties.Except(entityBaseProperties).ToList();

        // Now we can loop throught the properties and set values to each property
        foreach (var property in propertiesToPopulate)
        {
            // Switch statement can't be used in this case, so we will use the if clause                  
            if (property.PropertyType == typeof(string))
            {
                property.SetValue(entity, "GeneratedString");
            }
            else if (property.PropertyType == typeof(int))
            {
                property.SetValue(entity, 1994);
            }
        }

        return entity;
    }
    finally
    {
    }
} 

The propeblem is with var propertiesToPopulate = entityBaseProperties.Except(ourEntityProperties).ToList();

What I'm expecting is a list of PropertyInfo objects that are only unique to this entity, however I'm always getting ALL the properties of my entity. This line does not filter the list as intended.

Any help why??

Ra'ed Alaraj
  • 173
  • 1
  • 15
  • 1
    If the properties should be unique to the _derived_ entity type, shouldn't it be `propertiesToPopulate = ourEntityProperties.Except(entityBaseProperties)`? – René Vogt Mar 01 '17 at 14:57
  • @RenéVogt I also thought that this way around it should theoretically work. But it doesn't. – Mong Zhu Mar 01 '17 at 15:07

4 Answers4

3

PropertyInfo "knows" which type you used to ask for it. For example:

using System;
using System.Reflection;

class Base
{
    public int Foo { get; set; }
}

class Child : Base
{    
}

class Test
{
    static void Main()
    {
        var baseProp = typeof(Base).GetProperty("Foo");
        var childProp = typeof(Child).GetProperty("Foo");
        Console.WriteLine(baseProp.Equals(childProp));
        Console.WriteLine(baseProp.ReflectedType);
        Console.WriteLine(childProp.ReflectedType);
    }
}

has output of:

False
Base
Child

Fortunately, you can do this a lot more simply - if you just want to know which properties were declared in TEntity you can just use:

var props = typeof(entity.GetType()).GetProperties(BindingFlags.Instance | 
                                                   BindingFlags.Public | 
                                                   BindingFlags.DeclaredOnly);

Adjust if you want static properties as well. The important point is BindingFlags.DeclaredOnly.

Jon Skeet
  • 1,421,763
  • 867
  • 9,128
  • 9,194
1

Another possibility could be to check in your for loop whether the DeclaringType is the same as the type of entity:

// Get all properties of our entity
List<PropertyInfo> ourEntityProperties = new List<PropertyInfo>();
foreach (var property in entity.GetType().GetProperties())
{
    // check whether it only belongs to the child
    if (property.DeclaringType.Equals(entity.GetType())) 
    {
        ourEntityProperties.Add(property);
    }       
}

Then you would only need one loop to filter out all the necessary properties.

In a "linqish" oneliner you could write it as:

List<PropertyInfo> ourEntityProperties = entity.GetType().GetProperties().Where(x=>x.DeclaringType.Equals(entity.GetType())).ToList();

But it looks horrible ;)

Mong Zhu
  • 23,309
  • 10
  • 44
  • 76
0

PropertyInfo contains many attributes, some of which have values unique to the object type they refer to, so I believe you may be interested in checking only for the property's name attribute:

//properties whose names are unique to our Entity
var propertiesToPopulate = ourEntityProperties
    .Where(oep => !entityBaseProperties.Any(ebp => ebp.Name == oep.Name)).ToList();
Innat3
  • 3,561
  • 2
  • 11
  • 29
0

I've created a class that you can use for this exact thing

public class DTOGeneratorRule
    {
        #region Members

        protected Random _random;

        protected readonly Dictionary<Type, Func<object>> typeToRandomizerFuncMap = new Dictionary<Type, Func<object>>
        {
        };

        #endregion

        #region Constructors

        public DTOGeneratorRule()
        {
            // Do Not Change this
            // This is explicitly set to assure that values are generated the same for each test
            _random = new Random(123); 

            typeToRandomizerFuncMap.Add(typeof(int), () => _random.Next());
            typeToRandomizerFuncMap.Add(typeof(bool), () => _random.Next() % 2 == 0);
            // Most codes on our system have a limit of 10, this should be fixed when configuration exits
            typeToRandomizerFuncMap.Add(typeof(Guid), () => Guid.NewGuid());
            typeToRandomizerFuncMap.Add(typeof(string), () => _random.GetRandomAlphanumericCode(10));
            //Most of the times we need to work with dates anyway so truncate the time
            typeToRandomizerFuncMap.Add(typeof(DateTime), () =>  DateTime.Now.Date);
            typeToRandomizerFuncMap.Add(typeof(Char), () =>_random.GetRandomAlphanumericCode(1)[0]);
            typeToRandomizerFuncMap.Add(typeof(Double), () => _random.NextDouble());
            typeToRandomizerFuncMap.Add(typeof(float), () => _random.NextFloat());
            typeToRandomizerFuncMap.Add(typeof(Decimal), () => _random.NextDecimal());
        }

        #endregion

        #region Public Methods

        public T SetAutoGeneratedDTOValues<T>(IEnumerable<Action<T>> explicitValueSetters = null, Dictionary<string, Type> typeCaster = null)
            where T : new()
        {
            T initialDTO = new T();
            return this.SetAutoGeneratedDTOValues<T>(initialDTO, explicitValueSetters, typeCaster);
        }

        public T SetAutoGeneratedDTOValues<T>(T initialDTO, IEnumerable<Action<T>> explicitValueSetters = null, Dictionary<string, Type> typeCaster = null)
        {
            if (null == initialDTO)
            {
                throw new ArgumentNullException(nameof(initialDTO));
            }

            //TODO: This needs to work with Members as well
            foreach (var property in typeof (T).GetProperties())
            {
                if (null == property.GetSetMethod())
                {
                    continue;
                }

                object value = null;

                Type propertyType = property.PropertyType;
                if (propertyType.IsGenericType && propertyType.GetGenericTypeDefinition() == typeof(Nullable<>))
                {
                    propertyType = Nullable.GetUnderlyingType(propertyType);
                }

                var targetType = propertyType;
                if (typeCaster != null && typeCaster.ContainsKey(property.Name))
                {
                    targetType = typeCaster.Get(property.Name);
                }

                value = this.GetRandomValue(targetType);
                value = this.convertToType(value, propertyType);

                property.SetValue(initialDTO, value);
            }

            if (null != explicitValueSetters)
            {
                foreach (var setter in explicitValueSetters)
                {
                    setter.Invoke(initialDTO);
                }
            }

            return initialDTO;
        }

        #endregion

        #region Protected Methods

        protected object convertToType(object value, Type type)
        {
            return Convert.ChangeType(value, type);
        }

        protected bool TryGetRandomValue(Type type, out object value)
        {
            Func<object> getValueFunc;
            if (type.IsEnum)
            {
                var values = Enum.GetValues(type);
                int index = _random.Next(0, values.Length);
                value = values.GetValue(index);
                return true;
            }

            if (typeToRandomizerFuncMap.TryGetValue(type, out getValueFunc))
            {
                value = getValueFunc();
                return true;
            }

            value = null;
            return false;
        }

        protected object GetRandomValue(Type type)
        {
            object value = null;
            Func<object> getValueFunc;
            if (type.IsEnum)
            {
                var values = Enum.GetValues(type);
                int index = _random.Next(0, values.Length);
                value = values.GetValue(index);
            }
            else if (typeToRandomizerFuncMap.TryGetValue(type, out getValueFunc))
            {
                value = getValueFunc();
            }
            else
            {
                value = this.getDefault(type);
            }

            return value;
        }

        protected object getDefault(Type type)
        {
            if (type.IsValueType)
            {
                return Activator.CreateInstance(type);
            }
            return null;
        }

        #endregion

    }

You also need some static extensions

public static class RandomExtensions
{
    public static decimal NextDecimal(this Random rng)
    {
        // The max value should not be too large to avoid out of range errors when saving to database.
        return Math.Round(rng.NextDecimal(10000, 25), 2);
    }

    //From Another Jon Skeet: http://stackoverflow.com/a/3365374/1938988 
    public static float NextFloat(this Random rng)
    {
        // Perform arithmetic in double type to avoid overflowing
        double range = (double)float.MaxValue - (double)float.MinValue;
        double sample = rng.NextDouble();
        double scaled = (sample * range) + float.MinValue;
        return (float)scaled;
    }

    public static string GetRandomAlphanumericCode(this Random random, int length)
    {
      string chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
      return RandomExtensions.GetRandomString(random, length, chars);
    }
}
johnny 5
  • 19,893
  • 50
  • 121
  • 195