The SqlDbType enumeration value, 0, is invalid. (Parameter ‘SqlDbType’)

สำหรับวันนี้เจอเคสพิเศษนิดนึงครับ มี Code ที่ merge มาล่าสุดเจอ Error

ERROR 2025-08-23 18:35:30.351 (null) nginvs.Service.Helper.ExecutionTaskHelper+<>c__DisplayClass4_1:0 [10] - One or more errors occurred. (The SqlDbType enumeration value, 0, is invalid. (Parameter 'SqlDbType'))
System.AggregateException: One or more errors occurred. (The SqlDbType enumeration value, 0, is invalid. (Parameter 'SqlDbType'))
 ---> nginvs.Common.Exceptional.nginvsException: The SqlDbType enumeration value, 0, is invalid. (Parameter 'SqlDbType')
 ---> System.ArgumentOutOfRangeException: The SqlDbType enumeration value, 0, is invalid. (Parameter 'SqlDbType')
   at Microsoft.Data.SqlClient.MetaType.GetSqlDataType(Int32 tdsType, UInt32 userType, Int32 length)
   at Microsoft.Data.SqlClient.TdsParser.TryProcessTypeInfo(TdsParserStateObject stateObj, SqlMetaDataPriv col, UInt32 userType)
   at Microsoft.Data.SqlClient.TdsParser.TryProcessMetaData(Int32 cColumns, TdsParserStateObject stateObj, _SqlMetaDataSet& metaData, SqlCommandColumnEncryptionSetting columnEncryptionSetting)
   at Microsoft.Data.SqlClient.TdsParser.TryRun(RunBehavior runBehavior, SqlCommand cmdHandler, SqlDataReader dataStream, BulkCopySimpleResultSet bulkCopyHandler, TdsParserStateObject stateObj, Boolean& dataReady)
   at Microsoft.Data.SqlClient.SqlDataReader.TryConsumeMetaData()
   at Microsoft.Data.SqlClient.SqlDataReader.get_MetaData()
   at Microsoft.Data.SqlClient.SqlCommand.FinishExecuteReader(SqlDataReader ds, RunBehavior runBehavior, String resetOptionsString, Boolean isInternal, Boolean forDescribeParameterEncryption, Boolean shouldCacheForAlwaysEncrypted)
   at Microsoft.Data.SqlClient.SqlCommand.RunExecuteReaderTds(CommandBehavior cmdBehavior, RunBehavior runBehavior, Boolean returnStream, Boolean isAsync, Int32 timeout, Task& task, Boolean asyncWrite, Boolean inRetry, SqlDataReader ds, Boolean describeParameterEncryptionRequest)
   at Microsoft.Data.SqlClient.SqlCommand.RunExecuteReader(CommandBehavior cmdBehavior, RunBehavior runBehavior, Boolean returnStream, TaskCompletionSource`1 completion, Int32 timeout, Task& task, Boolean& usedCache, Boolean asyncWrite, Boolean inRetry, String method)
   at Microsoft.Data.SqlClient.SqlCommand.ExecuteReader(CommandBehavior behavior)
   at Dapper.SqlMapper.ExecuteReaderWithFlagsFallback(IDbCommand cmd, Boolean wasClosed, CommandBehavior behavior) in /_/Dapper/SqlMapper.cs:line 1156
   at Dapper.SqlMapper.MultiMapImpl[TFirst,TSecond,TThird,TFourth,TFifth,TSixth,TSeventh,TReturn](IDbConnection cnn, CommandDefinition command, Delegate map, String splitOn, DbDataReader reader, Identity identity, Boolean finalize)+MoveNext() in /_/Dapper/SqlMapper.cs:line 1567
   at System.Collections.Generic.List`1..ctor(IEnumerable`1 collection)
   at System.Linq.Enumerable.ToList[TSource](IEnumerable`1 source)
   at Dapper.SqlMapper.MultiMap[TFirst,TSecond,TThird,TFourth,TFifth,TSixth,TSeventh,TReturn](IDbConnection cnn, String sql, Delegate map, Object param, IDbTransaction transaction, Boolean buffered, String splitOn, Nullable`1 commandTimeout, Nullable`1 commandType) in /_/Dapper/SqlMapper.cs:line 1548
   at Dapper.SqlMapper.Query[TFirst,TSecond,TReturn](IDbConnection cnn, String sql, Func`3 map, Object param, IDbTransaction transaction, Boolean buffered, String splitOn, Nullable`1 commandTimeout, Nullable`1 commandType) in /_/Dapper/SqlMapper.cs:line 1397
   at nginvs.Invest.DAO.ADO.Master.Security.FixedIncomeADO.FetchListBySQL(Char flag) in D:\\DOTNET\invest-service\nginvs.Invest.DAO.ADO\Master\Security\FixedIncomeADO.cs:line 97
   at nginvs.Invest.DAO.ADO.Master.Security.FixedIncomeADO.FetchApprovedList() in D:\\DOTNET\invest-service\nginvs.Invest.DAO.ADO\Master\Security\FixedIncomeADO.cs:line 87
   at nginvs.Service.Common.MasterService`2.FetchApprovedList()

SqlDbType คือ อะไร ?

เป็น Enum ที่เอาไว้ Map DataType ของ dotnet กับ SQL Server โดยมีรายละเอียดเต็มๆ ดังนี้

สำหรับ Code ที่มีปัญหาจะเป็นรูปแบบนี้ มีการ Parallel และต่อ DB โดยใช้ ADO.NET objects ตัวเดียวกัน

var param = new SqlParameter("@myParam", SqlDbType.Int); // BAD: param is shared!
Parallel.ForEach(items, item =>
{
    param.Value = item; // Multiple threads modify the SAME param!
    // Execute command using param (not thread-safe!)
});

ซึ่งพวก ADO.NET objects (พวก SqlParameter, SqlCommand, SqlConnection) ไม่เป็น thread-safe. พอเอาไป Parallel เลย Shared Object มันเลยเกิดปัญหา ทางแก้ง่ายๆ ยอมสร้างทุกรอบ

ตัวอย่าง Code

  • Non-Parallel (Safe)
foreach (var item in items)
{
    var param = new SqlParameter("@myParam", SqlDbType.Int);
    param.Value = item;
    // Execute command using param
}
  • Parallel (Unsafe if sharing objects)
var param = new SqlParameter("@myParam", SqlDbType.Int); // BAD: param is shared!
Parallel.ForEach(items, item =>
{
    param.Value = item; // Multiple threads modify the SAME param!
    // Execute command using param (not thread-safe!)
});
  • Correct Parallel Usage (Create per-thread objects)
Parallel.ForEach(items, item =>
{
    var param = new SqlParameter("@myParam", SqlDbType.Int); // Each thread gets its own param!
    param.Value = item;
    // Execute command using param (thread-safe)
});

สำหรับสาเหตุที่เจอ Recap สั้นๆ

เราคุม เรื่อง Connection DB ไม่ได้ครับ ตัว ADOInterceptor (คนเดิมเค้าเขียนคุม Connection DB เอง) มันจ่าย Connection มาให้ แล้ว Connection นั้นอาจจะ reuse SqlParameter ทำให้เกิดเคสผิดฝาผิดตัว จังหวะมันเสียบ Connection ของ thread c Connection b / d เอา a มาให้ ผิดฝาผิดตัวกัน เช่น

  • thread a อ่าน fixedincome ใช้ conn1
  • thread b อ่าน currency ใช้ conn2
  • thread c อ่าน fixedincome ใช้ conn2 >> reuse มันจำพวก DB Connection + SqlParameter ของ Currency ไปใช้งานเลยผิดฝาผิดตัว
  • thread d อ่าน currency ใช้ conn1 และระเบิดตู้มแบบ thread c

Discover more from naiwaen@DebuggingSoft

Subscribe to get the latest posts sent to your email.