SQL 服务器 CLR Int64 到 SQLInt64 指定的转换无效

SQL Server CLR Int64 to SQLInt64 Specified cast is not valid

我正在尝试编写允许我在 SQL 服务器上 运行 WMI 查询的 CLR。​​

using System;
using System.Data.Sql;
using Microsoft.SqlServer.Server;
using System.Collections;
using System.Data.SqlTypes;
using System.Diagnostics;
using System.Management;

public class WMIQuery
{
    [SqlFunction(FillRowMethodName = "FillRow")]
    public static IEnumerable InitMethod()
    {      
        ManagementScope scope = new ManagementScope();    
        scope = new ManagementScope(@"\localhost\root\CIMV2");
        scope.Connect();    
        SelectQuery query = new SelectQuery("SELECT Name, Capacity, Freespace FROM Win32_Volume WHERE DriveType=3");
        ManagementObjectSearcher searcher = new ManagementObjectSearcher(scope, query);

        ManagementObjectCollection retObjectCollection = searcher.Get ( );    
        return retObjectCollection;
    }

    public static void FillRow(Object obj, out SqlString Name, out SqlInt64 Capacity, out SqlInt64 Freespace)
    {
        ManagementObject m = (ManagementObject)obj;           

        Name = new SqlString((string)m["name"]);
        Capacity = new SqlInt64((Int64)m["Capacity"]);
        Freespace = new SqlInt64((Int64)m["Freespace"]);
    }  
 }

当 运行 宁 table 值函数时,我得到以下错误:

An error occurred while getting new row from user defined Table Valued Function : System.InvalidCastException: Specified cast is not valid. System.InvalidCastException: at WMIQuery.FillRow(Object obj, SqlString& Name, SqlInt64& Capacity, SqlInt64& Freespace) .

我已经发现问题出在转换上:

Capacity = new SqlInt64((Int64)m["Capacity"]);
Freespace = new SqlInt64((Int64)m["Freespace"]);

我希望有人知道如何解决上述问题?

我测试这个 CLR 的代码是:

CREATE FUNCTION [dbo].[WMIQuery]()
RETURNS  TABLE (
    [Name] [nvarchar](4000) NULL,
    [Capacity] [bigint] NULL,
    [Freespace] [bigint] NULL
) WITH EXECUTE AS CALLER
AS 
EXTERNAL NAME [MyFirstAssembly].[WMIQuery].[InitMethod]
GO


select * from WMIQuery()

您应该使用并检查该行和列是否具有可以转换为 Int64 的正确值。试试如何检查这个 Here.

投射前请执行以下操作

bool success = Int64.TryParse(Convert.ToString(m["Capacity"]), out long number);
if (success)
{
   Capacity = new SqlInt64((Int64)m["Capacity"]);
}
else
{
   Capacity = 0;
}

正如您所发现的,错误是由于 m["Capacity"] 是一个无符号的 Int64。要修复,只需使用 Convert class 如下:

        Capacity = new SqlInt64(Convert.ToInt64(m["Capacity"]));
        Freespace = new SqlInt64(Convert.ToInt64(m["Freespace"]));

我用您的代码对此进行了测试,在进行任何更改之前遇到了同样的错误,然后进行了上面推荐的更改,现在我得到了正确的输出。

虽然不是这里问题的一部分,但只是一般问题(最初有一个 String Querytext 输入参数):对于输入参数/ return 类型,请改用 Sql* 类型大多数数据类型的原生类型(SQL_VARIANTobjectDATETIME2DateTime 是明显的例外)。因此,使用 SqlString 而不是 String 作为 Querytext 参数(或者只删除未使用的输入参数)。您可以使用 Value 属性(例如 Querytext.Value)从参数中获取本机 .NET string,所有 Sql* 类型都具有(return在每种情况下都是预期的本机类型)。

有关一般使用 SQLCLR 的更多信息,请访问:SQLCLR Info

然而, 并且可能更重要的是:查看您通过 WMI 查询的确切内容,看起来您正在获取已经有 DMV 的信息,sys.dm_os_volume_stats.您可以使用以下查询为您已有文件的所有驱动器/卷获取完全相同的信息:

SELECT DISTINCT vol.[volume_mount_point], vol.[volume_id], vol.[logical_volume_name],
                vol.[file_system_type], vol.[total_bytes], vol.[available_bytes],
                (vol.[total_bytes] - vol.[available_bytes]) AS [used_bytes]
FROM sys.master_files files
CROSS APPLY sys.dm_os_volume_stats(files.[database_id], files.[file_id]) vol

磁盘 m["Capacity"] 的类型是 UInt64。 我用这个函数来找出使用的数据类型。

m["Capacity"].GetType().ToString();

我修改了 CLR 以仅输出用于该目的的数据类型。

了解类型后,我研究了如何将 UInt64 转换为 Int64,并最终找到了答案:

Int64 int64cap;
Int64.TryParse(m["Capacity"].ToString(), out int64cap);

我不知道这是否是正确的解决方案,但它对我有用。

完整代码如下

public class WMIQuery
{
    [SqlFunction(FillRowMethodName = "FillRow")]
    public static IEnumerable InitMethod()
    {


        ManagementScope scope = new ManagementScope();

        scope = new ManagementScope(@"\localhost\root\CIMV2");
        scope.Connect();

        SelectQuery query = new SelectQuery("SELECT Name, Capacity, Freespace FROM Win32_Volume WHERE DriveType=3");
        ManagementObjectSearcher searcher = new ManagementObjectSearcher(scope, query);


        ManagementObjectCollection retObjectCollection = searcher.Get ( );

        return retObjectCollection;
    }

    public static void FillRow(Object obj, out SqlString Name, out SqlDecimal Capacity, out SqlDecimal Freespace)
    {
        ManagementObject m = (ManagementObject)obj;

        Name = new SqlString((string)m["name"]);


        Int64 int64cap;
        Int64.TryParse(m["Capacity"].ToString(), out int64cap);
        decimal decCap;
        decCap = int64cap / 1073741824; // to GB
        decCap = Math.Round(decCap, 2);
        Capacity = new SqlDecimal(decCap);

        Int64 int64Free;
        Int64.TryParse(m["Freespace"].ToString(), out int64Free);
        decimal decFree;
        decFree = int64Free / 1073741824; // to GB
        decFree = Math.Round(decFree, 2);
        Freespace = new SqlDecimal(decFree);




    }


}

SQL到运行这个东西:

CREATE ASSEMBLY [MyFirstAssembly]
FROM 'C:\MyFirstAssembly.dll'
WITH PERMISSION_SET = UNSAFE
GO

CREATE FUNCTION [dbo].[WMIQuery]()
RETURNS  TABLE (
    [Name] [nvarchar](4000) NULL,
    [Capacity] decimal(18,2) NULL,
    [Freespace] decimal(18,2) NULL
) WITH EXECUTE AS CALLER
AS 
EXTERNAL NAME [MyFirstAssembly].[WMIQuery].[InitMethod]
GO

select * from WMIQuery()