remove recursive set value on ToList and To

This commit is contained in:
Ryan Peters 2020-10-02 17:14:12 -04:00
parent 07ecc55433
commit 5e4c1ead0c
2 changed files with 79 additions and 52 deletions

View File

@ -117,12 +117,7 @@ namespace BinaryDad.Extensions
var instance = Activator.CreateInstance(type); var instance = Activator.CreateInstance(type);
/* 01/09/13 - SJK: Class properties can be decorated with our custom DataRowField attribute. This allows us to properties.ForEach(p => SetValueFromRow(p, row, instance));
* 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));
return instance; return instance;
} }
@ -281,66 +276,73 @@ namespace BinaryDad.Extensions
#region Private Methods #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("\"", "\"\""), "\"");
/// <summary>
/// Sets the value of a property using the matched column from the data row. Supports using <see cref="ColumnAttribute"/> and <see cref="NotMappedAttribute"/> for column mapping.
/// </summary>
/// <param name="property"></param>
/// <param name="row"></param>
/// <param name="instance"></param>
private static void SetValueFromRow(PropertyInfo property, DataRow row, object instance)
{ {
#region Max Depth if (property == null)
//Ensure we prevent too complex objects. This could be a sign of poor design or a condition prompting infinite recursion.
if (currentDepth > maxDepth)
{ {
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 // ignore the property if specified
if (property.HasCustomAttribute<NotMappedAttribute>())
try
{ {
// ignore the property if specified return;
if (property.HasCustomAttribute<NotMappedAttribute>()) }
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; try
}
// if we found DataRowPopulate attributes, we go a level deeper
if (property.HasCustomAttribute<DataRowPopulateAttribute>())
{
currentDepth++;
// get all properties on this type and continue if we have any
var subProperties = property.PropertyType.GetProperties();
if (subProperties.AnyAndNotNull())
{ {
// since we have properties, we attempt to create an item (e.g. Address) based on the type of the current p property.SetValue(instance, value.To(property.PropertyType), null);
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);
} }
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 #endregion
} }

View File

@ -1,5 +1,6 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.ComponentModel;
using System.ComponentModel.DataAnnotations.Schema; using System.ComponentModel.DataAnnotations.Schema;
using System.Data; using System.Data;
using System.Linq; using System.Linq;
@ -168,6 +169,30 @@ namespace BinaryDad.Extensions
.FirstOrDefault(f => columns.Contains(f)); .FirstOrDefault(f => columns.Contains(f));
} }
/// <summary>
/// Retrieves an instance of the <see cref="TypeConverter"/> associated with the property's <see cref="TypeConverterAttribute"/>
/// </summary>
/// <param name="property"></param>
/// <returns></returns>
internal static TypeConverter GetAttributeTypeConverter(this PropertyInfo property)
{
var converter = property.GetCustomAttribute<TypeConverterAttribute>();
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 #endregion
} }
} }