สำหรับวันนี้เจอเคสพิเศษนิดนึงครับ มี 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.