1

I have this weird problem that I cannot explain

When I run a certain query from a C# application, it returns a different result then when I run it in SSMS, but not when I use parameters

The command is this

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

It returns -1 when run from the c# application, and returns 1489 when run from SSMS

But if I do this

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

Then it returns the correct value 1489 also from the c# application

So what is the difference ?

Additional code :

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

And the code in 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);
                    }
                }
            }
        }
    }
}

What I have tried

Then I tried this change in the sql function

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

And now it does works correct from the c# application.

So, for some reason when I call this function from a c# application it seems that @@ROWCOUNT is not equal to 1, and when I call this function from SSMS then @@ROWCOUNT is equal to 1.
I have not confirmed this, but that is my conclusion.
The question remains, why ?
And also why does it works different when I use parameters ?

I have fixed my problem by altering the function to

select @Count = count(1) 
from   relation.tblRelation r 
where  r.DealerCode = @DealerCode

and then use the variable @Count in stead of rowcount, but I would still like some explanation of what is happening here.

I also checked what would happen to the first query if more than one row is found.
Because I put the result of the query in a variable, that should fail if more than one row is found (An error in my function, I know, I fixed it by now)

declare @Result int = -1

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

select @@ROWCOUNT

But in stead of throwing an exception what I expected, rowcount just had a value of 4512

EDIT

As suggested by @DanGuzman it works when I put TSQL_SCALAR_UDF_INLINING to OFF

select dbo.fnGetPlaceID('6025', null, null)
OPTION (USE HINT('DISABLE_TSQL_SCALAR_UDF_INLINING'))

EDIT 2

It also works without the hint if I do this ALTER DATABASE [myDataBase] SET COMPATIBILITY_LEVEL = 140; It was on 150

FINAL UPDATE

I have installed the CU15 today, and tested again with compatibility level set to 150 and now it works as expected also from c#. So this solution is now confirmed.

GuidoG
  • 11,359
  • 6
  • 44
  • 79
  • Could you check SQL profiler on sql server? Maybe it's related to null vs dbnull values. – Mate Mar 18 '22 at 09:48
  • What SQL Server 2019 CU do you have installed? A number of bugs with scalar function inlining have been fixed post RTM so try installing the latest CU if you have not already done so. You can also disable inlining with `USE YourDatabase;ALTER DATABASE SCOPED CONFIGURATION SET TSQL_SCALAR_UDF_INLINING = OFF;` – Dan Guzman Mar 18 '22 at 09:51
  • SQL Server is a mullti-threaded database and results do not get returned in same order every time. To get same results you must use an OrderBy to sort results. – jdweng Mar 18 '22 at 10:01
  • @Mate Yes I have checked with the profiler, the command is send correctly – GuidoG Mar 18 '22 at 10:03
  • @jdweng I don't understand what an order by can do in this scenario. Only one row is found using the where clause, no matter what order it is in. Can you explain what you mean with this so I can investigate it – GuidoG Mar 18 '22 at 10:04
  • @DanGuzman I have this `Microsoft SQL Server 2019 (RTM) - 15.0.2000.5 (X64) Sep 24 2019 13:48:23 Copyright (C) 2019 Microsoft Corporation Standard Edition (64-bit) on Windows Server 2019 Standard 10.0 (Build 17763: ) (Hypervisor) ` – GuidoG Mar 18 '22 at 10:18
  • Is r.RelationID a string or an integer in the database? – jdweng Mar 18 '22 at 10:25
  • @jdweng It is an integer, and it is the primary key for that table. But once again I think the order by has no value in this case, the where clause should find just one row with value 6025 in column dealercode, I don't see how an order by can change that – GuidoG Mar 18 '22 at 10:27
  • @GuidoG, try the db-scoped config change. If that fixes it, apply CU15 and turn the inlining option back off to see if it fixes the problem permanently. – Dan Guzman Mar 18 '22 at 10:39
  • @DanGuzman I will try this thank you, but what does CU15 means ? Where can I find this update ? I am not a dba just a simple programmer. And we don't have a dba here... – GuidoG Mar 18 '22 at 10:40
  • @DanGuzman And how can I see if it is OFF or ON right now ? – GuidoG Mar 18 '22 at 10:42
  • @DanGuzman You are spot on. `select dbo.fnGetPlaceID('6025', null, null) OPTION (USE HINT('DISABLE_TSQL_SCALAR_UDF_INLINING'))` works also in c#. So how can I update to CU15 to make this permanent ? – GuidoG Mar 18 '22 at 10:52
  • @DanGuzman And why does it matter if you run this from c# or SSMS ? – GuidoG Mar 18 '22 at 10:54
  • @DanGuzman It also works without the hint if I do this `ALTER DATABASE [GTT_Relation] SET COMPATIBILITY_LEVEL = 140;` It was on 150 – GuidoG Mar 18 '22 at 11:03
  • @GuidoG, the proper fix is CU15 and native SQL 2019 compatibility level. I added an answer to explain more. – Dan Guzman Mar 18 '22 at 12:01

1 Answers1

1

There are a number of issues with scalar function inlining that have been fixed since the initial SQL Server 2019 RTM release. Install the latest cumulative update to get these fixes and others as well.

Without patching, work-arounds include:

  • disable inlining with query hint OPTION (USE HINT('DISABLE_TSQL_SCALAR_UDF_INLINING'))
  • disable inlining at the database level with USE YourDatabase;ALTER DATABASE SCOPED CONFIGURATION SET TSQL_SCALAR_UDF_INLINING = OFF;
  • Change the database compatibility level to 140 or lower

The latest SQL Server patch download links are listed on this page

Regarding different behavior from C# code and SSMS, this is due to different execution plans. SSMS queries specify the ARITHABORT ON session setting by default whereas C# does not set the option. Different session settings may yield different execution plans and make it difficult to troubleshoot. See Erland Sommarskog's article for details. I'll add that SSMS sets ARITHABORT only for backwards compatibility. It's on by default anyway in the SQL 2019 world because database compatibility level 80 (SQL 2000) is no longer supported. Consider unchecking the SET ARITHABORT option in SSMS under Tools-->Options-->Query Execution-->SQL Server--Advanced.

Dan Guzman
  • 43,250
  • 3
  • 46
  • 71
  • Thank you so much for your assistance, I will test the latest update and if that does not help I will set the compatibility level to 140 – GuidoG Mar 18 '22 at 12:09
  • I have installed the CU15 today, and tested again with compatibility level set to 150 and now it works as expected also from c#. So this solution is now confirmed – GuidoG Mar 19 '22 at 10:24