diff --git a/BinaryDad.Extensions/Extensions/DataTableExtensions.cs b/BinaryDad.Extensions/Extensions/DataTableExtensions.cs index f4e6608..64e0a05 100644 --- a/BinaryDad.Extensions/Extensions/DataTableExtensions.cs +++ b/BinaryDad.Extensions/Extensions/DataTableExtensions.cs @@ -117,12 +117,7 @@ namespace BinaryDad.Extensions var instance = Activator.CreateInstance(type); - /* 01/09/13 - SJK: Class properties can be decorated with our custom DataRowField attribute. This allows us to - * declare a mapping between an object and a database column that are named differently. If the attribute is - * present on a property, we use its name for the customProperties collection. Otherwise, we use the name - * of the property as we assume the name on the database column is the same. - */ - properties.ForEach(p => RecursiveSetValue(p, row, instance)); + properties.ForEach(p => SetValueFromRow(p, row, instance)); return instance; } @@ -281,66 +276,73 @@ namespace BinaryDad.Extensions #region Private Methods - private static void RecursiveSetValue(PropertyInfo property, DataRow row, object instance, int maxDepth = 5, int currentDepth = 0) + private static string QuoteValue(string value) => string.Concat("\"", value.Replace("\"", "\"\""), "\""); + + /// + /// Sets the value of a property using the matched column from the data row. Supports using and for column mapping. + /// + /// + /// + /// + private static void SetValueFromRow(PropertyInfo property, DataRow row, object instance) { - #region Max Depth - - //Ensure we prevent too complex objects. This could be a sign of poor design or a condition prompting infinite recursion. - if (currentDepth > maxDepth) + if (property == null) { - throw new MaxRecursionException($"The currentDepth {currentDepth} may not exceed the maxDepth {maxDepth} for recursion. Check the complexity of the object and its implementation of the DataRowPopulate attribute."); + throw new ArgumentNullException(nameof(property)); } - #endregion - - try + // ignore the property if specified + if (property.HasCustomAttribute()) { - // ignore the property if specified - if (property.HasCustomAttribute()) + return; + } + + if (row == null) + { + return; + } + + // get the column name from the row + var matchedColumnName = property.GetDataColumnName(row.Table.Columns); + + //Set the value if we matched a column. If we don't have a match, there's simply no way to set a value. + if (matchedColumnName != null) + { + // raw value from the row, matching on column name + var tableValue = row[matchedColumnName]; + + // RJP - we'll come back to this + tableValue = ApplyTypeConversion(property, tableValue); + + // if DBNull, set as null + var value = tableValue == DBNull.Value ? null : tableValue; + + if (value != null) { - return; - } - - // if we found DataRowPopulate attributes, we go a level deeper - if (property.HasCustomAttribute()) - { - currentDepth++; - - // get all properties on this type and continue if we have any - var subProperties = property.PropertyType.GetProperties(); - - if (subProperties.AnyAndNotNull()) + try { - // since we have properties, we attempt to create an item (e.g. Address) based on the type of the current p - var subItem = Activator.CreateInstance(Type.GetType(property.PropertyType.AssemblyQualifiedName)); - - // attempt to set each value on subItem (e.g. populating Street1, Street2 on Address). - subProperties.ForEach(p2 => RecursiveSetValue(p2, row, subItem, maxDepth, currentDepth)); - - // now that subItem (instance of Address) has been populated, we in turn need to populate the current p (Address) on item. - property.SetValue(instance, subItem, null); + property.SetValue(instance, value.To(property.PropertyType), null); } - else + catch (Exception ex) { - property.SetValue(row, instance); + // encapsulate more detail about why the conversion failed + throw new DataPropertyConversionException(instance, property, value, ex); } } - else - { - property.SetValue(row, instance); - } - } - catch (MaxRecursionException) - { - throw; - } - catch (Exception ex) - { - throw new Exception($"Property {property.Name} cannot be set from row.", ex); } } - private static string QuoteValue(string value) => string.Concat("\"", value.Replace("\"", "\"\""), "\""); + private static object ApplyTypeConversion(PropertyInfo property, object sourceValue) + { + var converter = property.GetAttributeTypeConverter(); + + if (converter != null && converter.CanConvertFrom(sourceValue.GetType())) + { + sourceValue = converter.ConvertFrom(sourceValue); + } + + return sourceValue; + } #endregion } diff --git a/BinaryDad.Extensions/Extensions/ReflectionExtensions.cs b/BinaryDad.Extensions/Extensions/ReflectionExtensions.cs index 156cbbb..58ae021 100644 --- a/BinaryDad.Extensions/Extensions/ReflectionExtensions.cs +++ b/BinaryDad.Extensions/Extensions/ReflectionExtensions.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.ComponentModel; using System.ComponentModel.DataAnnotations.Schema; using System.Data; using System.Linq; @@ -168,6 +169,30 @@ namespace BinaryDad.Extensions .FirstOrDefault(f => columns.Contains(f)); } + /// + /// Retrieves an instance of the associated with the property's + /// + /// + /// + internal static TypeConverter GetAttributeTypeConverter(this PropertyInfo property) + { + var converter = property.GetCustomAttribute(); + + if (converter != null) + { + var converterType = Type.GetType(converter.ConverterTypeName); + + if (converterType == typeof(EnumConverter)) + { + return (TypeConverter)Activator.CreateInstance(converterType, property.PropertyType); + } + + return (TypeConverter)Activator.CreateInstance(converterType); + } + + return null; + } + #endregion } } \ No newline at end of file