从 C# 调用时,来自 SQL Server 的标量值函数不返回值

Posted

技术标签:

【中文标题】从 C# 调用时,来自 SQL Server 的标量值函数不返回值【英文标题】:Scalar valued function from SQL Server not returning value when called from C# 【发布时间】:2022-01-06 04:51:22 【问题描述】:

我被赋予了这个功能

CREATE FUNCTION [dbo].[GET_WEBGIS_ISSUE_NUM]
    ()
RETURNS VARCHAR(50)
AS 
BEGIN
    DECLARE @v_new_num int, @v_new_issue_num varchar(50);
    
    SET @v_new_num = (SELECT COUNT(*) + 1 
                      FROM [dbo].[WEBGIS_ISSUE] 
                      WHERE [ISSUE_NUM] LIKE  CONCAT(FORMAT(GETDATE(), 'yyMM'), '%'));

    IF @v_new_num < 10 
        SET @v_new_issue_num = CONCAT(FORMAT(GETDATE(), 'yyMM'), '00', @v_new_num);

    ELSE IF @v_new_num < 100
        SET @v_new_issue_num = CONCAT(FORMAT(GETDATE(), 'yyMM'), '00', @v_new_num);

    ELSE
        SET @v_new_issue_num = CONCAT(FORMAT(GETDATE(), 'yyMM'), @v_new_num);
    
    RETURN @v_new_issue_num 
END;

我尝试从以下 C# 代码调用它

SqlConnection cnn = new SqlConnection(connectionString);

SqlCommand cmd = new SqlCommand();
cmd.Connection = cnn;
cmd.CommandType = CommandType.StoredProcedure;
cmd.CommandText = "[NEPS].[dbo].[GET_WEBGIS_ISSUE_NUM]";

//add any parameters the stored procedure might require
if (cmd.Connection.State == ConnectionState.Closed) //cmd.Connection.Open();

    cnn.Open();
    var o = cmd.ExecuteScalar();
    //blabla
    cnn.Close();

但是当我调试代码时,我一直收到 null。

注意:连接正常,已连接,当我尝试更改函数名称时会产生错误,当我通过 SQL Server 检查时,它也会返回适当的返回值。

【问题讨论】:

cmd.CommandType = CommandType.StoredProcedure? GET_WEBGIS_ISSUE_NUM 不是存储过程;这是一个标量函数... 您需要使用 CommandType.Text 并运行 SELECT * FROM dbo.GET_WEBGIS_ISSUE_NUM() - 但这真的 BADLY 闻起来像黑客在做与 IDENTITY 列相同的事情 - 只是不是正确和安全........ 如果SEQUENCE 尝试服务于类似于IDENTITY 的目的但用于多个表,那么它可能是一个更好的选择。 SEQUENCE 在所有受支持(包括扩展支持)版本的 SQL Server 上都可用,所以如果你是这样的话,我看不出你为什么不使用它。 你需要一个实际的查询SELECT dbo.GET_WEBGIS_ISSUE_NUM()。虽然为什么这个功能甚至存在是另一个问题。至少它应该是一个 inline 表值函数,它要快得多。您还需要使用using 处理您的连接和命令对象。而且你不需要if (cmd.Connection.State,如果你刚刚创建它,它就没有理由打开 最后但并非最不重要的一点是,尝试使用 COUNT(*) + 1 生成“唯一”数字是高度可疑的,因为如果删除记录(即使没有并发),这似乎很容易破坏,所以你' d 必须提交一个绝对只追加的表。基于MAX 的方法似乎更稳定(但在并发下仍然不安全,请注意)。 【参考方案1】:

使用SELECT 语句返回标量函数结果更为常见。使用EXECUTE时(由于CommandType.StoredProcedure),还需要指定返回参数,执行后从参数中取出结果:

var result = cmd.Parameters.Add("@result", SqlDbType.VarChar, 50);
result.Direction = ParameterDirection.ReturnValue;
cmd.ExecuteNonQuery(); //ExecuteScalar will work too but the result is null and you still need to use the parameter
var o = result.Value;

正如您问题的 cmets 中所述,请考虑这种方法的并发影响。将返回重复值,直到行数发生变化。

【讨论】:

【参考方案2】:

您将标量函数视为存储过程,而这种执行类型是错误的类型。您需要使用标量函数来“CommandType.Text”。

关于C# 部分的其他说明:

using 块与 SqlConnectionSqlCommand 一起使用(让 using 子句为您处理处置和关闭连接部分)。 查询应声明为const string 始终以分号结束查询(即使它在没有分号的情况下在 SQL Server 中运行)。 避免使用短名称,为变量选择易读的命名。

这是 C# 代码:

const string query = "SELECT [NEPS].[dbo].[GET_WEBGIS_ISSUE_NUM]();";

using(SqlConnection connection = new SqlConnection(connectionString))
    using(SqlCommand command = new SqlCommand(query, connection))
    
        connection.Open();
        var result = command.ExecuteScalar();

        // do stuff
    

对于函数GET_WEBGIS_ISSUE_NUM,也许你可以用这一行避免额外的IFs:

CREATE FUNCTION [dbo].[GET_WEBGIS_ISSUE_NUM]
    ()
RETURNS VARCHAR(50)
AS 
BEGIN
    DECLARE @v_new_num int, @v_new_issue_num varchar(50);
    
    SET @v_new_num = (SELECT COUNT(*) + 1 
                      FROM [dbo].[WEBGIS_ISSUE] 
                      WHERE [ISSUE_NUM] LIKE  CONCAT(FORMAT(GETDATE(), 'yyMM'), '%'));

    SET @v_new_issue_num, FORMAT(GETDATE(), 'yyMM') + RIGHT('000' + CAST(@v_new_num AS VARCHAR), 4);
    
    RETURN @v_new_issue_num 
END;

【讨论】:

以上是关于从 C# 调用时,来自 SQL Server 的标量值函数不返回值的主要内容,如果未能解决你的问题,请参考以下文章

SqlDataReader 和 T-SQL:在使用来自 C# 的“异步”调用时使用“try...catch”和“throw”时不会传播异常

使用 C# 和存储过程从 SQL Server 检索 VarChar(MAX)

从 SQL Server 触发器调用 Freshdesk API

SQL Server 中标量、表值和聚合函数之间的区别?

从 C# 到 SQL Server 的精度损失

在 SQL Server 中计算 GS1 校验位的标量值函数