public virtual object CoerceType(Type desiredType, object input)
{
try
{
// For speed, put the most common checks at the top.
if (desiredType.IsInstanceOfType(input))
{
return input;
}
if (desiredType.Equals(typeof (string)))
{
return Convert.ToString(input);
}
if (desiredType.Equals(typeof (int)))
{
return input is string ? (int)Decimal.Parse((string)input) : Convert.ToInt32(input);
}
if (desiredType.Equals(typeof (long)))
{
return input is string ? (long)Decimal.Parse((string)input) : Convert.ToInt32(input);
}
if (desiredType.Equals(typeof (double)))
{
return Convert.ToDouble(input);
}
if (desiredType.Equals(typeof (DateTime)))
{
return Convert.ToDateTime(input);
}
if (desiredType.IsEnum)
{
return (input is int) ? input : Enum.Parse(desiredType, input.ToString());
}
if (desiredType.Equals(typeof (bool)))
{
return Convert.ToBoolean(input);
}
if (desiredType.Equals(typeof (short)))
{
return Convert.ToInt16(input);
}
if (desiredType.Equals(typeof (byte)))
{
return Convert.ToByte(input);
}
if (desiredType.Equals(typeof (char)))
{
return Convert.ToChar(input);
}
if (desiredType.Equals(typeof (float)))
{
return (float) Convert.ToDouble(input);
}
if (desiredType.Equals(typeof (DateTime?)))
{
if (input == null)
{
return null;
}
return Convert.ToDateTime(input);
}
if (desiredType.Equals(typeof(int?)))
{
if (input == null)
{
return null;
}
return Convert.ToInt32(input);
}
if (desiredType.Equals(typeof(long?)))
{
if (input == null)
{
return null;
}
return Convert.ToInt64(input);
}
if (desiredType.Equals(typeof(double?)))
{
if (input == null)
{
return null;
}
return Convert.ToDouble(input);
}
if (desiredType.Equals(typeof (float?)))
{
if (input == null)
{
return null;
}
return (float) Convert.ToDouble(input);
}
if (desiredType.Equals(typeof (bool?)))
{
if (input == null)
{
return null;
}
return Convert.ToBoolean(input);
}
if (desiredType.Equals(typeof (byte[])))
{
// Cast to byte array here so we'll throw if the type is incompatible.
// Technically we don't have to, since this returns object, but we want
// this to be the method that throws the type cast exception.
byte[] retVal = (byte[]) input;
return retVal;
}
// Nullables are generics, so nullable enums are more work to check for.
if (desiredType.IsGenericType &&
desiredType.GetGenericTypeDefinition().Equals(typeof(Nullable<>)))
{
// Technically this first check will work for any nullable type, not just enums.
if (input == null)
{
return null;
}
// Note that we're only handling nullables, which have 1 generic param
// which is why the [0] is correct.
Type genericType = desiredType.GetGenericArguments()[0];
if (genericType.IsEnum)
{
return (input is int)
// Unlike normal enums, integers cannot simply be set on a nullable enum.
// So we have to call ToObject to convert it to an enum value first.
? Enum.ToObject(genericType, input)
// Since it is a nullable enum, we're allowing blank or all-whitespace
// strings to count as nulls.
: (StringHelper.IsNonBlank(input.ToString())
? Enum.Parse(genericType, input.ToString())
: null);
}
}
// For misc object types, we'll just check if it is already the correct type.
if (desiredType.IsInstanceOfType(input))
{
return input;
}
// If it's mapped as an AsciiString, put the value in as a byte array of
// ascii characters. This supports old-style (non-unicode) varchars.
if (desiredType.Equals(typeof (AsciiString)))
{
return Encoding.ASCII.GetBytes(input.ToString());
}
// Have subclasses be able to add their own coerced types.
lock (this)
{
if (_coerceableTypes != null)
{
foreach (Type t in _coerceableTypes.Keys)
{
if (t.IsAssignableFrom(desiredType))
{
return _coerceableTypes[t].DynamicInvoke(input);
}
}
}
}
}
catch (Exception e)
{
throw new DaoTypeCoercionException(desiredType, input, e);
}
// To add support for lists, you'd need to add:
//if (desiredType.IsSubclassOf(typeof(IList)))
//{
// // input is some sort of list ID, so go run a subquery or something...
//}
// You'd also need to check if the input was a list, and do something special rather
// than just try to convert it to an int or whatever.
// Custom class types would be similar... Hmm, and you'd have to add support to the
// class mapping to handle nested classes. OK it's a tad more complicated, but it
// is doable if necessary.
// Oh yeah and then you have to handle transactions correctly, so if a second query fails
// you remember to roll back the first one... And there are probably a billion other details...
throw new DaoUnsupportedTypeCoercionException(desiredType, input);
}