2012-12-21 2 views
0

Je veux écrire un UDF SQLCLR qui prend un DATETIME2 et renvoie un DATETIME2. L'entrée et la sortie doivent autoriser les valeurs NULL.SQLCLR NULLable DATETIME2 dans SSDT

je créer un projet de base de données SQL Server (SSDT), configurez comme langue VB dans ses propriétés SQLCLR, puis ajoutez le fichier suivant 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 

L'utilisation de nullable de cette manière apparaît comme elle a été pris en charge depuis SQL Server 2008 par http://msdn.microsoft.com/en-us/library/ms131092(v=SQL.100).aspx.

Cependant, quand je lance la commande deploy, je reçois l'erreur suivante:

SQL46010: Incorrect syntax near).

En effet, le SQL a généré était:

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

Je ne peux pas remplacer SqlDateTime parce que je requiers la gamme complète et précision de DATETIME2.

+0

Je crois que SQLDateTime a la plus haute résolution dans les versions .NET plus récentes. Avez-vous essayé cela? –

+0

Désolé, mon commentaire ci-dessus n'est pas vrai: http://msdn.microsoft.com/fr-fr/library/system.data.sqltypes.sqldatetime(v=vs.110).aspx –

+0

Jason: Je me rends compte qu'il a été quelques années, mais juste au cas où vous cherchiez encore de l'aide à ce sujet, je viens de poster une réponse :-). –

Répondre

0

Jetez un oeil à this. SqlDateTime autorise les valeurs nulles d'où la possibilité de vérifier si la valeur est nulle à travers la propriété de la classe.

+0

Mais SqlDateTime correspond au type 'datetime' moins précis dans SQL Server, donc c'est inacceptable. Pour mapper au type 'datetime2' plus précis dans SQL Server, vos fonctions CLR doivent être de type CLR DateTime, ce qui n'autorise pas les valeurs NULL ... donc nous devons vraiment utiliser Nullable . Si ce n'est pas possible, il y a un sérieux problème. – Triynko

+0

@Triynko Cela aurait été impossible il y a plusieurs versions pour Visual Studio, mais cela vous permet (depuis au moins VS2012, je pense) d'utiliser 'DateTime?'.Le problème est, cependant, que le processus de publication SSDT le traduit toujours en DATETIME du côté T-SQL. J'ai proposé deux solutions de rechange dans ma [réponse] (http://stackoverflow.com/a/32063722/577765). –

0

Le problème est avec SSDT (SQL Server Data Tools) et non avec SQLCLR. Oui, SQLCLR prend en charge DATETIME2 via DateTime?/Nullable<DateTime>. Malheureusement, SSDT (j'utilise VS2013 et SSDT v 12.0.50512.0) ne pas encore (je suis optimiste ici, je sais) soutenir inférer DATETIME2 à partir de DateTime ou DateTime?. Mais, il ne se trompe pas non plus en utilisant DateTime? comme il le faisait. Toutefois, DateTime ou DateTime? s'afficheront comme un DATETIME normal dans le SQL généré.

Je ne suis pas sûr de tout vraiment moyen « approprié » de dire SSDT quel doit être le type de données. Il existe en réalité un certain nombre d'options qui ne sont pas prises en charge, y compris les options UDF générales telles que WITH RETURNS NULL ON NULL INPUT et les options de paramètres telles que les valeurs par défaut. C'est triste et frustrant, oui.

Le meilleur que je suis venu avec à ce jour (et je suis toujours à la recherche d'autres options) est d'ajouter un script après le déploiement à ALTER la définition de la fonction avec les options souhaitées:

  1. Dans le Menu du projet, sélectionnez Ajouter un nouvel élément ... (contrôle + Maj +A dans VS2013)
  2. Aller à SQL Server - > Scripts utilisateur
  3. Sélectionnez Script post-déploiement
  4. Donnez-lui un nom (le nom lui-même ne détermine pas si elle est pré-deploy, post-deploy, ou non plus; il doit juste se terminer par .sql) et cliquez sur Ajouter
  5. Vous serez placé dans une (la plupart du temps) script SQL vide
  6. Entrez dans un ou plusieurs ALTER déclarations, semblable à ce qui suit:

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

Ce script post-déploiement sera toujours être inclus à la fin des scripts de génération _Create et de publication/incrémentielle. D'où la logique supplémentaire de voir si les changements sont déjà présents ou non. Vrai, généralement ne fait pas de mal à toujours exécuter le ALTER, mais dans les rares cas où cet objet est une dépendance de quelque chose d'autre, comme une contrainte de vérification ou une colonne calculée, il est probablement préférable de le laisser seul sauf doit changer.

Vous pouvez obtenir la définition ALTER appropriée soit à partir du script \bin\Configuration\*_Create.sql (juste changer le CREATE-ALTER), ou SSMS si vous faites un clic droit sur l'objet et sélectionnez Modifier . Dans les deux cas, changez le type de données et toutes les autres options (évidemment ;-).


Un autre, un peu lié, idée est de tout simplement pas compter sur le processus d'édition Visual Studio/SSDT pour l'objet wrapper T-SQL CREATE déclarations, et ne l'utiliser que pour gérer l'ensemble. Dans cette configuration, vous un -check l'option Générer sur le DDL SQLCLR onglet de Propriétés du projet. Ensuite, vous ajouteriez un script Post Deployment (comme indiqué dans la suggestion ci-dessus) et ajouteriez vos propres instructions CREATE FUNCTION ..., CREATE PROCEDURE ..., etc. Pour le développement initial, ce n'est pas aussi simple et rapide d'introduire un nouvel objet que d'utiliser SSDT pour générer ce DDL, mais étant donné que les nouveaux objets ne sont pas créés très souvent, et que leurs signatures sont modifiées beaucoup moins fréquemment. ce DDL vous-même n'est vraiment pas si mauvais (et en fait, il est très similaire au processus que j'utilise pour ma bibliothèque SQL# qui a plus de 250 objets.Pratiquement parlant, une fois que vous avez une instruction de chaque type d'objet CREATE, vous pouvez Il suffit de les copier et les coller pour de nouveaux objets et de changer les noms et les paramètres, etc. Le plus de travail que cette approche nécessite est de créer un nouveau TVF s'il renvoie un tas de champs, mais ce n'est pas vraiment beaucoup de travail étant donné avoir à entrer dans le même dans la TableDefinition propriété de la SqlFunction attr Ibute de toute façon si vous utilisiez SSDT pour gérer la création DDL.