0

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

このコードを実行すると、次のエラーが返されます。

関数内での副作用演算子「INSERT EXEC」の使用は無効です。

なんで?

4

1 に答える 1

8

申し訳ありませんが、関数ピリオドで動的 SQL を実行することはできません。動的 SQL を必要としないように関数を書き直すことはできますが、それによって根本的な問題は本当に解決されるのでしょうか? プロシージャが必要であり、戻り値の型に関するこのまったく無関係な Entity Framework の問題によってまだ手錠をかけられている場合、あなたはどうしますか? 明らかに、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 インジェクションが発生しやすいため、これらのパラメーターに有効な値のみが含まれていることを検証する必要がある場合があります。

于 2013-08-21T21:12:55.873 に答える