【问题标题】:SQL Server CLR Int64 to SQLInt64 Specified cast is not validSQL Server CLR Int64 到 SQLInt64 指定的强制转换无效
【发布时间】:2019-01-17 13:06:01
【问题描述】:

我正在尝试编写一个允许我在 SQL Server 上运行 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"]);
    }  
 }

运行该表值函数时,出现以下错误:

从用户定义的值表中获取新行时出错 功能:System.InvalidCastException:指定的强制转换无效。 System.InvalidCastException:在 WMIQuery.FillRow(对象 obj, SqlString& 名称、SqlInt64& 容量、SqlInt64& 可用空间)。

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

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()

【问题讨论】:

  • 您的行值之一是NULL?以防万一,您可能需要改用 Nullable<Int64>。如果其中一个值是NULL,则转换为SqlInt64 将失败,因为它需要一个有效的Int64。如果您可以从数据库表中提供一些示例数据,那将会很有帮助。
  • 请发布您的数据库表的架构并提供一些示例数据。
  • 不要使用 SQLCLR,而是使用 sys.dm_os_volume_stats 视图
  • 我添加了代码来测试 CLR @RyanWilson。
  • @PanagiotisKanavos 我正在使用 win32_volume 类作为我想到的未来 wmi 查询的示例。无论如何,谢谢你把我带到 dmv。

标签: c# sql-server wmi sqldatatypes sqlclr


【解决方案1】:

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

请在投射前执行以下操作

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

【讨论】:

  • 因为它需要字符串作为参数。它需要格式为 - 'public static bool TryParse (string s, out long result);'的参数这里 s 是字符串。
  • 或者你可以使用Nullable<Int64>
  • 这可能是条件运算符或类似 string.IsNullOrEmpty....
  • 如果该列包含Int64 值,则它应该是有效的Int64DBNull
  • 是的,但我们不知道这里的数据类型。作为这里的名称容量,它可能是其他一些文本值或其他东西。
【解决方案2】:

磁盘 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()

【讨论】:

  • 很好地确定了底层类型是UInt64 / ulong。我在我的回答中发布了一个简化版本:) (不需要昂贵的转换为string 只需要做TryParse / 再次转换)。
【解决方案3】:

正如您所发现的,错误是由于m["Capacity"] 是一个未签名的、未签名的 Int64。要修复,只需使用Convert 类,如下所示:

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

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

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

有关使用 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

【讨论】:

  • 我已经尝试了您建议的解决方案,但仍然收到错误消息 6260,级别 16,状态 1,第 36 行从用户定义的表值函数获取新行时发生错误:System.InvalidCastException:指定的演员表无效。 System.InvalidCastException:在 WMIQuery.FillRow(对象 obj,SqlString& 名称,SqlInt64& 容量,SqlInt64& 可用空间)
  • 我知道 sys.dm_os_volume_stats,但这更像是一个示例 CLR,它是我想要运行的其他 wmi 查询的模板。
  • @Niklas 我刚刚更新了我的答案。该解决方案比您刚刚接受的答案更简单(也更有效)。它确实适用于您的代码。
猜你喜欢
  • 1970-01-01
  • 2012-06-12
  • 1970-01-01
  • 2018-05-27
  • 2014-07-30
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多