从 C# 查询 returns 与 SSMS 不同的结果

Query returns different result from C# then from SSMS

我有一个无法解释的奇怪问题

当我 运行 来自 C# 应用程序的某个查询时,它 returns 与我在 SSMS 中 运行 它时的结果不同,但当我使用参数时 [=20] =]

命令是这样的

select dbo.fnGetPlaceID('6025', null, null)

它 returns -1 当 运行 来自 c# 应用程序时, returns 1489 当 运行 来自 SSMS

但如果我这样做

declare @DealerCode varchar(30) = '6025'
declare @RegionCode varchar(50) = null
declare @MerkID int = null
select dbo.fnGetPlaceID(@DealerCode, @RegionCode, @MerkID)

然后它 returns 正确的值 1489 也来自 c# 应用程序

那有什么区别呢?

附加代码:

CREATE function dbo.fnGetPlaceID(@DealerCode varchar(30), @RegionCode varchar(50), @MerkID int) returns int as
begin
    declare @Result int = -1
    
    if @RegionCode = ''
       set @RegionCode = null
    
    select @Result = r.RelationID
    from   relation.tblRelation r
    where  r.dealercode = @DealerCode
    
    -- in the example of '6025' there is only one row, I checked this
    -- So the code below should not be executed    
    if @@ROWCOUNT <> 1
    begin
         select @Result = rel.RelationID
         from   relation.tblRelation rel
           left outer join dbo.tblRegionCodes r on rel.RegionCodeID = r.FordRegionCodeID
         where  rel.dealercode = @DealerCode
         and    (@RegionCode is null or r.RegionCode = @RegionCode)
         and    (@MerkID is null or rel.BrandID = @MerkID)
         
         if @@ROWCOUNT <> 1
            set @Result = -1
    end
    
    return @Result
end

和c#中的代码

FillDataTable(myDataTable, "select dbo.fnGetPlaceID('6025', null, null)")

public void FillDataTable(DataTable table, string SqlText, int commandTimeOut = 300)
{
    if (_ConnectionString != null && _ConnectionString != "")
    {
        using (SqlConnection connection = new SqlConnection(_ConnectionString))
        {
            OpenConnection(connection, SqlText);

            using (SqlCommand command = new SqlCommand(SqlText, connection))
            {
                command.CommandType = CommandType.Text;
                command.CommandTimeout = commandTimeOut;

                using (SqlDataAdapter adapter = new SqlDataAdapter(command))
                {
                    try
                    {
                        adapter.Fill(table);
                    }
                    catch (Exception ex)
                    {
                        throw new Exception(ex.Message);
                    }
                }
            }
        }
    }
}

我试过的

然后我尝试在 sql 函数中进行此更改

alter function dbo.fnGetPlaceID(@DealerCode varchar(30), @RegionCode varchar(50), @MerkID int) returns int as
begin
    declare @Result int = -1
    
    if @RegionCode = ''
       set @RegionCode = null
    
    select @Result = r.RelationID
    from   relation.tblRelation r
    where  r.dealercode = @DealerCode
/*    
    -- in the example of '6025' there is only one row, I checked this
    -- So the code below should not be executed
    if @@ROWCOUNT <> 1
    begin
         select @Result = rel.RelationID
         from   relation.tblRelation rel
           left outer join dbo.tblRegionCodes r on rel.RegionCodeID = r.FordRegionCodeID
         where  rel.dealercode = @DealerCode
         and    (@RegionCode is null or r.RegionCode = @RegionCode)
         and    (@MerkID is null or rel.BrandID = @MerkID)
         
         if @@ROWCOUNT <> 1
            set @Result = -1
    end
*/    
    return @Result
end

现在它在 c# 应用程序中确实可以正常工作。

因此,出于某种原因,当我从 C# 应用程序调用此函数时,@@ROWCOUNT 似乎不等于 1,而当我从 SSMS 调用此函数时,@@ROWCOUNT 等于 1。
我没有证实这一点,但这是我的结论。
问题仍然存在,为什么?
还有为什么当我使用参数时它的工作方式不同?

我已通过将函数更改为

解决了我的问题
select @Count = count(1) 
from   relation.tblRelation r 
where  r.DealerCode = @DealerCode

然后使用变量@Count 代替 rowcount,但我仍然想对这里发生的事情做一些解释。

我还检查了如果找到多个行,第一个查询会发生什么情况。
因为我将查询结果放在一个变量中,所以如果找到不止一行,它应该会失败(我知道我的函数中有一个错误,我现在已经修复了它)

declare @Result int = -1

select @Result = r.RelationID
from   relation.tblRelation r
where  r.dealercode is null

select @@ROWCOUNT

但是并没有像我预期的那样抛出异常,rowcount 的值为 4512

编辑

正如@DanGuzman 所建议的那样,当我将 TSQL_SCALAR_UDF_INLINING 设置为 OFF

时它会起作用
select dbo.fnGetPlaceID('6025', null, null)
OPTION (USE HINT('DISABLE_TSQL_SCALAR_UDF_INLINING'))

编辑 2

如果我这样做,它也可以在没有提示的情况下工作ALTER DATABASE [myDataBase] SET COMPATIBILITY_LEVEL = 140;它是在 150

最终更新

我今天安装了 CU15,并在兼容级别设置为 150 的情况下再次进行了测试,现在它也可以在 c# 中正常运行。所以这个方案现在确认了。

自初始 SQL Server 2019 RTM 版本以来,标量函数内联存在许多问题,这些问题已得到修复。安装最新的累积更新以获得这些修复和其他修复。

不打补丁,work-arounds包括:

  • 禁用查询提示内联OPTION (USE HINT('DISABLE_TSQL_SCALAR_UDF_INLINING'))
  • 使用 USE YourDatabase;ALTER DATABASE SCOPED CONFIGURATION SET TSQL_SCALAR_UDF_INLINING = OFF;
  • 在数据库级别禁用内联
  • 将数据库兼容级别更改为 140 或更低

最新的SQL服务器补丁下载链接列在this page

关于 C# 代码和 SSMS 的不同行为,这是由于不同的执行计划。 SSMS 查询默认指定 ARITHABORT ON 会话设置,而 C# 不设置该选项。不同的会话设置可能会产生不同的执行计划,详情请参阅 difficult to troubleshoot. See Erland Sommarskog's 文章。我要补充一点,SSMS 设置 ARITHABORT 只是为了向后兼容。在 SQL 2019 世界中,它默认处于启用状态,因为不再支持数据库兼容级别 80 (SQL 2000)。考虑在工具-->选项-->查询执行-->SQL 服务器--高级下取消选中 SSMS 中的 SET ARITHABORT 选项。