6

我有以下方法:

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;
}

我想MIN_ACTIVE_ROWVERSION将数据库作为ulong. 奇怪的是.. 下面的第一个方法调用会产生错误,但第二个方法调用工作正常。

方法调用 1产生错误:

ulong minActiveRowversion = 
    SqlUtils.ExecuteScalar<ulong>(
        "SELECT CAST(MIN_ACTIVE_ROWVERSION() AS BIGINT)"
        , _connectionString);

错误:

System.InvalidCastException: Specified cast is not valid.

方法调用 2工作正常:

ulong minActiveRowversion = 
    (ulong)SqlUtils.ExecuteScalar<long>(
        "SELECT CAST(MIN_ACTIVE_ROWVERSION() AS BIGINT)"
        , _connectionString);

我不明白这是怎么可能的,因为该command.ExecuteScalar() 方法的结果是这样的:

object result       | 1955612
result.GetType()    | {Name = "Int64" FullName = "System.Int64"}
  1. 有人能告诉我为什么第一种情况不可能,而第二种情况有效吗?
  2. 有人可以告诉我如何解决它,以便我可以使用场景 1。
4

2 回答 2

5

为什么

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);
于 2012-06-27T12:42:25.547 回答
4

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.

于 2012-06-27T13:01:24.783 回答