0

I am trying to return result set from function which use executes a command string with Execute statement.

create function GetLogApiCalls
(
@displayStart int,
@displayLength int,
@searchString nvarchar(1000),
@orderBy nvarchar(100),
@orderByDirection varchar(50)
)
returns @logs table(
[Id]                INT     NOT NULL,
[Provider]          NVARCHAR (10)    NULL,
[RequestIdentifier] UNIQUEIDENTIFIER NULL,
[RequestData]       NVARCHAR (MAX)   NULL,
[ResponseData]      NVARCHAR (MAX)   NULL,
[UserName]          NVARCHAR (50)    NULL,
[AccountName]       NVARCHAR (100)   NULL,
[AccountId]         INT              NULL,
[CreatedUserId]     INT              NULL,
[CreatedDate]       DATETIME         NULL,
[MethodName]        NVARCHAR (100)   NULL
)
AS
BEGIN
   
DECLARE @sqlScript NVARCHAR(1000)

IF(@searchString IS NOT NULL AND LEN(@searchString) > 0)
BEGIN
SET @sqlScript = N'SELECT * FROM 
                (
                 SELECT ROW_NUMBER() OVER (ORDER BY '+@orderBy+' '+@orderByDirection+') AS rn , * from LogAPICall where Provider like ''%'+@searchString+'%'' or MethodName like ''%'+@searchString+'%''
                ) as result where result.rn between '+ CONVERT(varchar,@displayStart) + ' and ' + CONVERT(varchar,@displayLength)
END
ELSE
BEGIN
SET @sqlScript = N'SELECT * FROM 
                (
                 SELECT ROW_NUMBER() OVER (ORDER BY '+@orderBy+' '+@orderByDirection+') AS RN , * FROM LogAPICall
                ) AS result WHERE result.rn between ' + CONVERT(varchar,@displayStart) + ' and ' + CONVERT(varchar,@displayLength)
END

 insert @logs
 Execute(@sqlScript)
return
END

When I run this code, it returns following error :

Invalid use of a side-effecting operator 'INSERT EXEC' within a function.

Why?

4

1 回答 1

8

抱歉,您不能在函数中执行动态 SQL,句号。您可以重写您的函数以不需要动态 SQL,但这真的解决了根本问题吗?当您需要一个过程并且仍然被这个完全不相关的涉及返回类型的实体框架问题所束缚时,您会怎么做?显然,您配置 EF 或连接程序的方式有问题,否则很多人会抱怨 EF 不适用于返回结果集的程序。你认为这可能吗?

CREATE FUNCTION dbo.GetLogApiCalls -- dbo prefix, always
(
  @displayStart     INT,
  @displayLength    INT, -- is this a page size, like 20, or @displayEnd?
  @searchString     NVARCHAR(1000),
  @orderBy          NVARCHAR(100),
  @orderByDirection VARCHAR(4)
)
RETURNS TABLE
AS -- make it an inline TVF. Multi-statement TVFs can be a perf nightmare.
RETURN 
(
  SELECT * FROM 
  (
    SELECT rn = ROW_NUMBER() OVER (ORDER BY 
     CASE @orderByDirection WHEN 'ASC' THEN
      CASE @orderBy WHEN N'Provider'     THEN Provider
                    WHEN N'RequestData'  THEN RequestData
                    WHEN N'ResponseData' THEN ResponseData
                    WHEN N'UserName'     THEN UserName
                    WHEN N'AccountName'  THEN AccountName
                    WHEN N'MethodName'   THEN MethodName
                    WHEN N'RequestIdentifier'
                                         THEN CONVERT(CHAR(36), RequestIdentifier)
                    WHEN N'CreatedDate'  THEN CONVERT(CHAR(23), CreatedDate, 126)
      END
     END,
     CASE @orderByDirection WHEN 'ASC' THEN
      CASE @orderBy WHEN N'Id'            THEN Id
                    WHEN N'AccountId'     THEN AccountId
                    WHEN N'CreatedUserId' THEN CreatedUserId
      END
     END,
     CASE @orderByDirection WHEN 'DESC' THEN
      CASE @orderBy WHEN N'Provider'     THEN Provider
                    WHEN N'RequestData'  THEN RequestData
                    WHEN N'ResponseData' THEN ResponseData
                    WHEN N'UserName'     THEN UserName
                    WHEN N'AccountName'  THEN AccountName
                    WHEN N'MethodName'   THEN MethodName
                    WHEN N'RequestIdentifier' 
                                         THEN CONVERT(CHAR(36), RequestIdentifier)
                    WHEN N'CreatedDate'  THEN CONVERT(CHAR(23), CreatedDate, 126)
      END
     END DESC,
     CASE @orderByDirection WHEN 'DESC' THEN
      CASE @orderBy WHEN N'Id'            THEN Id
                    WHEN N'AccountId'     THEN AccountId
                    WHEN N'CreatedUserId' THEN CreatedUserId
      END
     END DESC), [Id],[Provider],[RequestIdentifier],[RequestData],
     [ResponseData],[UserName],[AccountName],[AccountId],
     [CreatedUserId],[CreatedDate],[MethodName]
  FROM dbo.LogAPICall
  WHERE LEN(@searchString) = 0 OR 
  (
    @searchString > ''  AND 
    (   
         Provider   LIKE '%' + @searchString + '%'
      OR MethodName LIKE '%' + @searchString + '%'
    )
  )
) AS x 
WHERE rn BETWEEN @displayStart AND @displayStart + @displayLength - 1 -- assumption
);

不过,这种动态ORDER BY是魔鬼。即使作为内联 TVF,这也只能通过点击并添加OPTION (RECOMPILE)任何引用查询来对不同的参数表现良好。解决方案?使用带有动态 SQL 的存储过程,并单独找出您的实体框架配置问题。作为一个存储过程,这会更好:

CREATE PROCEDURE dbo.GetLogApiCalls -- dbo prefix, always
  @displayStart     INT,
  @displayLength    INT, -- is this a page size, like 20, or @displayEnd?
  @searchString     NVARCHAR(1000),
  @orderBy          NVARCHAR(100),
  @orderByDirection VARCHAR(4)
AS
BEGIN
  SET NOCOUNT ON;

  DECLARE @sql NVARCHAR(MAX) = N'SELECT * FROM (
    SELECT rn = ROW_NUMBER() OVER (ORDER BY ' 
        + @orderBy + ' ' + @orderByDirection + '), 
       [Id],[Provider],[RequestIdentifier],[RequestData],
       [ResponseData],[UserName],[AccountName],[AccountId],
       [CreatedUserId],[CreatedDate],[MethodName]
    FROM dbo.LogAPICall'
    + CASE WHEN LEN(@searchString) > 0 THEN
    ' WHERE Provider LIKE ''%'' + @searchString + ''%''
       OR MethodName LIKE ''%'' + @searchString + ''%'''
       ELSE '' END
    + ') AS x 
       WHERE rn BETWEEN @displayStart 
         AND @displayStart + @displayLength - 1;';

  DECLARE @params NVARCHAR(MAX) = N'@searchString NVARCHAR(1000),'
    + '@displayStart INT, @displayLength INT';

  EXEC sp_executesql @sql, @params, @searchString, @displayStart, @displayLength;
END
GO

特别是如果您optimize for ad hoc workloads启用了该设置。现在,由于动态 ORDER BY 无法参数化,这仍然容易发生 SQL 注入,因此您可能需要添加验证,以确保这些参数仅包含有效值。

于 2013-08-21T21:12:55.873 回答