I have the following method:
public static T ExecuteScalar<T>(
string query,
SqlConnection connection,
params SqlParameter[] parameters) where T : new()
{
// Create SqlCommand
SqlCommand command = CreateCommand(query, connection, parameters);
// Execute command using ExecuteScalar
object result = command.ExecuteScalar();
// Return value as expected type
if (result == null || result is DBNull) return default(T);
return (T)result;
}
I want to have the MIN_ACTIVE_ROWVERSION
of the database as an ulong
. The strange thing is.. the First method call below generates an error but the second method call works fine.
Method call 1 generates an error:
ulong minActiveRowversion =
SqlUtils.ExecuteScalar<ulong>(
"SELECT CAST(MIN_ACTIVE_ROWVERSION() AS BIGINT)"
, _connectionString);
Error:
System.InvalidCastException: Specified cast is not valid.
Method call 2 works fine:
ulong minActiveRowversion =
(ulong)SqlUtils.ExecuteScalar<long>(
"SELECT CAST(MIN_ACTIVE_ROWVERSION() AS BIGINT)"
, _connectionString);
I don't understand how that is possible because the result of the command.ExecuteScalar()
method is this:
object result | 1955612
result.GetType() | {Name = "Int64" FullName = "System.Int64"}
To convert a long to a ulong, simply cast it: long a; ulong b = (ulong)a; C# will NOT throw an exception if it is a negative number.
In C#, long is mapped to Int64. It is a value type and represent System. Int64 struct. It is signed and takes 64 bits.
Why
You can only unbox a value type to it's original type. In your case, the cast first needs to go to long
from object
and then to ulong
.
See this question for more detail:
Why can't I unbox an int as a decimal?
It also links a blog post by Eric Lippert.
How
One way, as you know, is to cast to the original type before casting to T
- unless, of course, the original type is T
.
As mentioned in the comments, another way is to use conversion routines (Convert.ToUInt64
) and not explicit casting.
This could potentially be achieved using a Func<object, T>
:
public static T ExecuteScalar<T>(
Func<object, T> conversionFunctor,
string query,
SqlConnection connection,
params SqlParameter[] parameters) where T : new()
{
// Create SqlCommand
SqlCommand command = CreateCommand(query, connection, parameters);
// Execute command using ExecuteScalar
object result = command.ExecuteScalar();
// Return value as expected type
if (result == null || result is DBNull)
return default(T);
return conversionFunctor(result);
}
Making your call:
ulong minActiveRowversion =
SqlUtils.ExecuteScalar<ulong>(
Convert.ToUInt64,
"SELECT CAST(MIN_ACTIVE_ROWVERSION() AS BIGINT)"
, _connectionString);
Adam's answer correctly identifies the problem; here is a solution: you can use LINQ to unbox any type, as long as it can be cast to T
with a built-in or a custom conversion.
static T UnboxUnchecked<T>(object obj) {
var pe = Expression.Parameter(typeof(object));
return Expression.Lambda<Func<object,T>>(
Expression.Convert(
Expression.Convert(pe, obj.GetType())
, typeof (T)
)
, pe
).Compile()(obj);
}
This method produces a LINQ expression that first unboxes the object to its actual type, and then applies the conversion. Replace the last line of your method
return (T)result;
with
return UnboxUnchecked<T>(result);
to make it work.
Here is a link to an article that explains how to make conversions of this kind more efficient by caching the compiled lambdas.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With