|
12 | 12 | using System.Linq;
|
13 | 13 | using System.Reflection;
|
14 | 14 | using System.Reflection.Emit;
|
| 15 | +using System.Runtime.CompilerServices; |
15 | 16 | using System.Text;
|
16 | 17 | using System.Text.RegularExpressions;
|
17 | 18 | using System.Threading;
|
@@ -1096,14 +1097,7 @@ private static IEnumerable<T> QueryImpl<T>(this IDbConnection cnn, CommandDefini
|
1096 | 1097 | while (reader.Read())
|
1097 | 1098 | {
|
1098 | 1099 | object val = func(reader);
|
1099 |
| - if (val == null || val is T) |
1100 |
| - { |
1101 |
| - yield return (T)val; |
1102 |
| - } |
1103 |
| - else |
1104 |
| - { |
1105 |
| - yield return (T)Convert.ChangeType(val, convertToType, CultureInfo.InvariantCulture); |
1106 |
| - } |
| 1100 | + yield return GetValue<T>(reader, effectiveType, val); |
1107 | 1101 | }
|
1108 | 1102 | while (reader.NextResult()) { /* ignore subsequent result sets */ }
|
1109 | 1103 | // happy path; close the reader cleanly - no
|
@@ -1179,31 +1173,14 @@ private static T QueryRowImpl<T>(IDbConnection cnn, Row row, ref CommandDefiniti
|
1179 | 1173 | : CommandBehavior.SequentialAccess | CommandBehavior.SingleResult | CommandBehavior.SingleRow);
|
1180 | 1174 | wasClosed = false; // *if* the connection was closed and we got this far, then we now have a reader
|
1181 | 1175 |
|
1182 |
| - T result = default(T); |
| 1176 | + T result = default; |
1183 | 1177 | if (reader.Read() && reader.FieldCount != 0)
|
1184 | 1178 | {
|
1185 | 1179 | // with the CloseConnection flag, so the reader will deal with the connection; we
|
1186 | 1180 | // still need something in the "finally" to ensure that broken SQL still results
|
1187 | 1181 | // in the connection closing itself
|
1188 |
| - var tuple = info.Deserializer; |
1189 |
| - int hash = GetColumnHash(reader); |
1190 |
| - if (tuple.Func == null || tuple.Hash != hash) |
1191 |
| - { |
1192 |
| - tuple = info.Deserializer = new DeserializerState(hash, GetDeserializer(effectiveType, reader, 0, -1, false)); |
1193 |
| - if (command.AddToCache) SetQueryCache(identity, info); |
1194 |
| - } |
| 1182 | + result = ReadRow<T>(info, identity, ref command, effectiveType, reader); |
1195 | 1183 |
|
1196 |
| - var func = tuple.Func; |
1197 |
| - object val = func(reader); |
1198 |
| - if (val == null || val is T) |
1199 |
| - { |
1200 |
| - result = (T)val; |
1201 |
| - } |
1202 |
| - else |
1203 |
| - { |
1204 |
| - var convertToType = Nullable.GetUnderlyingType(effectiveType) ?? effectiveType; |
1205 |
| - result = (T)Convert.ChangeType(val, convertToType, CultureInfo.InvariantCulture); |
1206 |
| - } |
1207 | 1184 | if ((row & Row.Single) != 0 && reader.Read()) ThrowMultipleRows(row);
|
1208 | 1185 | while (reader.Read()) { /* ignore subsequent rows */ }
|
1209 | 1186 | }
|
@@ -1236,6 +1213,53 @@ private static T QueryRowImpl<T>(IDbConnection cnn, Row row, ref CommandDefiniti
|
1236 | 1213 | }
|
1237 | 1214 | }
|
1238 | 1215 |
|
| 1216 | + /// <summary> |
| 1217 | + /// Shared value deserilization path for QueryRowImpl and QueryRowAsync |
| 1218 | + /// </summary> |
| 1219 | + [MethodImpl(MethodImplOptions.AggressiveInlining)] |
| 1220 | + private static T ReadRow<T>(CacheInfo info, Identity identity, ref CommandDefinition command, Type effectiveType, IDataReader reader) |
| 1221 | + { |
| 1222 | + var tuple = info.Deserializer; |
| 1223 | + int hash = GetColumnHash(reader); |
| 1224 | + if (tuple.Func == null || tuple.Hash != hash) |
| 1225 | + { |
| 1226 | + tuple = info.Deserializer = new DeserializerState(hash, GetDeserializer(effectiveType, reader, 0, -1, false)); |
| 1227 | + if (command.AddToCache) SetQueryCache(identity, info); |
| 1228 | + } |
| 1229 | + |
| 1230 | + var func = tuple.Func; |
| 1231 | + object val = func(reader); |
| 1232 | + return GetValue<T>(reader, effectiveType, val); |
| 1233 | + } |
| 1234 | + |
| 1235 | + [MethodImpl(MethodImplOptions.AggressiveInlining)] |
| 1236 | + private static T GetValue<T>(IDataReader reader, Type effectiveType, object val) |
| 1237 | + { |
| 1238 | + if (val is T tVal) |
| 1239 | + { |
| 1240 | + return tVal; |
| 1241 | + } |
| 1242 | + else if (val == null && (!effectiveType.IsValueType || Nullable.GetUnderlyingType(effectiveType) != null)) |
| 1243 | + { |
| 1244 | + return default; |
| 1245 | + } |
| 1246 | + else |
| 1247 | + { |
| 1248 | + try |
| 1249 | + { |
| 1250 | + var convertToType = Nullable.GetUnderlyingType(effectiveType) ?? effectiveType; |
| 1251 | + return (T)Convert.ChangeType(val, convertToType, CultureInfo.InvariantCulture); |
| 1252 | + } |
| 1253 | + catch (Exception ex) |
| 1254 | + { |
| 1255 | +#pragma warning disable CS0618 // Type or member is obsolete |
| 1256 | + ThrowDataException(ex, 0, reader, val); |
| 1257 | +#pragma warning restore CS0618 // Type or member is obsolete |
| 1258 | + return default; // For the compiler - we've already thrown |
| 1259 | + } |
| 1260 | + } |
| 1261 | + } |
| 1262 | + |
1239 | 1263 | /// <summary>
|
1240 | 1264 | /// Perform a multi-mapping query with 2 input types.
|
1241 | 1265 | /// This returns a single type, combined from the raw types via <paramref name="map"/>.
|
@@ -3619,6 +3643,11 @@ public static void ThrowDataException(Exception ex, int index, IDataReader reade
|
3619 | 3643 | if (reader != null && index >= 0 && index < reader.FieldCount)
|
3620 | 3644 | {
|
3621 | 3645 | name = reader.GetName(index);
|
| 3646 | + if (name == string.Empty) |
| 3647 | + { |
| 3648 | + // Otherwise we throw (=value) below, which isn't intuitive |
| 3649 | + name = "(Unnamed Column)"; |
| 3650 | + } |
3622 | 3651 | try
|
3623 | 3652 | {
|
3624 | 3653 | if (value == null || value is DBNull)
|
|
0 commit comments