【问题标题】:SQLCLR Nullable DATETIME2 in SSDTSSDT 中的 SQLCLR 可为空的 DATETIME2
【发布时间】:2015-08-18 20:47:11
【问题描述】:

我想编写一个接受DATETIME2 并返回DATETIME2 的SQLCLR UDF。 输入和输出应该允许 NULL。

我创建一个 SQL Server 数据库项目 (SSDT),在其 SQLCLR 属性中将其配置为 VB 语言,然后添加以下文件 Test.vb

Option Explicit On
Option Strict On

Imports System
Imports System.Data
Imports System.Data.SqlClient
Imports System.Data.SqlTypes
Imports Microsoft.SqlServer.Server

Partial Public Class UserDefinedFunctions
    <SqlFunction()> _
    Public Shared Function Test(d As Nullable(Of DateTime)) As Nullable(Of DateTime)
        Return d
    End Function
End Class

从 SQL Server 2008 开始,http://msdn.microsoft.com/en-us/library/ms131092(v=SQL.100).aspx 就支持这种方式使用 nullable。

但是,当我运行部署命令时,我收到以下错误:

SQL46010:) 附近的语法不正确。

这是因为它生成的 SQL 是:

CREATE FUNCTION [dbo].[Test] (@d /* Error: Unsupported type. */)
RETURNS /* Error: Unsupported type. */
    AS EXTERNAL NAME [Test].[Test.UserDefinedFunctions].[Test];

我无法替换 SqlDateTime,因为我需要 DATETIME2 的完整范围和精度。

【问题讨论】:

  • 我相信 SQLDateTime 在较新的 .NET 版本中具有更高的分辨率。你试过吗?
  • 对不起,我上面的评论不是真的:msdn.microsoft.com/en-us/library/…
  • Jason:我知道已经有几年了,但以防万一你还在寻求帮助,我刚刚发布了一个答案 :-)。

标签: sql-server sqlclr sql-server-data-tools


【解决方案1】:

看看this。 SqlDateTime 允许空值,因此能够通过类的属性检查值是否为空。

【讨论】:

  • 但是 SqlDateTime 映射到 SQL Server 中不太精确的 'datetime' 类型,所以它是不可接受的。要映射到 SQL Server 中更精确的“datetime2”类型,您的 CLR 函数必须是 CLR 类型 DateTime,它不允许空值...因此我们确实需要能够使用 Nullable。如果这不可能,那么就有一个严重的问题。
  • @Triynko 对于 Visual Studio 的几个版本来说,这可能是不可能的,但它确实允许您(至少从 VS2012 开始,我认为)使用DateTime?。但问题是,SSDT 发布过程仍将其转换为 T-SQL 端的 DATETIME。我在answer 中提出了两个解决方法。
【解决方案2】:

问题出在 SSDT (SQL Server Data Tools) 而不是 SQLCLR。是的,SQLCLR 确实通过DateTime? / Nullable&lt;DateTime&gt; 支持DATETIME2。不幸的是,SSDT(我正在使用 VS2013 和 SSDT v 12.0.50512.0)不(我在这里很乐观,我知道)支持从 DateTimeDateTime? 推断 DATETIME2。但是,像以前一样使用DateTime? 时也不会出错。尽管如此,DateTimeDateTime? 都会在生成的 SQL 中显示为常规的 DATETIME

我不确定告诉 SSDT 数据类型应该是什么的任何真正“适当”的方法。不支持的选项其实挺多的,包括WITH RETURNS NULL ON NULL INPUT等通用UDF选项和默认值等参数选项。是的,令人悲伤和沮丧。

到目前为止,我想出的最好的方法(我仍在寻找其他选项)是将部署后脚本添加到 ALTER 具有所需选项的函数定义:

  1. PROJECT 菜单中,选择 Add New Item... (Control+Shift+VS2013中的一个
  2. 转到 SQL Server -> 用户脚本
  3. 选择部署后脚本
  4. 给它一个名字(名字本身并不能确定它是预部署、部署后还是两者都不是;它只需要以 .sql 结尾)并单击 添加
  5. 您将被置于(大部分)空 SQL 脚本中
  6. 输入一个或多个ALTER 语句,类似于以下内容:

    -- declare once
    DECLARE @ObjectName sysname; -- keep lower-case to work in case-sensitive collations
    
    SET @ObjectName = N'Test';
    
    IF (EXISTS(
                SELECT  *
                FROM    sys.assembly_modules sam
                WHERE   sam.[object_id] = OBJECT_ID(@ObjectName)
            )
        )
    BEGIN
        PRINT 'Checking custom properties for [' + @ObjectName + N']...';
    
        IF (EXISTS(
                    SELECT  *
                    FROM    sys.parameters sp
                    INNER JOIN  sys.types st
                            ON  st.system_type_id = sp.system_type_id
                    WHERE   sp.[object_id] = OBJECT_ID(@ObjectName)
                    AND     st.[name] <> N'datetime2' -- keep lower-case to work in
                                                      -- case-sensitive collations
                )
        )
        BEGIN
            PRINT 'Setting custom properties for [' + @ObjectName + N']...';
    
            BEGIN TRY
                EXEC('
    ALTER FUNCTION [dbo].[Test](@d [datetime2])
    RETURNS [datetime2] WITH EXECUTE AS CALLER
    AS EXTERNAL NAME [Test].[Test.UserDefinedFunctions].[Test];
                ');
            END TRY
            BEGIN CATCH
                DECLARE @ErrorMessage NVARCHAR(4000);
                SET @ErrorMessage = ERROR_MESSAGE();
                RAISERROR(@ErrorMessage, 16, 1);
                RETURN;
            END CATCH;
        END;
    
    END;
    ELSE
    BEGIN
        RAISERROR(N'Oops. [%s] was renamed or no longer exists!', 16, 1, @ObjectName);
        RETURN;
    END;
    
    ---
    
    SET @ObjectName = N'NextObjectToFix';
    -- copy the rest from above
    

此部署后脚本将始终包含在 _Create 和发布/增量构建脚本的末尾。因此需要额外的逻辑来查看更改是否已经存在。确实,通常始终运行 ALTER 并没有什么坏处,但在极少数情况下,该对象是其他东西的依赖项,例如检查约束或计算列,这可能是最好的除非它需要改变,否则不要管它。

您可以从\bin\Configuration\*_Create.sql 脚本中获得正确的ALTER 定义(只需将CREATE 更改为ALTER),或者在SSMS 中右键单击对象并选择修改。在这两种情况下,更改数据类型和任何其他选项(显然 ;-)。


另一个有点相关的想法是,对于 T-SQL 包装对象CREATE 语句,根本不依赖 Visual Studio / SSDT 发布过程,而仅使用它来管理程序集。在此设置中,您将取消-选中 Project PropertiesSQLCLR 选项卡上的 Generate DDL 选项。然后,您将添加一个部署后脚本(如上面的建议中所述)并放入您自己的 CREATE FUNCTION ...CREATE PROCEDURE ... 等语句。

对于初始开发而言,引入新对象并不像使用 SSDT 生成 DDL 那样快速和容易,但鉴于新对象的创建并不频繁,而且它们的签名更改频率也低得多,因此管理这个DDL 自己真的没那么糟糕(事实上,它与我用于我的 SQL# 库的过程非常相似,它有超过 250 个对象。务实地说,一旦你拥有每个对象类型中的一个 CREATE 语句,您可以将它们复制并粘贴到新对象并更改名称和参数等。这种方法需要的最多工作是在创建新 TVF 时,如果它返回一堆字段,但这并不是真正的那么多工作如果您使用 SSDT 来管理 DDL 创建,则无论如何您都必须将其输入到 SqlFunction 属性的 TableDefinition 属性中。

【讨论】:

    猜你喜欢
    • 2011-02-06
    • 2021-02-16
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-05-24
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多