3

I'm trying to stand up an old solution that's publishing a number of .net functions to a SQL Server database. But the attempt to publish to a new database is failing on a function that manipulates dates.

The function that's failing is:

[SqlFunction(TableDefinition="localtime datetime2", IsDeterministic=true, IsPrecise=true,
             DataAccess=DataAccessKind.None,
             SystemDataAccess=SystemDataAccessKind.None)]
public static DateTime ConvertFromUTC(DateTime utctime, string timezoneid)
{
    if (utctime.Kind == DateTimeKind.Unspecified)
        utctime = DateTime.SpecifyKind( utctime, DateTimeKind.Utc );

    utctime = utctime.ToUniversalTime();

    return TimeZoneInfo.ConvertTimeBySystemTimeZoneId( utctime, timezoneid );
}

The error message I get when attempting to publish is:

Creating [dbo].[ConvertFromUTC]...

(268,1): SQL72014: .Net SqlClient Data Provider:

Msg 6551, Level 16, State 2, Procedure ConvertFromUTC, Line 1
CREATE FUNCTION for "ConvertFromUTC" failed because T-SQL and CLR types for return value do not match.

(268,0): SQL72045: Script execution error

SQL generated from the .net in an attempt to add the function:

CREATE FUNCTION [dbo].[ConvertFromUTC]
    (@utctime DATETIME, @timezoneid NVARCHAR (MAX))
RETURNS TABLE ([localtime] DATETIME2 (7) NULL)
AS EXTERNAL NAME [database].[IntelligentTutor.Database.Functions].[ConvertFromUTC]

SQL definition for the version of the function in the existing database (which confirms that @MattJohnson was right about how it needs fixed):

CREATE FUNCTION [dbo].[ConvertFromUTC]
    (@utctime [datetime], @timezoneid [nvarchar](4000))
RETURNS [datetime] WITH EXECUTE AS CALLER
AS EXTERNAL NAME [database].[IntelligentTutor.Database.Functions].[ConvertFromUTC]
  • In addition to the suggestions noted in @Matt's answer, you should also consider the following: remove `TableDefinition="localtime datetime2",` from the attribute; use `SqlString` instead of `string` for `timezoneid` (which will require changing `timezoneid` in the `return` to be `timezoneid.Value`); and change `@timezoneid` from `NVARCHAR(MAX) `to NVARCHAR(50) or whatever size makes sense that is not `MAX`. Also, please be aware that the `TimeZoneInfo` class has a memory leak, which is why it requires the Assembly to be marked as `UNSAFE`. – Solomon Rutzky Jul 12 '17 at 20:13
  • For more information about working with SQLCLR in general, please see the series I am writing on this topic on SQL Server Central: [Stairway To SQLCLR](http://www.sqlservercentral.com/stairway/105855/) (free registration is required to read their content, but it's worth it). – Solomon Rutzky Jul 12 '17 at 20:27
  • @srutzky - [`HostProtectionAttribute.MayLeakOnAbort`](https://msdn.microsoft.com/en-us/library/system.security.permissions.hostprotectionattribute.mayleakonabort.aspx) does not mean "has a memory leak". – Matt Johnson-Pint Jul 12 '17 at 21:06
  • @MattJohnson Thanks for that link. So just means "_can_ leak, _if_ aborted", right? As in, won't leak if allowed to complete successfully? – Solomon Rutzky Jul 12 '17 at 21:38
  • Right. It's a highly unlikely event. One could even argue that the attribute isn't necessary on this particular class, though there's not much can be done about that now. – Matt Johnson-Pint Jul 12 '17 at 22:20
  • @MattJohnson Thanks for confirming that. I do recall a question on SQL Server Central from a few years ago in which someone was experiencing a memory leak using `TimeZoneInfo` and doing bulk updates. Of course, I didn't get a chance to fully investigate to see if calling `GC` would fix it or if it ever corrected itself over a long-enough period of time. It's possible the O.P. there was just impatient ;-) – Solomon Rutzky Jul 13 '17 at 15:49

1 Answers1

3

The SQL function doesn't match the .NET method signature. To make it match:

  1. Change your the type of @utctime to DATETIME2 instead of DATETIME in the function definition.

  2. Change the return type to just RETURNS DATETIME2 instead of returning a table with a nullable datetime2 column.

Also, note that if you are on SQL 2016 or later, or on Azure SQL DB, you don't need this function, as you can now use AT TIME ZONE instead.

Matt Johnson-Pint
  • 230,703
  • 74
  • 448
  • 575
  • I need to get the existing site running (all I got was a snapshot of code apparently copied off the web server and a DB dump, no documentation at all) before I can think about starting to apply a dozen+ years of skipped code modernization to it. Would what I currently have have worked with an older pairing of .net/sql server; or do I need to climb even deeper into the rabbit hole trying to figure out what's going on. – Dan Is Fiddling By Firelight Jul 12 '17 at 20:13
  • @DanNeely There is no way that the code you posted in the question ever would have worked. The only way to return a table is if the return type in .NET is `IEnumerable`. And I don't think you can pass back a table without having a FillRowMethod noted in the `SqlFunction` attribute. This is all in addtion to the two items mentioned by Matt in this answer (which is entirely correct, btw, so +1 there). – Solomon Rutzky Jul 12 '17 at 20:16
  • Right. It wouldn't have worked, because the .net code returns a single value, and your SQL wrapper expects a table. Scalar-valued functions are different than table-valued functions. [Read here](https://learn.microsoft.com/en-us/sql/relational-databases/clr-integration-database-objects-user-defined-functions/clr-user-defined-functions). – Matt Johnson-Pint Jul 12 '17 at 20:16
  • ok. I have a backup of the existing sites production database. Is there a way to view whatever was published to that so I can make sure whatever fix I do with the C# corresponds to whatever the previous dev actually managed to get into the system somehow? – Dan Is Fiddling By Firelight Jul 12 '17 at 20:33
  • Yes, but that's a different question. Please give it a try and normal research first, and if you get stuck on that, then ask about that in a new question. Remember, the primary goal of StackOverflow is to create question/answer pairs that are valuable to a large audience, not just to assist with your particular issue. Thanks. – Matt Johnson-Pint Jul 12 '17 at 20:42
  • @DanNeely If this is presently working in Production, then at the very least you can script out the `CREATE FUNCTION` statement in SQL Server Management Studio (SSMS). That should get you started :). – Solomon Rutzky Jul 12 '17 at 21:09
  • 1
    Followup posted: https://stackoverflow.com/questions/45083492/how-can-i-get-the-definition-of-a-sql-clr-function-from-the-database – Dan Is Fiddling By Firelight Jul 13 '17 at 14:24