Posts Tagged ‘Reflection’
ToStringHelper
The implementation is inspired by Google Guava libraries. ToStringHelper is a simple class helps to get all the property values of a class in string format. Some times for logging purpose we may need the property values of entities, to achieve that we override ToString() and append the required property values. ToStringHelper will ease this step.
In Guava libs, ToStringHelper is implemented as fluent APIs and user needs to provide the property and values as shown below.
return InstanceHelper.ToStringHelper(this).Add("Name", Name).Add("Age", Age).ToString();
I am very lazy of doing this way, so I used reflection to do my job. In the later part of this post you can see the implementation of ToStringHelper and how I get the value using reflection.
Let’s see the use of this helper method.
public class Student { public Student(string name,string lastName, int age) { this.Name = name; this.Age = age; this.LastName = lastName; } [UseInToString(AliasName="Student Name")] public string Name { get; private set; } [UseInToString] public int Age { get; private set; } public string LastName { get; private set; }
public override string ToString() { ToStringHelper toStrngHelper = new ToStringHelper("Student"); toStrngHelper.Add<Student>(this); return toStrngHelper.ToString(); } }
Have a look at the above class. I override ToString() and called the ToStringHelper to provide a textual data. This way I can avoid all the repeated process of appending and returning the values from ToString function. In my scenario I don’t need all the value in the string returned by ToString. So I created a CustomAttribute called UseInToString, which ever the property decorated with UseInToString will be used in creating the string output. Also we can provide an AliasName for the property, if AliasName exist then AliasName will be used instead of Property name.
Let’s write a test and see whether out function works fine.
[TestCase] public void TestInstanceHelper() { Student student = new Student("Sony","Arouje",30); Assert.AreEqual("Student{Student Name: Sony, Age: 30}", student.ToString()); Console.WriteLine(student.ToString()); }
No more description required for the above test case, the output is pretty much self explanatory.
Now let’s have a look at the ToStringHelper class and UseInToString custom attribute
[System.AttributeUsage(System.AttributeTargets.Property)] public class UseInToString : System.Attribute { private string _humanReadablePropertyName; public string AliasName { get { return _humanReadablePropertyName; } set { _humanReadablePropertyName = value; } } } public class ToStringHelper { StringBuilder _outPutValue = null; string _delimiter = string.Empty; public ToStringHelper(object instance) { _outPutValue = new StringBuilder
(instance.GetType()==typeof(string)?instance.ToString():instance.GetType().Name); _outPutValue.Append("{"); } private void Add(string name, object value) { _outPutValue.Append(string.Format("{0}{1}: {2}", _delimiter, name, value)); _delimiter = ", "; } public override string ToString() { _outPutValue.Append("}"); return _outPutValue.ToString(); } public void Add<TClazz>(TClazz clazz) where TClazz : class { object propertyValue; typeof(TClazz).GetProperties().Where(pr => pr.CanRead).ToList()
.ForEach(property => { UseInToString useInAttr = (UseInToString)property.GetCustomAttributes(false)
.FirstOrDefault(attr => attr is UseInToString); if (useInAttr != null) { propertyValue = property.GetValue(clazz, null); this.Add(string.IsNullOrEmpty(useInAttr.AliasName) ? property.Name
: useInAttr.AliasName, propertyValue); } }); } }
I think the class describes what it does. Hope you may find it useful. Happy coding…
.NET Reflection–Helpful Functions
In this post I will explain few functions I created to solve some of the scenarios in one of my project.
Property Defaulter
In one of my project I had to deal with a table that have hundreds of fields but we are dealing with very few field in the table. The several fields of the table needs default values either zero or empty. I know if I am not finding a generic solution I will be in trouble as I had to fill all the fields my self and I don’t want to do that. So I created a small function that will do the job of setting my entities to default values if the properties doesn’t have any values. Here is my function
public static void SetDefaultValue<TEntity>(TEntity entity) where TEntity : class { try { PropertyInfo[] properties = typeof(TEntity).GetProperties(); bool isSetDefaultVal = false; foreach (PropertyInfo property in properties) { object propertyValue; if (property.CanRead) { isSetDefaultVal = false; if (property.PropertyType.Name != "String" &&
property.PropertyType.Name != "Single" &&
property.PropertyType.Name != "Int32") continue; try { propertyValue = property.GetValue(entity, null); } catch (Exception ex) { //log error continue; } if (propertyValue==null) { if (property.PropertyType.Name == "String") propertyValue = ""; else propertyValue = 0; isSetDefaultVal = true; } //set the value if property value is null and set to 0 in above if block. if (property.CanWrite && isSetDefaultVal == true) { if (property.PropertyType.Name == "Single") property.SetValue(entity, Convert.ToSingle(propertyValue), null); else if (property.PropertyType.Name == "Int32") property.SetValue(entity, Convert.ToInt32(propertyValue), null); else if (property.PropertyType.Name == "String") property.SetValue(entity, "", null); } } } } catch (Exception ex) { throw; } }
So how we use it, lets see with a e.g
PropertyDefaulter.SetDefaultValue<ItemMaster>(itemMaster);
The above function will inspect the properties in the itemMaster object, if any property doesn’t has value then based on the type the function will set the value. Int or float property will set to 0 and string property will set to empty.
Copy DataReader to Entities
When I was dealing with ADO.NET I used to fetch data from database table using SqlCommand’s execute reader and get the data in DataReader. I have DTO’s (Data Transfer Object) to transfer data between layers. So I have to copy all the data in DataReader to my DTO’s. I need to find an easy way rather than manually assigning values from datareader to properties. Here is my method to do the copy of data from DataReader to my DTO’s
public static List<TEntity> CopyDataReaderToEntity<TEntity>(IDataReader dataReader)
where TEntity : class { List<TEntity> entities = new List<TEntity>(); PropertyInfo[] properties = typeof(TEntity).GetProperties(); while (dataReader.Read()) { TEntity tempEntity = Activator.CreateInstance<TEntity>(); foreach (PropertyInfo property in properties) { SetValue<TEntity>(property, tempEntity, dataReader[property.Name]); } entities.Add(tempEntity); } return entities; } public static TEntity SetValue<TEntity>(PropertyInfo property, TEntity entity, object propertyValue)
where TEntity : class { if (property.CanRead) { if (property.PropertyType.Name != "String" &&
property.PropertyType.Name != "Single" &&
property.PropertyType.Name != "Int32") return entity; if (propertyValue == null) { if (property.PropertyType.Name == "String") propertyValue = ""; else propertyValue = 0; } if (property.CanWrite) { if (property.PropertyType.Name == "Single") property.SetValue(entity, Convert.ToSingle(propertyValue), null); else if (property.PropertyType.Name == "Int32") property.SetValue(entity, Convert.ToInt32(propertyValue), null); else if (property.PropertyType.Name == "String") property.SetValue(entity, propertyValue, null); } } return entity; }
Here we need to follow a convention to get it working. The convention I follow here is, the Property name in the DTO and the table field should be same.
Let’s see how we can use it.
SqlCommand cmd = new SqlCommand(qryToExecute, connection); SqlDataReader reader = cmd.ExecuteReader(); List<ItemDetails> itemDetails = SQLHelper.CopyDataReaderToEntity<ItemDetails>(reader);
Create Select and Insert SQL statement Dynamically
When I was dealing with some table that have a lot of fields, I always have trouble in creating select or insert statement. So I created a small function that creates the Select and Insert statement dynamically. Here also I follow the same convention I explained earlier, that is the field name and the property name should be same.
private static List<FiledAndValueHolder> CreateFieldValueMapper<TEntity>(TEntity entity)
where TEntity : class { List<FiledAndValueHolder> filedAndValues = new List<FiledAndValueHolder>(); PropertyInfo[] properties = typeof(TEntity).GetProperties(); foreach (PropertyInfo property in properties) { object propertyValue; string propertyName; if (property.CanRead) { if (property.PropertyType.Name != "String" &&
property.PropertyType.Name != "Single" &&
property.PropertyType.Name != "Int32") continue; propertyValue = property.GetValue(entity, null); propertyName = property.Name; if (propertyName.StartsWith("Native")) continue; FiledAndValueHolder fieldAndValue = new FiledAndValueHolder(); fieldAndValue.FieldValue = propertyValue; fieldAndValue.FiledName = propertyName; fieldAndValue.FieldType = property.PropertyType.Name; filedAndValues.Add(fieldAndValue); } } return filedAndValues; } public static string CreateInsertStatement<TEntity>(TEntity entity, string tableName)
where TEntity : class { List<FiledAndValueHolder> filedAndValues = CreateFieldValueMapper<TEntity>(entity); string sql = "insert into [dbo]." + tableName + "({0}) values ({1})"; StringBuilder fieldBuilder = new StringBuilder(); StringBuilder valueBuilder = new StringBuilder(); string seprator = string.Empty; foreach (FiledAndValueHolder filedAndValue in filedAndValues) { fieldBuilder.Append(seprator); fieldBuilder.Append(filedAndValue.FiledName); valueBuilder.Append(seprator); if (filedAndValue.FieldType == "String") { valueBuilder.Append(string.Format("'{0}'", filedAndValue.FieldValue as string)); } else { valueBuilder.Append(filedAndValue.FieldValue); } if (string.IsNullOrEmpty(seprator)) seprator = ","; } string sqlStatement = string.Format(sql, fieldBuilder.ToString(), valueBuilder.ToString()); return sqlStatement; }
public static string CreateSelectStatement<TEntity>(TEntity entity, string tableName, string selectCondition) where TEntity : class { List<FiledAndValueHolder> filedAndValues = CreateFieldValueMapper<TEntity>(entity); string sql = "select {0} from dbo.{1} where ({2})"; StringBuilder fieldBuilder = new StringBuilder(); string seprator = string.Empty; foreach (FiledAndValueHolder filedAndValue in filedAndValues) { fieldBuilder.Append(seprator); fieldBuilder.Append(filedAndValue.FiledName); if (string.IsNullOrEmpty(seprator)) seprator = ","; } return string.Format(sql, fieldBuilder.ToString(), tableName, selectCondition); }
Let’s see how we can use it.
insertItemMaster = SQLHelper.CreateInsertStatement<ItemMaster>(itemMaster, "ItemMaster");
Most of the functions I explained above doesn’t required much explanations. If any one need any more details then leme know.