SQL Server 2012 中的 FMTONLY 问题

Dav*_*rke 1 sql-server linked-server sql-server-2012

我支持使用 CodeSmith 和 NetTiers 模板生成 C# 代码的应用程序。CodeSmith 检查数据库并使用SET FMTONLY ON设置来确定应为其生成代码的列。

不幸的是,在从 SQL Server 2005 迁移到 2012 的过程中,在某些特定情况下,这不再起作用。有一个在链接服务器上执行存储过程的存储过程,并且为该存储过程生成的代码不正确。

我已经设法将问题EXECSET FMTONLY ON. 以下(仅示例)SELECT适用于 2005 和 2012 实例:

SET FMTONLY ON
SELECT TOP(10) [MCMCU]
              ,[MCSTYL]
              ,[MCDC]
FROM [JDE].[JDE_CRP].[CRPDTA].[F0006]
SET FMTONLY OFF
Run Code Online (Sandbox Code Playgroud)

正如预期的那样,这仅返回列标题。以下仅适用于 2005 实例:

SET FMTONLY ON
EXEC('SELECT TOP(10) [MCMCU]
                    ,[MCSTYL]
                    ,[MCDC]
      FROM [JDE_CRP].[CRPDTA].[F0006]') AT [JDE]
SET FMTONLY OFF
Run Code Online (Sandbox Code Playgroud)

在 2012 实例上运行时,SSMS 显示消息“命令成功完成”,但不显示列标题。

有什么我在这里想念的吗?也许是我需要更改的设置?链接服务器定义是相同的,包括用于连接的身份。是的,我知道这FMTONLY已被弃用,但我无法更改 CodeSmith 询问数据库的方式。

Sol*_*zky 5

我可以在 SQL Server 2012 上重现相同的行为。但是,我可以通过将链接服务器名称放回查询中来使其工作。你有什么理由把它移到AT子句中吗?

请尝试以下操作:

SET FMTONLY ON
EXEC('SELECT TOP(10) [MCMCU]
                    ,[MCSTYL]
                    ,[MCDC]
      FROM [JDE].[JDE_CRP].[CRPDTA].[F0006]');
SET FMTONLY OFF
Run Code Online (Sandbox Code Playgroud)

好吧,虽然以上在 SQL Server 2012 上确实有效,但它不是一个好的测试,因为它在执行存储过程时不起作用。它可能与不同类型的调用有关(即存储过程调用是 RPC,必须单独配置)。

我什至只是尝试添加WITH RESULT SETS ((...)),但仍然无效。


好的,所以我找到了一些东西。众所周知,FMTONLY ON它实际上并不运行代码,它只是扫描它的SELECT语句。但是,这也意味着它不知道如何处理条件代码。所以它返回任何可以返回的结果集,即使没有代码路径会返回其中的一些。

示例 1 有两个SELECT语句,但一次只能返回一个。尽管如此,当使用 运行时FMTONLY ON,两者都会返回:

CREATE PROCEDURE dbo.FmtOnlyTest1
AS
SET NOCOUNT ON;
  DECLARE @A INT = 5;

  IF (@A <> 6)
  BEGIN
    SELECT TOP 5 * FROM [master].[sys].[objects];
  END;
  ELSE
  BEGIN
    DECLARE @SQL NVARCHAR(MAX);
    SET @SQL = N'SELECT ' + CONVERT(NVARCHAR(MAX), @A) + N' AS [IntVal];'
    EXEC(@SQL);
END;
GO
Run Code Online (Sandbox Code Playgroud)

结果:

-- Running normally returns one result set of 5 rows from sys.objects:
EXEC Test.dbo.FmtOnlyTest1;

-- With FMTONLY ON, it returns two result sets: one from sys.objects AND one with [IntVal]
SET FMTONLY ON
EXEC Test.dbo.FmtOnlyTest1;
SET FMTONLY OFF
Run Code Online (Sandbox Code Playgroud)

示例 2 显示了无法运行代码的另一个后果。如果代码动态生成结果集,则无法通过FMTONLY ON. 这可能解释了为什么当代码是远程的时它不能跟随 EXEC 调用。我怀疑调用本地存储过程只需在本地系统目录表中查找该定义即可。

CREATE PROCEDURE dbo.FmtOnlyTest2
AS
SET NOCOUNT ON;
  DECLARE @A INT;

  SELECT TOP 1 @A = [object_id] FROM sys.objects;

  DECLARE @SQL NVARCHAR(MAX);
  SET @SQL = N'SELECT ' + CONVERT(NVARCHAR(MAX), @A) + N' AS [IntVal];'
  EXEC(@SQL);
GO
Run Code Online (Sandbox Code Playgroud)

结果:

-- Running normally returns one result set of just [IntVal];
EXEC Test.dbo.FmtOnlyTest2;

-- With FMTONLY ON, there are no result sets returned (just like with the remote procedure)
SET FMTONLY ON
EXEC Test.dbo.FmtOnlyTest2;
SET FMTONLY OFF
Run Code Online (Sandbox Code Playgroud)

知道我们现在所知道的,我们可以使用这样一个事实,即FMTONLY ON在不实际运行代码的情况下找到所有结果集,即使不理想,也可以伪造结果集以使 CodeSmith(和其他仍然以这种方式工作的工具)再次工作.

示例 3 表明我们可以有效地在无法逻辑执行的代码块中隐藏虚拟结果集。只有FMTONLY ON能够看到这一点,所以它不会给任何其他代码带来问题(好吧,我确实尝试过sys.dm_exec_describe_first_result_set,但并不真正喜欢它,但我认为在这种情况下这不是问题)。

CREATE PROCEDURE dbo.FmtOnlyTest3
AS
SET NOCOUNT ON;

  IF (1 = 0)
  BEGIN
    SELECT CONVERT(INT, NULL) AS [FieldName1],
           CONVERT(DATETIME, NULL) AS [FieldName2];
  END;

  DECLARE @A INT;

  SELECT TOP 1 @A = [object_id] FROM sys.objects;

  DECLARE @SQL NVARCHAR(MAX);
  SET @SQL = N'SELECT ' + CONVERT(NVARCHAR(MAX), @A) + N' AS [IntVal];'
  EXEC(@SQL);
GO
Run Code Online (Sandbox Code Playgroud)

结果:

-- Running normally returns one result set of just [IntVal];
EXEC Test.dbo.FmtOnlyTest3;

-- Running with FMTONLY ON returns one result set of [FieldName1], [FieldName2]
SET FMTONLY ON
EXEC Test.dbo.FmtOnlyTest3;
SET FMTONLY OFF
Run Code Online (Sandbox Code Playgroud)

对于这个测试,我让代码为IF (1 = 0)块返回不同的结果集,以使行为差异更加明显。但实际上,您可能希望在 中返回SELECT与远程存储过程返回完全相同的结果集结构。

这里明显的缺点是,如果您更改该远程过程的结果集,您将必须记住也要更改IF (1 = 0)块中的定义,但至少 CodeSmith 会起作用。