SqlException:执行存储过程中“必须声明标量变量”

时间:2019-05-09 08:33:57

标签: sql-server stored-procedures .net-core entity-framework-core

我写了一个存储过程:

CREATE PROCEDURE [dbo].[GetAudioBookStats]
    @UserId NVARCHAR(450) = NULL
    ,@ArtistId BIGINT = 0
    ,@PublisherId BIGINT = 0
    ,@AudioBookId BIGINT = 0
    ,@SubscriptionType INT = 0
    ,@ResultStat BIGINT OUTPUT
AS
BEGIN
    -- SET NOCOUNT ON added to prevent extra result sets from
    -- interfering with SELECT statements.
    SET NOCOUNT ON;

SELECT  DISTINCT
        @ResultStat = COUNT (*)
FROM    dbo.AudioBookDownload AS d
        LEFT JOIN dbo.AudioBooks AS b ON d.AudioBookId = b.Id
WHERE   (   @ArtistId IS NULL
            OR  b.WriterArtistId = @ArtistId
            OR  b.TranslatorArtistId = @ArtistId
            OR  b.NarratorArtistId = @ArtistId)
        AND (@PublisherId IS NULL
                OR b.PublisherId = @PublisherId)
        AND (@AudioBookId IS NULL
                OR b.Id = @AudioBookId)
        AND (@SubscriptionType IS NULL
                OR d.SubscriptionType = @SubscriptionType)
        AND (@UserId IS NULL
                OR d.UserId = @UserId);

END;

我以这种方式在服务中称呼它

        var outDownloadCount = new SqlParameter("ResultStat", SqlDbType.BigInt)
        {
            Direction = ParameterDirection.Output
        };

        const string sql = "EXECUTE dbo.[GetAudioBookStats] @UserId, @ArtistId, @PublisherId, @AudioBookId, @SubscriptionType, @ResultStat OUTPUT";

        await context
            .Database
            .ExecuteSqlCommandAsync(sql, userId, artistId, publisherId, audioBookId, subscriptionType, outDownloadCount);

        var result = (int)outDownloadCount.Value;

在执行中所有参数都设置为null,但是出现此错误:

  

SqlException:必须声明标量变量“ @UserId”。

如果有帮助,则使用ASP.net core 2.2开发该项目。

我在做什么错了?

1 个答案:

答案 0 :(得分:1)

这是the XY Problem的情况。您遇到了X问题(如何动态添加查询条件),并假设Y是解决方案(全部捕获查询)。但是,当您遇到Y问题时,您会询问Y而不是原始问题X。

回答Y

在SQL Server中,参数名称始终以@开头。您在EF Core中定义的参数名称应为@ResultStat@UserID,而不仅仅是ResultStatUserID,例如:

var userId = new SqlParameter("@UserID", SqlDbType.NVarChar,450);

var outDownloadCount = new SqlParameter("@ResultStat", SqlDbType.BigInt)
{
        Direction = ParameterDirection.Output
};

避免使用所有存储过程

尽管您根本不需要该包罗万象的存储过程,但实际上这会损害性能。这是因为创建存储过程执行计划并对其进行缓存,以便在首次调用存储过程时可以重复使用。

忽略字段的查询的执行计划与尝试使用特定参数进行过滤的查询的执行计划完全不同。如果对存储过程的首次调用包含某个参数的NULL,则优化器将创建一个执行计划,该计划使用覆盖该字段的索引。当使用参数值调用存储过程时,服务器将使用不使用这些索引的缓存执行计划。

检查恰当命名的How to Confuse the SQL Server Query Optimizer以获得详细说明

动态添加条件的简便快捷方式

像EF这样的ORM和像LINQ这样的语言完全消除了对全部查询的需求。编写条件查询很简单:

var query=context.AudioBookDownloads;
if (someUserId!=null)
{
    query=query.Where(dl=>dl.UserId==someUserId);
}
if (someArtistId>0)
{
    query=query.Where(dl=> dl.Book.ArtistId == someArtistId 
                        || dl.Bookbook.WriterArtistId == someArtistId );
}
....

var count=query.CountAsync();

就是这样。 EF本身将创建仅包含所需条件的查询,并将多个条件与AND组合在一起。