使用动态查询从函数返回结果集

时间:2013-08-21 21:09:25

标签: sql-server user-defined-functions

我试图从函数返回结果集,该函数使用Execute语句执行命令字符串。

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

当我运行此代码时,它会返回以下错误:

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

请帮我解决此问题。 感谢

1 个答案:

答案 0 :(得分:5)

抱歉,您无法在函数期间执行动态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的存储过程,并分别找出您的Entity Framework配置问题。作为存储过程,这将更好:

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注入,因此您可能希望添加这些参数仅包含有效值的验证。