diff --git a/BinaryDad.Extensions/BinaryDad.Extensions.csproj b/BinaryDad.Extensions/BinaryDad.Extensions.csproj
index 33e62a3..bb68b57 100644
--- a/BinaryDad.Extensions/BinaryDad.Extensions.csproj
+++ b/BinaryDad.Extensions/BinaryDad.Extensions.csproj
@@ -11,7 +11,7 @@
netstandard2.0
A set of common utilities and extension methods for .NET.
7.3
- 21.4.20.1
+ 21.4.20.2
true
diff --git a/BinaryDad.Extensions/Extensions/CollectionExtensions.cs b/BinaryDad.Extensions/Extensions/CollectionExtensions.cs
index af23fb5..75732e7 100644
--- a/BinaryDad.Extensions/Extensions/CollectionExtensions.cs
+++ b/BinaryDad.Extensions/Extensions/CollectionExtensions.cs
@@ -6,6 +6,7 @@ using System.ComponentModel.DataAnnotations.Schema;
using System.Data;
using System.Linq;
using System.Linq.Expressions;
+using System.Reflection;
namespace BinaryDad.Extensions
{
@@ -24,6 +25,20 @@ namespace BinaryDad.Extensions
items.Add(item);
}
+ ///
+ /// Adds the elements of the specified collection to the end of the
+ ///
+ ///
+ ///
+ ///
+ public static void AddRange(this ICollection source, IEnumerable items)
+ {
+ foreach (var item in items)
+ {
+ source.Add(item);
+ }
+ }
+
///
/// Returns a distinct list of elements using the first-matched item on a specific property
///
@@ -59,31 +74,82 @@ namespace BinaryDad.Extensions
///
///
///
- /// Specifies whether the data column should use the name from or , if bound to a property.
+ /// If a non-null string is returned, the column name is overridden
+ /// Specifies whether the data column should use the name from , if bound to a property.
///
///
- public static DataTable ToDataTable(this IEnumerable collection, bool useColumnAttributeName = false, string tableName = null) where T : class
+ public static DataTable ToDataTable(this IEnumerable collection, Func columnNameModifier, bool useColumnAttributeName = false, string tableName = null)
{
+ if (collection == null)
+ {
+ return null;
+ }
+
+ #region Get type of first record
+
+ // NOTE:
+ // We assume all values are the same, so use the type from the first record.
+ // This allows us to use the actual instance type instead of the generic version (useful for anonymous type collections)
+
+ Type type = null;
+ var genericArguments = collection.GetType().GetGenericArguments();
+
+ if (genericArguments.Any())
+ {
+ // handle anonymous types, where there are multiple generic arguments (the first is an integer)
+ type = genericArguments.FirstOrDefault(g => g.IsClass);
+ }
+ else
+ {
+ var enumerator = collection.GetEnumerator();
+
+ enumerator.MoveNext();
+
+ type = enumerator.Current.GetType();
+ }
+
+ #endregion
+
using (var table = new DataTable(tableName))
{
#region Build Table Schema
- var propertyInfo = typeof(T)
+ var propertyInfo = type
.GetProperties()
- .Select(p => new
+ .Where(p => !p.HasCustomAttribute())
+ .Select(p =>
{
- Property = p,
+ string columnName = p.Name;
+ string columnTypeName = null;
// set column name to be either the property name
// or, if specified, based on the attribute
- ColumnName = useColumnAttributeName
- ? p.GetDataColumnNames().FirstOrDefault()
- : p.Name,
+ if (useColumnAttributeName)
+ {
+ var columnAttributes = p.GetColumnAttributes();
- // include the column if [NotMapped] is NOT attached
- IncludeColumn = !p.HasCustomAttribute()
+ columnName = p.GetDataColumnNames(columnAttributes).FirstOrDefault();
+ columnTypeName = columnAttributes.FirstOrDefault()?.TypeName;
+ }
+
+ if (columnNameModifier != null)
+ {
+ var modifiedColumnName = columnNameModifier.Invoke(p);
+
+ if (modifiedColumnName.IsNotEmpty())
+ {
+ columnName = modifiedColumnName;
+ }
+ }
+
+ return new
+ {
+ Property = p,
+ ColumnTypeName = columnTypeName,
+ Converter = p.GetAttributeTypeConverter(),
+ ColumnName = columnName
+ };
})
- .Where(p => p.IncludeColumn)
.ToList();
foreach (var info in propertyInfo)
@@ -95,6 +161,11 @@ namespace BinaryDad.Extensions
columnType = columnType.GetGenericArguments()[0];
}
+ if (info.ColumnTypeName.IsNotEmpty())
+ {
+ columnType = SqlTypeMap.GetType(info.ColumnTypeName);
+ }
+
table.Columns.Add(info.ColumnName, columnType);
}
@@ -109,6 +180,12 @@ namespace BinaryDad.Extensions
foreach (var info in propertyInfo)
{
var value = info.Property.GetValue(item, null);
+ var columnType = row.Table.Columns[info.ColumnName].DataType;
+
+ if (info.Converter != null && info.Converter.CanConvertTo(columnType))
+ {
+ value = info.Converter.ConvertTo(value, columnType);
+ }
if (value != null)
{
diff --git a/BinaryDad.Extensions/Extensions/ReflectionExtensions.cs b/BinaryDad.Extensions/Extensions/ReflectionExtensions.cs
index 58ae021..63a32b3 100644
--- a/BinaryDad.Extensions/Extensions/ReflectionExtensions.cs
+++ b/BinaryDad.Extensions/Extensions/ReflectionExtensions.cs
@@ -83,6 +83,19 @@ namespace BinaryDad.Extensions
///
///
internal static IEnumerable GetDataColumnNames(this PropertyInfo property)
+ {
+ var attributes = property.GetColumnAttributes();
+
+ return GetDataColumnNames(property, attributes);
+ }
+
+ ///
+ /// Retrieves a list of available property binding aliases using .
+ ///
+ ///
+ ///
+ ///
+ internal static IEnumerable GetDataColumnNames(this PropertyInfo property, ICollection attributes)
{
#region Null checks
@@ -95,14 +108,9 @@ namespace BinaryDad.Extensions
var dataRowFieldNames = new List();
- var attributes = property
- .GetCustomAttributes(true);
-
- var columnAttribute = attributes.FirstOfType();
-
- if (columnAttribute != null)
+ foreach (var attribute in attributes)
{
- dataRowFieldNames.Add(columnAttribute.Name);
+ dataRowFieldNames.Add(attribute.Name);
}
// add the property's name at the end, so it's the last in the lookup
@@ -169,6 +177,14 @@ namespace BinaryDad.Extensions
.FirstOrDefault(f => columns.Contains(f));
}
+ internal static ICollection GetColumnAttributes(this PropertyInfo property)
+ {
+ return property
+ .GetCustomAttributes()
+ .OrderBy(c => !c.IsDefaultAttribute())
+ .ToList();
+ }
+
///
/// Retrieves an instance of the associated with the property's
///
diff --git a/BinaryDad.Extensions/Extensions/SqlTypeMap.cs b/BinaryDad.Extensions/Extensions/SqlTypeMap.cs
new file mode 100644
index 0000000..43feae4
--- /dev/null
+++ b/BinaryDad.Extensions/Extensions/SqlTypeMap.cs
@@ -0,0 +1,44 @@
+using System;
+using System.Collections.Generic;
+using System.Data;
+using System.Linq;
+
+namespace BinaryDad.Extensions
+{
+ public static class SqlTypeMap
+ {
+ public static Dictionary typeMap = new Dictionary
+ {
+ [SqlDbType.NVarChar] = typeof(string),
+ [SqlDbType.VarChar] = typeof(string),
+ [SqlDbType.Char] = typeof(char[]),
+ [SqlDbType.NChar] = typeof(char[]),
+ [SqlDbType.TinyInt] = typeof(byte),
+ [SqlDbType.SmallInt] = typeof(short),
+ [SqlDbType.Int] = typeof(int),
+ [SqlDbType.BigInt] = typeof(long),
+ [SqlDbType.Bit] = typeof(bool),
+ [SqlDbType.DateTime] = typeof(DateTime),
+ [SqlDbType.DateTime2] = typeof(DateTime),
+ [SqlDbType.SmallDateTime] = typeof(DateTime),
+ [SqlDbType.Time] = typeof(TimeSpan),
+ [SqlDbType.DateTimeOffset] = typeof(DateTimeOffset),
+ [SqlDbType.Decimal] = typeof(decimal),
+ [SqlDbType.Money] = typeof(decimal),
+ [SqlDbType.SmallMoney] = typeof(decimal),
+ [SqlDbType.Float] = typeof(double),
+ [SqlDbType.Real] = typeof(float)
+ };
+
+ public static Type GetType(string sqlDbTypeName)
+ {
+ return GetType(sqlDbTypeName.ToEnum());
+ }
+
+ public static Type GetType(SqlDbType sqlDbType)
+ {
+ // I'm aware this is backward
+ return typeMap.FirstOrDefault(t => t.Key == sqlDbType).Value;
+ }
+ }
+}
\ No newline at end of file