diff --git a/src/Dapper.AOT.Analyzers/CodeAnalysis/DapperAnalyzer.cs b/src/Dapper.AOT.Analyzers/CodeAnalysis/DapperAnalyzer.cs index d798745..a01dc29 100644 --- a/src/Dapper.AOT.Analyzers/CodeAnalysis/DapperAnalyzer.cs +++ b/src/Dapper.AOT.Analyzers/CodeAnalysis/DapperAnalyzer.cs @@ -554,6 +554,8 @@ internal static Location SharedParseArgsAndFlags(in ParseState ctx, IInvocationO case "startIndex": case "length": case "returnNullIfFirstMissing": + case "map": // multi-map function parameter + case "splitOn": // multi-map split column parameter case "concreteType" when arg.Value is IDefaultValueOperation || (arg.ConstantValue.HasValue && arg.ConstantValue.Value is null): // nothing to do break; diff --git a/src/Dapper.AOT.Analyzers/CodeAnalysis/DapperInterceptorGenerator.MultiMap.cs b/src/Dapper.AOT.Analyzers/CodeAnalysis/DapperInterceptorGenerator.MultiMap.cs new file mode 100644 index 0000000..53b5d56 --- /dev/null +++ b/src/Dapper.AOT.Analyzers/CodeAnalysis/DapperInterceptorGenerator.MultiMap.cs @@ -0,0 +1,145 @@ +using Dapper.Internal; +using Dapper.Internal.Roslyn; +using Microsoft.CodeAnalysis; +using System.Collections.Immutable; + +namespace Dapper.CodeAnalysis; + +public sealed partial class DapperInterceptorGenerator +{ + static void WriteMultiMapImplementation( + CodeWriter sb, + IMethodSymbol method, + OperationFlags flags, + OperationFlags commandTypeMode, + ITypeSymbol? parameterType, + string map, bool cache, + in ImmutableArray methodParameters, + in CommandFactoryState factories, + in RowReaderState readers, + string? fixedSql, + AdditionalCommandState? additionalCommandState) + { + var typeArgs = method.TypeArguments; + var arity = typeArgs.Length; + + sb.Append("return "); + if (flags.HasAll(OperationFlags.Async | OperationFlags.Query | OperationFlags.Buffered)) + { + sb.Append("global::Dapper.DapperAotExtensions.AsEnumerableAsync(").Indent(false).NewLine(); + } + + // Create the Command + sb.Append("global::Dapper.DapperAotExtensions.Command(cnn, ").Append(Forward(methodParameters, "transaction")).Append(", "); + if (fixedSql is not null) + { + sb.AppendVerbatimLiteral(fixedSql).Append(", "); + } + else + { + sb.Append("sql, "); + } + if (commandTypeMode == 0) + { + if (HasParam(methodParameters, "command")) + { + sb.Append("command.GetValueOrDefault()"); + } + else + { + sb.Append("default"); + } + } + else + { + sb.Append("global::System.Data.CommandType.").Append(commandTypeMode.ToString()); + } + sb.Append(", ").Append(Forward(methodParameters, "commandTimeout")).Append(HasParam(methodParameters, "commandTimeout") ? ".GetValueOrDefault()" : "").Append(", "); + if (flags.HasAny(OperationFlags.HasParameters)) + { + var index = factories.GetIndex(parameterType!, map, cache, additionalCommandState, out var subIndex); + sb.Append("CommandFactory").Append(index).Append(".Instance").Append(subIndex); + } + else + { + sb.Append("DefaultCommandFactory"); + } + sb.Append(")."); + + // Call the appropriate QueryBuffered/QueryBufferedAsync/QueryUnbuffered/QueryUnbufferedAsync method + bool isAsync = flags.HasAny(OperationFlags.Async); + bool isUnbuffered = flags.HasAny(OperationFlags.Unbuffered); + + if (isUnbuffered) + { + sb.Append("QueryUnbuffered"); + } + else + { + sb.Append("QueryBuffered"); + } + + if (isAsync) + { + sb.Append("Async"); + } + + // Add type parameters + sb.Append("<"); + for (int i = 0; i < arity; i++) + { + if (i > 0) sb.Append(", "); + sb.Append(typeArgs[i]); + } + sb.Append(">("); + + // Add arguments: args, map, factory1, factory2, ..., splitOn, rowCountHint + WriteTypedArg(sb, parameterType).Append(", "); + sb.Append(Forward(methodParameters, "map")).Append(", "); + + // Add row factories for each type (except the return type) + for (int i = 0; i < arity - 1; i++) + { + var typeArg = typeArgs[i]; + sb.AppendReader(typeArg, readers, flags, additionalCommandState?.QueryColumns ?? default); + sb.Append(", "); + } + + // Add splitOn parameter + if (HasParam(methodParameters, "splitOn")) + { + sb.Append(Forward(methodParameters, "splitOn")); + } + else + { + sb.Append("\"Id\""); + } + + // Add rowCountHint if needed (only for buffered queries) + if (!isUnbuffered && flags.HasAny(OperationFlags.Query) && additionalCommandState is { HasRowCountHint: true }) + { + if (additionalCommandState.RowCountHintMemberName is null) + { + sb.Append(", rowCountHint: ").Append(additionalCommandState.RowCountHint); + } + else + { + // member-based hint; add after args + sb.Append(", rowCountHint: ").Append("args.").Append(additionalCommandState.RowCountHintMemberName); + } + } + + // Add cancellationToken for async + if (isAsync && HasParam(methodParameters, "cancellationToken")) + { + sb.Append(", cancellationToken: ").Append(Forward(methodParameters, "cancellationToken")); + } + + if (flags.HasAll(OperationFlags.Async | OperationFlags.Query | OperationFlags.Buffered)) + { + sb.Append(")").Outdent(false); + } + sb.Append(")"); + sb.Append(";").NewLine(); + } +} diff --git a/src/Dapper.AOT.Analyzers/CodeAnalysis/DapperInterceptorGenerator.Single.cs b/src/Dapper.AOT.Analyzers/CodeAnalysis/DapperInterceptorGenerator.Single.cs index 58c3f80..c0f2269 100644 --- a/src/Dapper.AOT.Analyzers/CodeAnalysis/DapperInterceptorGenerator.Single.cs +++ b/src/Dapper.AOT.Analyzers/CodeAnalysis/DapperInterceptorGenerator.Single.cs @@ -152,19 +152,6 @@ static void WriteSingleImplementation( } } sb.Append(";").NewLine(); - - static CodeWriter WriteTypedArg(CodeWriter sb, ITypeSymbol? parameterType) - { - if (parameterType is null || parameterType.IsAnonymousType) - { - sb.Append("param"); - } - else - { - sb.Append("(").Append(parameterType).Append(")param!"); - } - return sb; - } } private static bool HasParam(ImmutableArray methodParameters, string name) @@ -181,4 +168,17 @@ private static bool HasParam(ImmutableArray methodParameters, private static string Forward(ImmutableArray methodParameters, string name) => HasParam(methodParameters, name) ? name : "default"; + + private static CodeWriter WriteTypedArg(CodeWriter sb, ITypeSymbol? parameterType) + { + if (parameterType is null || parameterType.IsAnonymousType) + { + sb.Append("param"); + } + else + { + sb.Append("(").Append(parameterType).Append(")param!"); + } + return sb; + } } \ No newline at end of file diff --git a/src/Dapper.AOT.Analyzers/CodeAnalysis/DapperInterceptorGenerator.cs b/src/Dapper.AOT.Analyzers/CodeAnalysis/DapperInterceptorGenerator.cs index 595282a..a3ae315 100644 --- a/src/Dapper.AOT.Analyzers/CodeAnalysis/DapperInterceptorGenerator.cs +++ b/src/Dapper.AOT.Analyzers/CodeAnalysis/DapperInterceptorGenerator.cs @@ -396,6 +396,10 @@ internal void Generate(in GenerateState ctx) { WriteGetRowParser(sb, resultType, readers, grp.Key.Flags, grp.Key.AdditionalCommandState?.QueryColumns ?? default); } + else if (flags.HasAny(OperationFlags.MultiMap)) + { + WriteMultiMapImplementation(sb, method, flags, commandTypeMode, parameterType, grp.Key.ParameterMap, grp.Key.UniqueLocation is not null, methodParameters, factories, readers, fixedSql, additionalCommandState); + } else if (!TryWriteMultiExecImplementation(sb, flags, commandTypeMode, parameterType, grp.Key.ParameterMap, grp.Key.UniqueLocation is not null, methodParameters, factories, fixedSql, additionalCommandState)) { WriteSingleImplementation(sb, method, resultType, flags, commandTypeMode, parameterType, grp.Key.ParameterMap, grp.Key.UniqueLocation is not null, methodParameters, factories, readers, fixedSql, additionalCommandState); @@ -818,7 +822,10 @@ void WriteTokenizeMethod() sb.Append("public override object? Tokenize(global::System.Data.Common.DbDataReader reader, global::System.Span tokens, int columnOffset)").Indent().NewLine(); if (queryColumns.IsDefault) // need to apply full map { - sb.Append("for (int i = 0; i < tokens.Length; i++)").Indent().NewLine() + // Limit tokens to available columns starting from columnOffset + sb.Append("int availableColumns = reader.FieldCount - columnOffset;").NewLine() + .Append("int tokenCount = global::System.Math.Min(tokens.Length, availableColumns);").NewLine() + .Append("for (int i = 0; i < tokenCount; i++)").Indent().NewLine() .Append("int token = -1;").NewLine() .Append("var name = reader.GetName(columnOffset);").NewLine() .Append("var type = reader.GetFieldType(columnOffset);").NewLine() @@ -850,7 +857,11 @@ void WriteTokenizeMethod() sb.Outdent().NewLine() .Append("tokens[i] = token;").NewLine() .Append("columnOffset++;").NewLine() - .Outdent().NewLine(); + .Outdent().NewLine() + .Append("// Initialize remaining tokens to -1 (unmapped)").NewLine() + .Append("for (int i = tokenCount; i < tokens.Length; i++)").Indent().NewLine() + .Append("tokens[i] = -1;").Outdent().NewLine() + .Append("return tokenCount;").Outdent().NewLine(); } else { @@ -858,11 +869,14 @@ void WriteTokenizeMethod() if (flags.HasAny(OperationFlags.StrictTypes)) { sb.Append("// (no mapping applied for strict types and pre-defined columns)").NewLine(); + sb.Append("return null;").Outdent().NewLine(); } else { sb.Append("// pre-defined columns, but still needs type map").NewLine(); - sb.Append("for (int i = 0; i < tokens.Length; i++)").Indent().NewLine() + sb.Append("int availableColumns = reader.FieldCount - columnOffset;").NewLine() + .Append("int tokenCount = global::System.Math.Min(tokens.Length, availableColumns);").NewLine() + .Append("for (int i = 0; i < tokenCount; i++)").Indent().NewLine() .Append("var type = reader.GetFieldType(columnOffset);").NewLine() .Append("tokens[i] = i switch").Indent().NewLine(); for (int i = 0; i < members.Length;i++) @@ -874,11 +888,14 @@ void WriteTokenizeMethod() .Append(" : ").Append(i + map.Members.Length).Append(",").NewLine(); } } - sb.Append("_ => -1,").Outdent().Append(";").Outdent().NewLine(); + sb.Append("_ => -1,").Outdent().Append(";").NewLine() + .Append("columnOffset++;").Outdent().NewLine() + .Append("// Initialize remaining tokens to -1 (unmapped)").NewLine() + .Append("for (int i = tokenCount; i < tokens.Length; i++)").Indent().NewLine() + .Append("tokens[i] = -1;").Outdent().NewLine() + .Append("return tokenCount;").Outdent().NewLine(); } } - - sb.Append("return null;").Outdent().NewLine(); } void WriteReadMethod(in GenerateState context) { @@ -886,6 +903,10 @@ void WriteReadMethod(in GenerateState context) sb.Append("public override ").Append(type).Append(" Read(global::System.Data.Common.DbDataReader reader, global::System.ReadOnlySpan tokens, int columnOffset, object? state)").Indent().NewLine(); + // For multi-map queries: if this is not the first entity (columnOffset > 0) and the first column is NULL, + // return default to handle LEFT JOINs where the related entity doesn't exist + sb.Append("if (columnOffset > 0 && reader.IsDBNull(columnOffset)) return default!;").NewLine(); + int token = 0; var deferredMethodArgumentsOrdered = new SortedList(); @@ -934,13 +955,15 @@ void WriteReadMethod(in GenerateState context) { // no mapping involved - simple ordinal iteration sb.Append("int lim = global::System.Math.Min(tokens.Length, ").Append(queryColumns.Length).Append(");").NewLine() - .Append("for (int token = 0; token < lim; token++) // query-columns predefined"); + .Append("for (int token = 0; token < lim; token++) // query-columns predefined").Indent().NewLine(); } else { - sb.Append("foreach (var token in tokens)"); + sb.Append("int tokenCount = state is int count ? count : tokens.Length;").NewLine() + .Append("for (int i = 0; i < tokenCount; i++)").Indent().NewLine() + .Append("var token = tokens[i];").NewLine(); } - sb.Indent().NewLine().Append("switch (token)").Indent().NewLine(); + sb.Append("switch (token)").Indent().NewLine(); token = 0; foreach (var member in members) @@ -1009,7 +1032,7 @@ void WriteReadMethod(in GenerateState context) if (useConstructorDeferred) { // `return new Type(member0, member1, member2, ...);` - sb.Append("return new ").Append(type).Append('('); + sb.Append("return new ").AppendTypeForNew(type).Append('('); WriteDeferredMethodArgs(); sb.Append(')'); WriteDeferredInitialization(); @@ -1031,7 +1054,7 @@ void WriteReadMethod(in GenerateState context) // Member1 = value1, // Member2 = value2 // } - sb.Append("return new ").Append(type); + sb.Append("return new ").AppendTypeForNew(type); WriteDeferredInitialization(); sb.Append(";").Outdent(); } diff --git a/src/Dapper.AOT.Analyzers/Internal/CodeWriter.cs b/src/Dapper.AOT.Analyzers/Internal/CodeWriter.cs index 7f3b03e..a67b26f 100644 --- a/src/Dapper.AOT.Analyzers/Internal/CodeWriter.cs +++ b/src/Dapper.AOT.Analyzers/Internal/CodeWriter.cs @@ -122,6 +122,24 @@ public CodeWriter Append(ITypeSymbol? value, bool anonToTuple = false) return this; } + // Append type for use in 'new Type()' expressions, stripping nullable annotation + public CodeWriter AppendTypeForNew(ITypeSymbol? value) + { + if (value is null) + { } + else if (value.IsAnonymousType) + { + Append(value.ToDisplayString(SymbolDisplayFormat.MinimallyQualifiedFormat)); + } + else + { + // Strip nullable annotation for object creation (can't do 'new Type?()') + var nonNullable = value.WithNullableAnnotation(NullableAnnotation.NotAnnotated); + Append(GetTypeName(nonNullable)); + } + return this; + } + private void AppendAsValueTuple(ITypeSymbol value) { var members = value.GetMembers(); diff --git a/src/Dapper.AOT.Analyzers/Internal/Inspection.cs b/src/Dapper.AOT.Analyzers/Internal/Inspection.cs index ed11497..6064520 100644 --- a/src/Dapper.AOT.Analyzers/Internal/Inspection.cs +++ b/src/Dapper.AOT.Analyzers/Internal/Inspection.cs @@ -1244,7 +1244,12 @@ public static bool IsDapperMethod(this IInvocationOperation operation, out Opera { case "Query": flags |= OperationFlags.Query; - if (method.Arity > 1) flags |= OperationFlags.NotAotSupported; + if (method.Arity > 1) + { + flags |= OperationFlags.MultiMap; + // Multi-map is supported for 2-7 types (arity 3-8: T1, T2, TReturn through T1, T2, T3, T4, T5, T6, T7, TReturn) + if (method.Arity > 8) flags |= OperationFlags.NotAotSupported; + } break; case "QueryAsync": case "QueryUnbufferedAsync": @@ -1305,6 +1310,11 @@ public static bool TryGetConstantValue(IOperation op, out T? value) { return typeArgs[0]; } + // For multi-map queries, the last type argument is the return type + if (typeArgs.Length > 1 && flags.HasAny(OperationFlags.MultiMap)) + { + return typeArgs[typeArgs.Length - 1]; + } } return null; } @@ -1597,5 +1607,6 @@ enum OperationFlags QueryMultiple = 1 << 22, GetRowParser = 1 << 23, StrictTypes = 1 << 24, + MultiMap = 1 << 25, // multi-map queries (Query) NotAotSupported = 1 << 31, } diff --git a/src/Dapper.AOT/CommandT.QueryMultiMap.cs b/src/Dapper.AOT/CommandT.QueryMultiMap.cs new file mode 100644 index 0000000..3ad105b --- /dev/null +++ b/src/Dapper.AOT/CommandT.QueryMultiMap.cs @@ -0,0 +1,1412 @@ +using Dapper.Internal; +using System; +using System.Collections.Generic; +using System.Data; +using System.Runtime.CompilerServices; +using System.Threading; +using System.Threading.Tasks; + +namespace Dapper; + +partial struct Command +{ + /// + /// Finds multiple split points based on splitOn column names + /// + private static int[] FindSplits(System.Data.Common.DbDataReader reader, string splitOn, int count) + { + var splits = new int[count]; + var fieldCount = reader.FieldCount; + + // Handle wildcard splitOn - split after every column + if (splitOn == "*") + { + for (int i = 0; i < count; i++) + { + splits[i] = i + 1; + } + return splits; + } + +#if NET5_0_OR_GREATER + var splitColumns = splitOn.Split(',', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries); +#else + var splitColumns = splitOn.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries); + for (int idx = 0; idx < splitColumns.Length; idx++) + { + splitColumns[idx] = splitColumns[idx].Trim(); + } +#endif + + // Search RIGHT-TO-LEFT like original Dapper to handle duplicate column names correctly + // "in this we go right to left through the data reader in order to cope with properties that are + // named the same as a subsequent primary key that we split on" + int splitIndex = count - 1; + int currentPos = fieldCount; + + // Process splits from last to first + for (int splitIdx = splitColumns.Length - 1; splitIdx >= 0; splitIdx--) + { + if (splitIndex < 0) break; + + var split = splitColumns[splitIdx]; + bool found = false; + + // Search backwards from currentPos + for (int i = currentPos - 1; i > 0; i--) + { + var name = reader.GetName(i); + if (string.Equals(name, split, StringComparison.OrdinalIgnoreCase)) + { + splits[splitIndex--] = i; + currentPos = i; + found = true; + break; + } + } + + if (!found) + { + // Build helpful error message like original Dapper + var availableColumns = new System.Text.StringBuilder(); + for (int i = 0; i < fieldCount; i++) + { + if (i > 0) availableColumns.Append(", "); + availableColumns.Append(reader.GetName(i)); + } + throw new System.ArgumentException( + $"Multi-map error: splitOn column '{split}' was not found - please ensure your splitOn parameter is set and in the correct order (available columns: {availableColumns})", + nameof(splitOn)); + } + } + + // Fill remaining splits evenly if not all found + // Note: splitIndex will be >= 0 if we didn't find all splits (since we decrement on each find) + if (splitIndex >= 0) + { + var remaining = splitIndex + 1; + var step = currentPos / (remaining + 1); + for (int i = 0; i <= splitIndex; i++) + { + currentPos -= step; + if (currentPos <= 0) currentPos = 1; // Ensure we don't go below column 1 + splits[i] = currentPos; + } + } + + return splits; + } + + + /// + /// Reads buffered rows from a multi-map query with 2 types + /// + public List QueryBuffered( + TArgs args, + Func map, + [DapperAot] RowFactory? factory1 = null, + [DapperAot] RowFactory? factory2 = null, + string splitOn = "Id", + int rowCountHint = 0) + { + SyncQueryState state = default; + try + { + state.ExecuteReader(GetCommand(args), CommandBehavior.SingleResult | CommandBehavior.SequentialAccess); + + List results; + if (state.Reader.Read()) + { + var split = FindSplits(state.Reader, splitOn, 1)[0]; + var allTokens = state.Lease(); + var tokenState1 = (factory1 ??= RowFactory.Default).Tokenize(state.Reader, allTokens.Slice(0, split), 0); + var tokenState2 = (factory2 ??= RowFactory.Default).Tokenize(state.Reader, allTokens.Slice(split), split); + + results = RowFactory.GetRowBuffer(rowCountHint); + do + { + var allTokensReadOnly = state.Tokens; + var obj1 = factory1.Read(state.Reader, allTokensReadOnly.Slice(0, split), 0, tokenState1); + var obj2 = factory2.Read(state.Reader, allTokensReadOnly.Slice(split), split, tokenState2); + results.Add(map(obj1, obj2)); + } + while (state.Reader.Read()); + state.Return(); + } + else + { + results = []; + } + + // consume entire results (avoid unobserved TDS error messages) + while (state.Reader.NextResult()) { } + PostProcessAndRecycle(ref state, args, state.Reader.CloseAndCapture()); + return results; + } + finally + { + state.Dispose(); + } + } + + /// + /// Reads buffered rows from a multi-map query with 2 types (async) + /// + public async Task> QueryBufferedAsync( + TArgs args, + Func map, + [DapperAot] RowFactory? factory1 = null, + [DapperAot] RowFactory? factory2 = null, + string splitOn = "Id", + int rowCountHint = 0, + CancellationToken cancellationToken = default) + { + AsyncQueryState state = new(); + try + { + cancellationToken = GetCancellationToken(args, cancellationToken); + await state.ExecuteReaderAsync(GetCommand(args), CommandBehavior.SingleResult | CommandBehavior.SequentialAccess, cancellationToken); + + List results; + if (await state.Reader.ReadAsync(cancellationToken)) + { + var splits = FindSplits(state.Reader, splitOn, 1); + var allTokens = state.Lease(); + var tokenState1 = (factory1 ??= RowFactory.Default).Tokenize(state.Reader, allTokens.Slice(0, splits[0]), 0); + var tokenState2 = (factory2 ??= RowFactory.Default).Tokenize(state.Reader, allTokens.Slice(splits[0]), splits[0]); + + results = RowFactory.GetRowBuffer(rowCountHint); + do + { + var allTokensReadOnly = state.Tokens; + var obj1 = factory1.Read(state.Reader, allTokensReadOnly.Slice(0, splits[0]), 0, tokenState1); + var obj2 = factory2.Read(state.Reader, allTokensReadOnly.Slice(splits[0]), splits[0], tokenState2); + results.Add(map(obj1, obj2)); + } + while (await state.Reader.ReadAsync(cancellationToken)); + state.Return(); + } + else + { + results = []; + } + + // consume entire results (avoid unobserved TDS error messages) + while (await state.Reader.NextResultAsync(cancellationToken)) { } + PostProcessAndRecycle(state, args, await state.Reader.CloseAndCaptureAsync()); + return results; + } + finally + { + await state.DisposeAsync(); + } + } + + /// + /// Reads buffered rows from a multi-map query with 3 types + /// + public List QueryBuffered( + TArgs args, + Func map, + [DapperAot] RowFactory? factory1 = null, + [DapperAot] RowFactory? factory2 = null, + [DapperAot] RowFactory? factory3 = null, + string splitOn = "Id", + int rowCountHint = 0) + { + SyncQueryState state = default; + try + { + state.ExecuteReader(GetCommand(args), CommandBehavior.SingleResult | CommandBehavior.SequentialAccess); + + List results; + if (state.Reader.Read()) + { + var splits = FindSplits(state.Reader, splitOn, 2); + var allTokens = state.Lease(); + var tokenState1 = (factory1 ??= RowFactory.Default).Tokenize(state.Reader, allTokens.Slice(0, splits[0]), 0); + var tokenState2 = (factory2 ??= RowFactory.Default).Tokenize(state.Reader, allTokens.Slice(splits[0], splits[1] - splits[0]), splits[0]); + var tokenState3 = (factory3 ??= RowFactory.Default).Tokenize(state.Reader, allTokens.Slice(splits[1]), splits[1]); + + results = RowFactory.GetRowBuffer(rowCountHint); + do + { + var allTokensReadOnly = state.Tokens; + var obj1 = factory1.Read(state.Reader, allTokensReadOnly.Slice(0, splits[0]), 0, tokenState1); + var obj2 = factory2.Read(state.Reader, allTokensReadOnly.Slice(splits[0], splits[1] - splits[0]), splits[0], tokenState2); + var obj3 = factory3.Read(state.Reader, allTokensReadOnly.Slice(splits[1]), splits[1], tokenState3); + results.Add(map(obj1, obj2, obj3)); + } + while (state.Reader.Read()); + state.Return(); + } + else + { + results = []; + } + + while (state.Reader.NextResult()) { } + PostProcessAndRecycle(ref state, args, state.Reader.CloseAndCapture()); + return results; + } + finally + { + state.Dispose(); + } + } + + /// + /// Reads buffered rows from a multi-map query with 3 types (async) + /// + public async Task> QueryBufferedAsync( + TArgs args, + Func map, + [DapperAot] RowFactory? factory1 = null, + [DapperAot] RowFactory? factory2 = null, + [DapperAot] RowFactory? factory3 = null, + string splitOn = "Id", + int rowCountHint = 0, + CancellationToken cancellationToken = default) + { + AsyncQueryState state = new(); + try + { + cancellationToken = GetCancellationToken(args, cancellationToken); + await state.ExecuteReaderAsync(GetCommand(args), CommandBehavior.SingleResult | CommandBehavior.SequentialAccess, cancellationToken); + + List results; + if (await state.Reader.ReadAsync(cancellationToken)) + { + var splits = FindSplits(state.Reader, splitOn, 2); + var allTokens = state.Lease(); + + var tokenState1 = (factory1 ??= RowFactory.Default).Tokenize(state.Reader, allTokens.Slice(0, splits[0]), 0); + var tokenState2 = (factory2 ??= RowFactory.Default).Tokenize(state.Reader, allTokens.Slice(splits[0], splits[1] - splits[0]), splits[0]); + var tokenState3 = (factory3 ??= RowFactory.Default).Tokenize(state.Reader, allTokens.Slice(splits[1]), splits[1]); + + results = RowFactory.GetRowBuffer(rowCountHint); + do + { + var allTokensReadOnly = state.Tokens; + var obj1 = factory1.Read(state.Reader, allTokensReadOnly.Slice(0, splits[0]), 0, tokenState1); + var obj2 = factory2.Read(state.Reader, allTokensReadOnly.Slice(splits[0], splits[1] - splits[0]), splits[0], tokenState2); + var obj3 = factory3.Read(state.Reader, allTokensReadOnly.Slice(splits[1]), splits[1], tokenState3); + results.Add(map(obj1, obj2, obj3)); + } + while (await state.Reader.ReadAsync(cancellationToken)); + state.Return(); + } + else + { + results = []; + } + + while (await state.Reader.NextResultAsync(cancellationToken)) { } + PostProcessAndRecycle(state, args, await state.Reader.CloseAndCaptureAsync()); + return results; + } + finally + { + await state.DisposeAsync(); + } + } + + /// + /// Reads buffered rows from a multi-map query with 4 types + /// + public List QueryBuffered( + TArgs args, + Func map, + [DapperAot] RowFactory? factory1 = null, + [DapperAot] RowFactory? factory2 = null, + [DapperAot] RowFactory? factory3 = null, + [DapperAot] RowFactory? factory4 = null, + string splitOn = "Id", + int rowCountHint = 0) + { + SyncQueryState state = default; + try + { + state.ExecuteReader(GetCommand(args), CommandBehavior.SingleResult | CommandBehavior.SequentialAccess); + + List results; + if (state.Reader.Read()) + { + var splits = FindSplits(state.Reader, splitOn, 3); + var allTokens = state.Lease(); + var tokenState1 = (factory1 ??= RowFactory.Default).Tokenize(state.Reader, allTokens.Slice(0, splits[0]), 0); + var tokenState2 = (factory2 ??= RowFactory.Default).Tokenize(state.Reader, allTokens.Slice(splits[0], splits[1] - splits[0]), splits[0]); + var tokenState3 = (factory3 ??= RowFactory.Default).Tokenize(state.Reader, allTokens.Slice(splits[1], splits[2] - splits[1]), splits[1]); + var tokenState4 = (factory4 ??= RowFactory.Default).Tokenize(state.Reader, allTokens.Slice(splits[2]), splits[2]); + + results = RowFactory.GetRowBuffer(rowCountHint); + do + { + var allTokensReadOnly = state.Tokens; + var obj1 = factory1.Read(state.Reader, allTokensReadOnly.Slice(0, splits[0]), 0, tokenState1); + var obj2 = factory2.Read(state.Reader, allTokensReadOnly.Slice(splits[0], splits[1] - splits[0]), splits[0], tokenState2); + var obj3 = factory3.Read(state.Reader, allTokensReadOnly.Slice(splits[1], splits[2] - splits[1]), splits[1], tokenState3); + var obj4 = factory4.Read(state.Reader, allTokensReadOnly.Slice(splits[2]), splits[2], tokenState4); + results.Add(map(obj1, obj2, obj3, obj4)); + } + while (state.Reader.Read()); + state.Return(); + } + else + { + results = []; + } + + while (state.Reader.NextResult()) { } + PostProcessAndRecycle(ref state, args, state.Reader.CloseAndCapture()); + return results; + } + finally + { + state.Dispose(); + } + } + + /// + /// Reads buffered rows from a multi-map query with 4 types (async) + /// + public async Task> QueryBufferedAsync( + TArgs args, + Func map, + [DapperAot] RowFactory? factory1 = null, + [DapperAot] RowFactory? factory2 = null, + [DapperAot] RowFactory? factory3 = null, + [DapperAot] RowFactory? factory4 = null, + string splitOn = "Id", + int rowCountHint = 0, + CancellationToken cancellationToken = default) + { + AsyncQueryState state = new(); + try + { + cancellationToken = GetCancellationToken(args, cancellationToken); + await state.ExecuteReaderAsync(GetCommand(args), CommandBehavior.SingleResult | CommandBehavior.SequentialAccess, cancellationToken); + + List results; + if (await state.Reader.ReadAsync(cancellationToken)) + { + var splits = FindSplits(state.Reader, splitOn, 3); + var allTokens = state.Lease(); + var tokenState1 = (factory1 ??= RowFactory.Default).Tokenize(state.Reader, allTokens.Slice(0, splits[0]), 0); + var tokenState2 = (factory2 ??= RowFactory.Default).Tokenize(state.Reader, allTokens.Slice(splits[0], splits[1] - splits[0]), splits[0]); + var tokenState3 = (factory3 ??= RowFactory.Default).Tokenize(state.Reader, allTokens.Slice(splits[1], splits[2] - splits[1]), splits[1]); + var tokenState4 = (factory4 ??= RowFactory.Default).Tokenize(state.Reader, allTokens.Slice(splits[2]), splits[2]); + + results = RowFactory.GetRowBuffer(rowCountHint); + do + { + var allTokensReadOnly = state.Tokens; + var obj1 = factory1.Read(state.Reader, allTokensReadOnly.Slice(0, splits[0]), 0, tokenState1); + var obj2 = factory2.Read(state.Reader, allTokensReadOnly.Slice(splits[0], splits[1] - splits[0]), splits[0], tokenState2); + var obj3 = factory3.Read(state.Reader, allTokensReadOnly.Slice(splits[1], splits[2] - splits[1]), splits[1], tokenState3); + var obj4 = factory4.Read(state.Reader, allTokensReadOnly.Slice(splits[2]), splits[2], tokenState4); + results.Add(map(obj1, obj2, obj3, obj4)); + } + while (await state.Reader.ReadAsync(cancellationToken)); + state.Return(); + } + else + { + results = []; + } + + while (await state.Reader.NextResultAsync(cancellationToken)) { } + PostProcessAndRecycle(state, args, await state.Reader.CloseAndCaptureAsync()); + return results; + } + finally + { + await state.DisposeAsync(); + } + } + + /// + /// Reads buffered rows from a multi-map query with 5 types + /// + public List QueryBuffered( + TArgs args, + Func map, + [DapperAot] RowFactory? factory1 = null, + [DapperAot] RowFactory? factory2 = null, + [DapperAot] RowFactory? factory3 = null, + [DapperAot] RowFactory? factory4 = null, + [DapperAot] RowFactory? factory5 = null, + string splitOn = "Id", + int rowCountHint = 0) + { + SyncQueryState state = default; + try + { + state.ExecuteReader(GetCommand(args), CommandBehavior.SingleResult | CommandBehavior.SequentialAccess); + + List results; + if (state.Reader.Read()) + { + var splits = FindSplits(state.Reader, splitOn, 4); + var allTokens = state.Lease(); + var tokenState1 = (factory1 ??= RowFactory.Default).Tokenize(state.Reader, allTokens.Slice(0, splits[0]), 0); + var tokenState2 = (factory2 ??= RowFactory.Default).Tokenize(state.Reader, allTokens.Slice(splits[0], splits[1] - splits[0]), splits[0]); + var tokenState3 = (factory3 ??= RowFactory.Default).Tokenize(state.Reader, allTokens.Slice(splits[1], splits[2] - splits[1]), splits[1]); + var tokenState4 = (factory4 ??= RowFactory.Default).Tokenize(state.Reader, allTokens.Slice(splits[2], splits[3] - splits[2]), splits[2]); + var tokenState5 = (factory5 ??= RowFactory.Default).Tokenize(state.Reader, allTokens.Slice(splits[3]), splits[3]); + + results = RowFactory.GetRowBuffer(rowCountHint); + do + { + var allTokensReadOnly = state.Tokens; + var obj1 = factory1.Read(state.Reader, allTokensReadOnly.Slice(0, splits[0]), 0, tokenState1); + var obj2 = factory2.Read(state.Reader, allTokensReadOnly.Slice(splits[0], splits[1] - splits[0]), splits[0], tokenState2); + var obj3 = factory3.Read(state.Reader, allTokensReadOnly.Slice(splits[1], splits[2] - splits[1]), splits[1], tokenState3); + var obj4 = factory4.Read(state.Reader, allTokensReadOnly.Slice(splits[2], splits[3] - splits[2]), splits[2], tokenState4); + var obj5 = factory5.Read(state.Reader, allTokensReadOnly.Slice(splits[3]), splits[3], tokenState5); + results.Add(map(obj1, obj2, obj3, obj4, obj5)); + } + while (state.Reader.Read()); + state.Return(); + } + else + { + results = []; + } + + while (state.Reader.NextResult()) { } + PostProcessAndRecycle(ref state, args, state.Reader.CloseAndCapture()); + return results; + } + finally + { + state.Dispose(); + } + } + + /// + /// Reads buffered rows from a multi-map query with 5 types (async) + /// + public async Task> QueryBufferedAsync( + TArgs args, + Func map, + [DapperAot] RowFactory? factory1 = null, + [DapperAot] RowFactory? factory2 = null, + [DapperAot] RowFactory? factory3 = null, + [DapperAot] RowFactory? factory4 = null, + [DapperAot] RowFactory? factory5 = null, + string splitOn = "Id", + int rowCountHint = 0, + CancellationToken cancellationToken = default) + { + AsyncQueryState state = new(); + try + { + cancellationToken = GetCancellationToken(args, cancellationToken); + await state.ExecuteReaderAsync(GetCommand(args), CommandBehavior.SingleResult | CommandBehavior.SequentialAccess, cancellationToken); + + List results; + if (await state.Reader.ReadAsync(cancellationToken)) + { + var splits = FindSplits(state.Reader, splitOn, 4); + var allTokens = state.Lease(); + var tokenState1 = (factory1 ??= RowFactory.Default).Tokenize(state.Reader, allTokens.Slice(0, splits[0]), 0); + var tokenState2 = (factory2 ??= RowFactory.Default).Tokenize(state.Reader, allTokens.Slice(splits[0], splits[1] - splits[0]), splits[0]); + var tokenState3 = (factory3 ??= RowFactory.Default).Tokenize(state.Reader, allTokens.Slice(splits[1], splits[2] - splits[1]), splits[1]); + var tokenState4 = (factory4 ??= RowFactory.Default).Tokenize(state.Reader, allTokens.Slice(splits[2], splits[3] - splits[2]), splits[2]); + var tokenState5 = (factory5 ??= RowFactory.Default).Tokenize(state.Reader, allTokens.Slice(splits[3]), splits[3]); + + results = RowFactory.GetRowBuffer(rowCountHint); + do + { + var allTokensReadOnly = state.Tokens; + var obj1 = factory1.Read(state.Reader, allTokensReadOnly.Slice(0, splits[0]), 0, tokenState1); + var obj2 = factory2.Read(state.Reader, allTokensReadOnly.Slice(splits[0], splits[1] - splits[0]), splits[0], tokenState2); + var obj3 = factory3.Read(state.Reader, allTokensReadOnly.Slice(splits[1], splits[2] - splits[1]), splits[1], tokenState3); + var obj4 = factory4.Read(state.Reader, allTokensReadOnly.Slice(splits[2], splits[3] - splits[2]), splits[2], tokenState4); + var obj5 = factory5.Read(state.Reader, allTokensReadOnly.Slice(splits[3]), splits[3], tokenState5); + results.Add(map(obj1, obj2, obj3, obj4, obj5)); + } + while (await state.Reader.ReadAsync(cancellationToken)); + state.Return(); + } + else + { + results = []; + } + + while (await state.Reader.NextResultAsync(cancellationToken)) { } + PostProcessAndRecycle(state, args, await state.Reader.CloseAndCaptureAsync()); + return results; + } + finally + { + await state.DisposeAsync(); + } + } + + /// + /// Reads buffered rows from a multi-map query with 6 types + /// + public List QueryBuffered( + TArgs args, + Func map, + [DapperAot] RowFactory? factory1 = null, + [DapperAot] RowFactory? factory2 = null, + [DapperAot] RowFactory? factory3 = null, + [DapperAot] RowFactory? factory4 = null, + [DapperAot] RowFactory? factory5 = null, + [DapperAot] RowFactory? factory6 = null, + string splitOn = "Id", + int rowCountHint = 0) + { + SyncQueryState state = default; + try + { + state.ExecuteReader(GetCommand(args), CommandBehavior.SingleResult | CommandBehavior.SequentialAccess); + + List results; + if (state.Reader.Read()) + { + var splits = FindSplits(state.Reader, splitOn, 5); + var allTokens = state.Lease(); + var tokenState1 = (factory1 ??= RowFactory.Default).Tokenize(state.Reader, allTokens.Slice(0, splits[0]), 0); + var tokenState2 = (factory2 ??= RowFactory.Default).Tokenize(state.Reader, allTokens.Slice(splits[0], splits[1] - splits[0]), splits[0]); + var tokenState3 = (factory3 ??= RowFactory.Default).Tokenize(state.Reader, allTokens.Slice(splits[1], splits[2] - splits[1]), splits[1]); + var tokenState4 = (factory4 ??= RowFactory.Default).Tokenize(state.Reader, allTokens.Slice(splits[2], splits[3] - splits[2]), splits[2]); + var tokenState5 = (factory5 ??= RowFactory.Default).Tokenize(state.Reader, allTokens.Slice(splits[3], splits[4] - splits[3]), splits[3]); + var tokenState6 = (factory6 ??= RowFactory.Default).Tokenize(state.Reader, allTokens.Slice(splits[4]), splits[4]); + + results = RowFactory.GetRowBuffer(rowCountHint); + do + { + var allTokensReadOnly = state.Tokens; + var obj1 = factory1.Read(state.Reader, allTokensReadOnly.Slice(0, splits[0]), 0, tokenState1); + var obj2 = factory2.Read(state.Reader, allTokensReadOnly.Slice(splits[0], splits[1] - splits[0]), splits[0], tokenState2); + var obj3 = factory3.Read(state.Reader, allTokensReadOnly.Slice(splits[1], splits[2] - splits[1]), splits[1], tokenState3); + var obj4 = factory4.Read(state.Reader, allTokensReadOnly.Slice(splits[2], splits[3] - splits[2]), splits[2], tokenState4); + var obj5 = factory5.Read(state.Reader, allTokensReadOnly.Slice(splits[3], splits[4] - splits[3]), splits[3], tokenState5); + var obj6 = factory6.Read(state.Reader, allTokensReadOnly.Slice(splits[4]), splits[4], tokenState6); + results.Add(map(obj1, obj2, obj3, obj4, obj5, obj6)); + } + while (state.Reader.Read()); + state.Return(); + } + else + { + results = []; + } + + while (state.Reader.NextResult()) { } + PostProcessAndRecycle(ref state, args, state.Reader.CloseAndCapture()); + return results; + } + finally + { + state.Dispose(); + } + } + + /// + /// Reads buffered rows from a multi-map query with 6 types (async) + /// + public async Task> QueryBufferedAsync( + TArgs args, + Func map, + [DapperAot] RowFactory? factory1 = null, + [DapperAot] RowFactory? factory2 = null, + [DapperAot] RowFactory? factory3 = null, + [DapperAot] RowFactory? factory4 = null, + [DapperAot] RowFactory? factory5 = null, + [DapperAot] RowFactory? factory6 = null, + string splitOn = "Id", + int rowCountHint = 0, + CancellationToken cancellationToken = default) + { + AsyncQueryState state = new(); + try + { + cancellationToken = GetCancellationToken(args, cancellationToken); + await state.ExecuteReaderAsync(GetCommand(args), CommandBehavior.SingleResult | CommandBehavior.SequentialAccess, cancellationToken); + + List results; + if (await state.Reader.ReadAsync(cancellationToken)) + { + var splits = FindSplits(state.Reader, splitOn, 5); + var allTokens = state.Lease(); + var tokenState1 = (factory1 ??= RowFactory.Default).Tokenize(state.Reader, allTokens.Slice(0, splits[0]), 0); + var tokenState2 = (factory2 ??= RowFactory.Default).Tokenize(state.Reader, allTokens.Slice(splits[0], splits[1] - splits[0]), splits[0]); + var tokenState3 = (factory3 ??= RowFactory.Default).Tokenize(state.Reader, allTokens.Slice(splits[1], splits[2] - splits[1]), splits[1]); + var tokenState4 = (factory4 ??= RowFactory.Default).Tokenize(state.Reader, allTokens.Slice(splits[2], splits[3] - splits[2]), splits[2]); + var tokenState5 = (factory5 ??= RowFactory.Default).Tokenize(state.Reader, allTokens.Slice(splits[3], splits[4] - splits[3]), splits[3]); + var tokenState6 = (factory6 ??= RowFactory.Default).Tokenize(state.Reader, allTokens.Slice(splits[4]), splits[4]); + + results = RowFactory.GetRowBuffer(rowCountHint); + do + { + var allTokensReadOnly = state.Tokens; + var obj1 = factory1.Read(state.Reader, allTokensReadOnly.Slice(0, splits[0]), 0, tokenState1); + var obj2 = factory2.Read(state.Reader, allTokensReadOnly.Slice(splits[0], splits[1] - splits[0]), splits[0], tokenState2); + var obj3 = factory3.Read(state.Reader, allTokensReadOnly.Slice(splits[1], splits[2] - splits[1]), splits[1], tokenState3); + var obj4 = factory4.Read(state.Reader, allTokensReadOnly.Slice(splits[2], splits[3] - splits[2]), splits[2], tokenState4); + var obj5 = factory5.Read(state.Reader, allTokensReadOnly.Slice(splits[3], splits[4] - splits[3]), splits[3], tokenState5); + var obj6 = factory6.Read(state.Reader, allTokensReadOnly.Slice(splits[4]), splits[4], tokenState6); + results.Add(map(obj1, obj2, obj3, obj4, obj5, obj6)); + } + while (await state.Reader.ReadAsync(cancellationToken)); + state.Return(); + } + else + { + results = []; + } + + while (await state.Reader.NextResultAsync(cancellationToken)) { } + PostProcessAndRecycle(state, args, await state.Reader.CloseAndCaptureAsync()); + return results; + } + finally + { + await state.DisposeAsync(); + } + } + + /// + /// Reads buffered rows from a multi-map query with 7 types + /// + public List QueryBuffered( + TArgs args, + Func map, + [DapperAot] RowFactory? factory1 = null, + [DapperAot] RowFactory? factory2 = null, + [DapperAot] RowFactory? factory3 = null, + [DapperAot] RowFactory? factory4 = null, + [DapperAot] RowFactory? factory5 = null, + [DapperAot] RowFactory? factory6 = null, + [DapperAot] RowFactory? factory7 = null, + string splitOn = "Id", + int rowCountHint = 0) + { + SyncQueryState state = default; + try + { + state.ExecuteReader(GetCommand(args), CommandBehavior.SingleResult | CommandBehavior.SequentialAccess); + + List results; + if (state.Reader.Read()) + { + var splits = FindSplits(state.Reader, splitOn, 6); + var allTokens = state.Lease(); + var tokenState1 = (factory1 ??= RowFactory.Default).Tokenize(state.Reader, allTokens.Slice(0, splits[0]), 0); + var tokenState2 = (factory2 ??= RowFactory.Default).Tokenize(state.Reader, allTokens.Slice(splits[0], splits[1] - splits[0]), splits[0]); + var tokenState3 = (factory3 ??= RowFactory.Default).Tokenize(state.Reader, allTokens.Slice(splits[1], splits[2] - splits[1]), splits[1]); + var tokenState4 = (factory4 ??= RowFactory.Default).Tokenize(state.Reader, allTokens.Slice(splits[2], splits[3] - splits[2]), splits[2]); + var tokenState5 = (factory5 ??= RowFactory.Default).Tokenize(state.Reader, allTokens.Slice(splits[3], splits[4] - splits[3]), splits[3]); + var tokenState6 = (factory6 ??= RowFactory.Default).Tokenize(state.Reader, allTokens.Slice(splits[4], splits[5] - splits[4]), splits[4]); + var tokenState7 = (factory7 ??= RowFactory.Default).Tokenize(state.Reader, allTokens.Slice(splits[5]), splits[5]); + + results = RowFactory.GetRowBuffer(rowCountHint); + do + { + var allTokensReadOnly = state.Tokens; + var obj1 = factory1.Read(state.Reader, allTokensReadOnly.Slice(0, splits[0]), 0, tokenState1); + var obj2 = factory2.Read(state.Reader, allTokensReadOnly.Slice(splits[0], splits[1] - splits[0]), splits[0], tokenState2); + var obj3 = factory3.Read(state.Reader, allTokensReadOnly.Slice(splits[1], splits[2] - splits[1]), splits[1], tokenState3); + var obj4 = factory4.Read(state.Reader, allTokensReadOnly.Slice(splits[2], splits[3] - splits[2]), splits[2], tokenState4); + var obj5 = factory5.Read(state.Reader, allTokensReadOnly.Slice(splits[3], splits[4] - splits[3]), splits[3], tokenState5); + var obj6 = factory6.Read(state.Reader, allTokensReadOnly.Slice(splits[4], splits[5] - splits[4]), splits[4], tokenState6); + var obj7 = factory7.Read(state.Reader, allTokensReadOnly.Slice(splits[5]), splits[5], tokenState7); + results.Add(map(obj1, obj2, obj3, obj4, obj5, obj6, obj7)); + } + while (state.Reader.Read()); + state.Return(); + } + else + { + results = []; + } + + while (state.Reader.NextResult()) { } + PostProcessAndRecycle(ref state, args, state.Reader.CloseAndCapture()); + return results; + } + finally + { + state.Dispose(); + } + } + + /// + /// Reads buffered rows from a multi-map query with 7 types (async) + /// + public async Task> QueryBufferedAsync( + TArgs args, + Func map, + [DapperAot] RowFactory? factory1 = null, + [DapperAot] RowFactory? factory2 = null, + [DapperAot] RowFactory? factory3 = null, + [DapperAot] RowFactory? factory4 = null, + [DapperAot] RowFactory? factory5 = null, + [DapperAot] RowFactory? factory6 = null, + [DapperAot] RowFactory? factory7 = null, + string splitOn = "Id", + int rowCountHint = 0, + CancellationToken cancellationToken = default) + { + AsyncQueryState state = new(); + try + { + cancellationToken = GetCancellationToken(args, cancellationToken); + await state.ExecuteReaderAsync(GetCommand(args), CommandBehavior.SingleResult | CommandBehavior.SequentialAccess, cancellationToken); + + List results; + if (await state.Reader.ReadAsync(cancellationToken)) + { + var splits = FindSplits(state.Reader, splitOn, 6); + var allTokens = state.Lease(); + var tokenState1 = (factory1 ??= RowFactory.Default).Tokenize(state.Reader, allTokens.Slice(0, splits[0]), 0); + var tokenState2 = (factory2 ??= RowFactory.Default).Tokenize(state.Reader, allTokens.Slice(splits[0], splits[1] - splits[0]), splits[0]); + var tokenState3 = (factory3 ??= RowFactory.Default).Tokenize(state.Reader, allTokens.Slice(splits[1], splits[2] - splits[1]), splits[1]); + var tokenState4 = (factory4 ??= RowFactory.Default).Tokenize(state.Reader, allTokens.Slice(splits[2], splits[3] - splits[2]), splits[2]); + var tokenState5 = (factory5 ??= RowFactory.Default).Tokenize(state.Reader, allTokens.Slice(splits[3], splits[4] - splits[3]), splits[3]); + var tokenState6 = (factory6 ??= RowFactory.Default).Tokenize(state.Reader, allTokens.Slice(splits[4], splits[5] - splits[4]), splits[4]); + var tokenState7 = (factory7 ??= RowFactory.Default).Tokenize(state.Reader, allTokens.Slice(splits[5]), splits[5]); + + results = RowFactory.GetRowBuffer(rowCountHint); + do + { + var allTokensReadOnly = state.Tokens; + var obj1 = factory1.Read(state.Reader, allTokensReadOnly.Slice(0, splits[0]), 0, tokenState1); + var obj2 = factory2.Read(state.Reader, allTokensReadOnly.Slice(splits[0], splits[1] - splits[0]), splits[0], tokenState2); + var obj3 = factory3.Read(state.Reader, allTokensReadOnly.Slice(splits[1], splits[2] - splits[1]), splits[1], tokenState3); + var obj4 = factory4.Read(state.Reader, allTokensReadOnly.Slice(splits[2], splits[3] - splits[2]), splits[2], tokenState4); + var obj5 = factory5.Read(state.Reader, allTokensReadOnly.Slice(splits[3], splits[4] - splits[3]), splits[3], tokenState5); + var obj6 = factory6.Read(state.Reader, allTokensReadOnly.Slice(splits[4], splits[5] - splits[4]), splits[4], tokenState6); + var obj7 = factory7.Read(state.Reader, allTokensReadOnly.Slice(splits[5]), splits[5], tokenState7); + results.Add(map(obj1, obj2, obj3, obj4, obj5, obj6, obj7)); + } + while (await state.Reader.ReadAsync(cancellationToken)); + state.Return(); + } + else + { + results = []; + } + + while (await state.Reader.NextResultAsync(cancellationToken)) { } + PostProcessAndRecycle(state, args, await state.Reader.CloseAndCaptureAsync()); + return results; + } + finally + { + await state.DisposeAsync(); + } + } + + /// + /// Reads unbuffered rows from a multi-map query with 2 types + /// + public IEnumerable QueryUnbuffered( + TArgs args, + Func map, + [DapperAot] RowFactory? factory1 = null, + [DapperAot] RowFactory? factory2 = null, + string splitOn = "Id") + { + SyncQueryState state = default; + try + { + state.ExecuteReader(GetCommand(args), CommandBehavior.SingleResult | CommandBehavior.SequentialAccess); + + if (state.Reader.Read()) + { + var split = FindSplits(state.Reader, splitOn, 1)[0]; + var allTokens = state.Lease(); + var tokenState1 = (factory1 ??= RowFactory.Default).Tokenize(state.Reader, allTokens.Slice(0, split), 0); + var tokenState2 = (factory2 ??= RowFactory.Default).Tokenize(state.Reader, allTokens.Slice(split), split); + + do + { + var allTokensReadOnly = state.Tokens; + var obj1 = factory1.Read(state.Reader, allTokensReadOnly.Slice(0, split), 0, tokenState1); + var obj2 = factory2.Read(state.Reader, allTokensReadOnly.Slice(split), split, tokenState2); + yield return map(obj1, obj2); + } + while (state.Reader.Read()); + state.Return(); + } + + while (state.Reader.NextResult()) { } + PostProcessAndRecycle(ref state, args, state.Reader.CloseAndCapture()); + } + finally + { + state.Dispose(); + } + } + + /// + /// Reads unbuffered rows from a multi-map query with 2 types (async) + /// + public async IAsyncEnumerable QueryUnbufferedAsync( + TArgs args, + Func map, + [DapperAot] RowFactory? factory1 = null, + [DapperAot] RowFactory? factory2 = null, + string splitOn = "Id", + [EnumeratorCancellation] CancellationToken cancellationToken = default) + { + AsyncQueryState state = new(); + try + { + cancellationToken = GetCancellationToken(args, cancellationToken); + await state.ExecuteReaderAsync(GetCommand(args), CommandBehavior.SingleResult | CommandBehavior.SequentialAccess, cancellationToken); + + if (await state.Reader.ReadAsync(cancellationToken)) + { + var split = FindSplits(state.Reader, splitOn, 1)[0]; + var allTokens = state.Lease(); + var tokenState1 = (factory1 ??= RowFactory.Default).Tokenize(state.Reader, allTokens.Slice(0, split), 0); + var tokenState2 = (factory2 ??= RowFactory.Default).Tokenize(state.Reader, allTokens.Slice(split), split); + + do + { + var allTokensReadOnly = state.Tokens; + var obj1 = factory1.Read(state.Reader, allTokensReadOnly.Slice(0, split), 0, tokenState1); + var obj2 = factory2.Read(state.Reader, allTokensReadOnly.Slice(split), split, tokenState2); + yield return map(obj1, obj2); + } + while (await state.Reader.ReadAsync(cancellationToken)); + state.Return(); + } + + while (await state.Reader.NextResultAsync(cancellationToken)) { } + PostProcessAndRecycle(state, args, await state.Reader.CloseAndCaptureAsync()); + } + finally + { + await state.DisposeAsync(); + } + } + + /// + /// Reads unbuffered rows from a multi-map query with 3 types + /// + public IEnumerable QueryUnbuffered( + TArgs args, + Func map, + [DapperAot] RowFactory? factory1 = null, + [DapperAot] RowFactory? factory2 = null, + [DapperAot] RowFactory? factory3 = null, + string splitOn = "Id") + { + SyncQueryState state = default; + try + { + state.ExecuteReader(GetCommand(args), CommandBehavior.SingleResult | CommandBehavior.SequentialAccess); + + if (state.Reader.Read()) + { + var splits = FindSplits(state.Reader, splitOn, 2); + var allTokens = state.Lease(); + var tokenState1 = (factory1 ??= RowFactory.Default).Tokenize(state.Reader, allTokens.Slice(0, splits[0]), 0); + var tokenState2 = (factory2 ??= RowFactory.Default).Tokenize(state.Reader, allTokens.Slice(splits[0], splits[1] - splits[0]), splits[0]); + var tokenState3 = (factory3 ??= RowFactory.Default).Tokenize(state.Reader, allTokens.Slice(splits[1]), splits[1]); + + do + { + var allTokensReadOnly = state.Tokens; + var obj1 = factory1.Read(state.Reader, allTokensReadOnly.Slice(0, splits[0]), 0, tokenState1); + var obj2 = factory2.Read(state.Reader, allTokensReadOnly.Slice(splits[0], splits[1] - splits[0]), splits[0], tokenState2); + var obj3 = factory3.Read(state.Reader, allTokensReadOnly.Slice(splits[1]), splits[1], tokenState3); + yield return map(obj1, obj2, obj3); + } + while (state.Reader.Read()); + state.Return(); + } + + while (state.Reader.NextResult()) { } + PostProcessAndRecycle(ref state, args, state.Reader.CloseAndCapture()); + } + finally + { + state.Dispose(); + } + } + + /// + /// Reads unbuffered rows from a multi-map query with 3 types (async) + /// + public async IAsyncEnumerable QueryUnbufferedAsync( + TArgs args, + Func map, + [DapperAot] RowFactory? factory1 = null, + [DapperAot] RowFactory? factory2 = null, + [DapperAot] RowFactory? factory3 = null, + string splitOn = "Id", + [EnumeratorCancellation] CancellationToken cancellationToken = default) + { + AsyncQueryState state = new(); + try + { + cancellationToken = GetCancellationToken(args, cancellationToken); + await state.ExecuteReaderAsync(GetCommand(args), CommandBehavior.SingleResult | CommandBehavior.SequentialAccess, cancellationToken); + + if (await state.Reader.ReadAsync(cancellationToken)) + { + var splits = FindSplits(state.Reader, splitOn, 2); + var allTokens = state.Lease(); + var tokenState1 = (factory1 ??= RowFactory.Default).Tokenize(state.Reader, allTokens.Slice(0, splits[0]), 0); + var tokenState2 = (factory2 ??= RowFactory.Default).Tokenize(state.Reader, allTokens.Slice(splits[0], splits[1] - splits[0]), splits[0]); + var tokenState3 = (factory3 ??= RowFactory.Default).Tokenize(state.Reader, allTokens.Slice(splits[1]), splits[1]); + + do + { + var allTokensReadOnly = state.Tokens; + var obj1 = factory1.Read(state.Reader, allTokensReadOnly.Slice(0, splits[0]), 0, tokenState1); + var obj2 = factory2.Read(state.Reader, allTokensReadOnly.Slice(splits[0], splits[1] - splits[0]), splits[0], tokenState2); + var obj3 = factory3.Read(state.Reader, allTokensReadOnly.Slice(splits[1]), splits[1], tokenState3); + yield return map(obj1, obj2, obj3); + } + while (await state.Reader.ReadAsync(cancellationToken)); + state.Return(); + } + + while (await state.Reader.NextResultAsync(cancellationToken)) { } + PostProcessAndRecycle(state, args, await state.Reader.CloseAndCaptureAsync()); + } + finally + { + await state.DisposeAsync(); + } + } + + /// + /// Reads unbuffered rows from a multi-map query with 4 types + /// + public IEnumerable QueryUnbuffered( + TArgs args, + Func map, + [DapperAot] RowFactory? factory1 = null, + [DapperAot] RowFactory? factory2 = null, + [DapperAot] RowFactory? factory3 = null, + [DapperAot] RowFactory? factory4 = null, + string splitOn = "Id") + { + SyncQueryState state = default; + try + { + state.ExecuteReader(GetCommand(args), CommandBehavior.SingleResult | CommandBehavior.SequentialAccess); + + if (state.Reader.Read()) + { + var splits = FindSplits(state.Reader, splitOn, 3); + var allTokens = state.Lease(); + var tokenState1 = (factory1 ??= RowFactory.Default).Tokenize(state.Reader, allTokens.Slice(0, splits[0]), 0); + var tokenState2 = (factory2 ??= RowFactory.Default).Tokenize(state.Reader, allTokens.Slice(splits[0], splits[1] - splits[0]), splits[0]); + var tokenState3 = (factory3 ??= RowFactory.Default).Tokenize(state.Reader, allTokens.Slice(splits[1], splits[2] - splits[1]), splits[1]); + var tokenState4 = (factory4 ??= RowFactory.Default).Tokenize(state.Reader, allTokens.Slice(splits[2]), splits[2]); + + do + { + var allTokensReadOnly = state.Tokens; + var obj1 = factory1.Read(state.Reader, allTokensReadOnly.Slice(0, splits[0]), 0, tokenState1); + var obj2 = factory2.Read(state.Reader, allTokensReadOnly.Slice(splits[0], splits[1] - splits[0]), splits[0], tokenState2); + var obj3 = factory3.Read(state.Reader, allTokensReadOnly.Slice(splits[1], splits[2] - splits[1]), splits[1], tokenState3); + var obj4 = factory4.Read(state.Reader, allTokensReadOnly.Slice(splits[2]), splits[2], tokenState4); + yield return map(obj1, obj2, obj3, obj4); + } + while (state.Reader.Read()); + state.Return(); + } + + while (state.Reader.NextResult()) { } + PostProcessAndRecycle(ref state, args, state.Reader.CloseAndCapture()); + } + finally + { + state.Dispose(); + } + } + + /// + /// Reads unbuffered rows from a multi-map query with 4 types (async) + /// + public async IAsyncEnumerable QueryUnbufferedAsync( + TArgs args, + Func map, + [DapperAot] RowFactory? factory1 = null, + [DapperAot] RowFactory? factory2 = null, + [DapperAot] RowFactory? factory3 = null, + [DapperAot] RowFactory? factory4 = null, + string splitOn = "Id", + [EnumeratorCancellation] CancellationToken cancellationToken = default) + { + AsyncQueryState state = new(); + try + { + cancellationToken = GetCancellationToken(args, cancellationToken); + await state.ExecuteReaderAsync(GetCommand(args), CommandBehavior.SingleResult | CommandBehavior.SequentialAccess, cancellationToken); + + if (await state.Reader.ReadAsync(cancellationToken)) + { + var splits = FindSplits(state.Reader, splitOn, 3); + var allTokens = state.Lease(); + var tokenState1 = (factory1 ??= RowFactory.Default).Tokenize(state.Reader, allTokens.Slice(0, splits[0]), 0); + var tokenState2 = (factory2 ??= RowFactory.Default).Tokenize(state.Reader, allTokens.Slice(splits[0], splits[1] - splits[0]), splits[0]); + var tokenState3 = (factory3 ??= RowFactory.Default).Tokenize(state.Reader, allTokens.Slice(splits[1], splits[2] - splits[1]), splits[1]); + var tokenState4 = (factory4 ??= RowFactory.Default).Tokenize(state.Reader, allTokens.Slice(splits[2]), splits[2]); + + do + { + var allTokensReadOnly = state.Tokens; + var obj1 = factory1.Read(state.Reader, allTokensReadOnly.Slice(0, splits[0]), 0, tokenState1); + var obj2 = factory2.Read(state.Reader, allTokensReadOnly.Slice(splits[0], splits[1] - splits[0]), splits[0], tokenState2); + var obj3 = factory3.Read(state.Reader, allTokensReadOnly.Slice(splits[1], splits[2] - splits[1]), splits[1], tokenState3); + var obj4 = factory4.Read(state.Reader, allTokensReadOnly.Slice(splits[2]), splits[2], tokenState4); + yield return map(obj1, obj2, obj3, obj4); + } + while (await state.Reader.ReadAsync(cancellationToken)); + state.Return(); + } + + while (await state.Reader.NextResultAsync(cancellationToken)) { } + PostProcessAndRecycle(state, args, await state.Reader.CloseAndCaptureAsync()); + } + finally + { + await state.DisposeAsync(); + } + } + + /// + /// Reads unbuffered rows from a multi-map query with 5 types + /// + public IEnumerable QueryUnbuffered( + TArgs args, + Func map, + [DapperAot] RowFactory? factory1 = null, + [DapperAot] RowFactory? factory2 = null, + [DapperAot] RowFactory? factory3 = null, + [DapperAot] RowFactory? factory4 = null, + [DapperAot] RowFactory? factory5 = null, + string splitOn = "Id") + { + SyncQueryState state = default; + try + { + state.ExecuteReader(GetCommand(args), CommandBehavior.SingleResult | CommandBehavior.SequentialAccess); + + if (state.Reader.Read()) + { + var splits = FindSplits(state.Reader, splitOn, 4); + var allTokens = state.Lease(); + var tokenState1 = (factory1 ??= RowFactory.Default).Tokenize(state.Reader, allTokens.Slice(0, splits[0]), 0); + var tokenState2 = (factory2 ??= RowFactory.Default).Tokenize(state.Reader, allTokens.Slice(splits[0], splits[1] - splits[0]), splits[0]); + var tokenState3 = (factory3 ??= RowFactory.Default).Tokenize(state.Reader, allTokens.Slice(splits[1], splits[2] - splits[1]), splits[1]); + var tokenState4 = (factory4 ??= RowFactory.Default).Tokenize(state.Reader, allTokens.Slice(splits[2], splits[3] - splits[2]), splits[2]); + var tokenState5 = (factory5 ??= RowFactory.Default).Tokenize(state.Reader, allTokens.Slice(splits[3]), splits[3]); + + do + { + var allTokensReadOnly = state.Tokens; + var obj1 = factory1.Read(state.Reader, allTokensReadOnly.Slice(0, splits[0]), 0, tokenState1); + var obj2 = factory2.Read(state.Reader, allTokensReadOnly.Slice(splits[0], splits[1] - splits[0]), splits[0], tokenState2); + var obj3 = factory3.Read(state.Reader, allTokensReadOnly.Slice(splits[1], splits[2] - splits[1]), splits[1], tokenState3); + var obj4 = factory4.Read(state.Reader, allTokensReadOnly.Slice(splits[2], splits[3] - splits[2]), splits[2], tokenState4); + var obj5 = factory5.Read(state.Reader, allTokensReadOnly.Slice(splits[3]), splits[3], tokenState5); + yield return map(obj1, obj2, obj3, obj4, obj5); + } + while (state.Reader.Read()); + state.Return(); + } + + while (state.Reader.NextResult()) { } + PostProcessAndRecycle(ref state, args, state.Reader.CloseAndCapture()); + } + finally + { + state.Dispose(); + } + } + + /// + /// Reads unbuffered rows from a multi-map query with 5 types (async) + /// + public async IAsyncEnumerable QueryUnbufferedAsync( + TArgs args, + Func map, + [DapperAot] RowFactory? factory1 = null, + [DapperAot] RowFactory? factory2 = null, + [DapperAot] RowFactory? factory3 = null, + [DapperAot] RowFactory? factory4 = null, + [DapperAot] RowFactory? factory5 = null, + string splitOn = "Id", + [EnumeratorCancellation] CancellationToken cancellationToken = default) + { + AsyncQueryState state = new(); + try + { + cancellationToken = GetCancellationToken(args, cancellationToken); + await state.ExecuteReaderAsync(GetCommand(args), CommandBehavior.SingleResult | CommandBehavior.SequentialAccess, cancellationToken); + + if (await state.Reader.ReadAsync(cancellationToken)) + { + var splits = FindSplits(state.Reader, splitOn, 4); + var allTokens = state.Lease(); + var tokenState1 = (factory1 ??= RowFactory.Default).Tokenize(state.Reader, allTokens.Slice(0, splits[0]), 0); + var tokenState2 = (factory2 ??= RowFactory.Default).Tokenize(state.Reader, allTokens.Slice(splits[0], splits[1] - splits[0]), splits[0]); + var tokenState3 = (factory3 ??= RowFactory.Default).Tokenize(state.Reader, allTokens.Slice(splits[1], splits[2] - splits[1]), splits[1]); + var tokenState4 = (factory4 ??= RowFactory.Default).Tokenize(state.Reader, allTokens.Slice(splits[2], splits[3] - splits[2]), splits[2]); + var tokenState5 = (factory5 ??= RowFactory.Default).Tokenize(state.Reader, allTokens.Slice(splits[3]), splits[3]); + + do + { + var allTokensReadOnly = state.Tokens; + var obj1 = factory1.Read(state.Reader, allTokensReadOnly.Slice(0, splits[0]), 0, tokenState1); + var obj2 = factory2.Read(state.Reader, allTokensReadOnly.Slice(splits[0], splits[1] - splits[0]), splits[0], tokenState2); + var obj3 = factory3.Read(state.Reader, allTokensReadOnly.Slice(splits[1], splits[2] - splits[1]), splits[1], tokenState3); + var obj4 = factory4.Read(state.Reader, allTokensReadOnly.Slice(splits[2], splits[3] - splits[2]), splits[2], tokenState4); + var obj5 = factory5.Read(state.Reader, allTokensReadOnly.Slice(splits[3]), splits[3], tokenState5); + yield return map(obj1, obj2, obj3, obj4, obj5); + } + while (await state.Reader.ReadAsync(cancellationToken)); + state.Return(); + } + + while (await state.Reader.NextResultAsync(cancellationToken)) { } + PostProcessAndRecycle(state, args, await state.Reader.CloseAndCaptureAsync()); + } + finally + { + await state.DisposeAsync(); + } + } + + /// + /// Reads unbuffered rows from a multi-map query with 6 types + /// + public IEnumerable QueryUnbuffered( + TArgs args, + Func map, + [DapperAot] RowFactory? factory1 = null, + [DapperAot] RowFactory? factory2 = null, + [DapperAot] RowFactory? factory3 = null, + [DapperAot] RowFactory? factory4 = null, + [DapperAot] RowFactory? factory5 = null, + [DapperAot] RowFactory? factory6 = null, + string splitOn = "Id") + { + SyncQueryState state = default; + try + { + state.ExecuteReader(GetCommand(args), CommandBehavior.SingleResult | CommandBehavior.SequentialAccess); + + if (state.Reader.Read()) + { + var splits = FindSplits(state.Reader, splitOn, 5); + var allTokens = state.Lease(); + var tokenState1 = (factory1 ??= RowFactory.Default).Tokenize(state.Reader, allTokens.Slice(0, splits[0]), 0); + var tokenState2 = (factory2 ??= RowFactory.Default).Tokenize(state.Reader, allTokens.Slice(splits[0], splits[1] - splits[0]), splits[0]); + var tokenState3 = (factory3 ??= RowFactory.Default).Tokenize(state.Reader, allTokens.Slice(splits[1], splits[2] - splits[1]), splits[1]); + var tokenState4 = (factory4 ??= RowFactory.Default).Tokenize(state.Reader, allTokens.Slice(splits[2], splits[3] - splits[2]), splits[2]); + var tokenState5 = (factory5 ??= RowFactory.Default).Tokenize(state.Reader, allTokens.Slice(splits[3], splits[4] - splits[3]), splits[3]); + var tokenState6 = (factory6 ??= RowFactory.Default).Tokenize(state.Reader, allTokens.Slice(splits[4]), splits[4]); + + do + { + var allTokensReadOnly = state.Tokens; + var obj1 = factory1.Read(state.Reader, allTokensReadOnly.Slice(0, splits[0]), 0, tokenState1); + var obj2 = factory2.Read(state.Reader, allTokensReadOnly.Slice(splits[0], splits[1] - splits[0]), splits[0], tokenState2); + var obj3 = factory3.Read(state.Reader, allTokensReadOnly.Slice(splits[1], splits[2] - splits[1]), splits[1], tokenState3); + var obj4 = factory4.Read(state.Reader, allTokensReadOnly.Slice(splits[2], splits[3] - splits[2]), splits[2], tokenState4); + var obj5 = factory5.Read(state.Reader, allTokensReadOnly.Slice(splits[3], splits[4] - splits[3]), splits[3], tokenState5); + var obj6 = factory6.Read(state.Reader, allTokensReadOnly.Slice(splits[4]), splits[4], tokenState6); + yield return map(obj1, obj2, obj3, obj4, obj5, obj6); + } + while (state.Reader.Read()); + state.Return(); + } + + while (state.Reader.NextResult()) { } + PostProcessAndRecycle(ref state, args, state.Reader.CloseAndCapture()); + } + finally + { + state.Dispose(); + } + } + + /// + /// Reads unbuffered rows from a multi-map query with 6 types (async) + /// + public async IAsyncEnumerable QueryUnbufferedAsync( + TArgs args, + Func map, + [DapperAot] RowFactory? factory1 = null, + [DapperAot] RowFactory? factory2 = null, + [DapperAot] RowFactory? factory3 = null, + [DapperAot] RowFactory? factory4 = null, + [DapperAot] RowFactory? factory5 = null, + [DapperAot] RowFactory? factory6 = null, + string splitOn = "Id", + [EnumeratorCancellation] CancellationToken cancellationToken = default) + { + AsyncQueryState state = new(); + try + { + cancellationToken = GetCancellationToken(args, cancellationToken); + await state.ExecuteReaderAsync(GetCommand(args), CommandBehavior.SingleResult | CommandBehavior.SequentialAccess, cancellationToken); + + if (await state.Reader.ReadAsync(cancellationToken)) + { + var splits = FindSplits(state.Reader, splitOn, 5); + var allTokens = state.Lease(); + var tokenState1 = (factory1 ??= RowFactory.Default).Tokenize(state.Reader, allTokens.Slice(0, splits[0]), 0); + var tokenState2 = (factory2 ??= RowFactory.Default).Tokenize(state.Reader, allTokens.Slice(splits[0], splits[1] - splits[0]), splits[0]); + var tokenState3 = (factory3 ??= RowFactory.Default).Tokenize(state.Reader, allTokens.Slice(splits[1], splits[2] - splits[1]), splits[1]); + var tokenState4 = (factory4 ??= RowFactory.Default).Tokenize(state.Reader, allTokens.Slice(splits[2], splits[3] - splits[2]), splits[2]); + var tokenState5 = (factory5 ??= RowFactory.Default).Tokenize(state.Reader, allTokens.Slice(splits[3], splits[4] - splits[3]), splits[3]); + var tokenState6 = (factory6 ??= RowFactory.Default).Tokenize(state.Reader, allTokens.Slice(splits[4]), splits[4]); + + do + { + var allTokensReadOnly = state.Tokens; + var obj1 = factory1.Read(state.Reader, allTokensReadOnly.Slice(0, splits[0]), 0, tokenState1); + var obj2 = factory2.Read(state.Reader, allTokensReadOnly.Slice(splits[0], splits[1] - splits[0]), splits[0], tokenState2); + var obj3 = factory3.Read(state.Reader, allTokensReadOnly.Slice(splits[1], splits[2] - splits[1]), splits[1], tokenState3); + var obj4 = factory4.Read(state.Reader, allTokensReadOnly.Slice(splits[2], splits[3] - splits[2]), splits[2], tokenState4); + var obj5 = factory5.Read(state.Reader, allTokensReadOnly.Slice(splits[3], splits[4] - splits[3]), splits[3], tokenState5); + var obj6 = factory6.Read(state.Reader, allTokensReadOnly.Slice(splits[4]), splits[4], tokenState6); + yield return map(obj1, obj2, obj3, obj4, obj5, obj6); + } + while (await state.Reader.ReadAsync(cancellationToken)); + state.Return(); + } + + while (await state.Reader.NextResultAsync(cancellationToken)) { } + PostProcessAndRecycle(state, args, await state.Reader.CloseAndCaptureAsync()); + } + finally + { + await state.DisposeAsync(); + } + } + + /// + /// Reads unbuffered rows from a multi-map query with 7 types + /// + public IEnumerable QueryUnbuffered( + TArgs args, + Func map, + [DapperAot] RowFactory? factory1 = null, + [DapperAot] RowFactory? factory2 = null, + [DapperAot] RowFactory? factory3 = null, + [DapperAot] RowFactory? factory4 = null, + [DapperAot] RowFactory? factory5 = null, + [DapperAot] RowFactory? factory6 = null, + [DapperAot] RowFactory? factory7 = null, + string splitOn = "Id") + { + SyncQueryState state = default; + try + { + state.ExecuteReader(GetCommand(args), CommandBehavior.SingleResult | CommandBehavior.SequentialAccess); + + if (state.Reader.Read()) + { + var splits = FindSplits(state.Reader, splitOn, 6); + var allTokens = state.Lease(); + var tokenState1 = (factory1 ??= RowFactory.Default).Tokenize(state.Reader, allTokens.Slice(0, splits[0]), 0); + var tokenState2 = (factory2 ??= RowFactory.Default).Tokenize(state.Reader, allTokens.Slice(splits[0], splits[1] - splits[0]), splits[0]); + var tokenState3 = (factory3 ??= RowFactory.Default).Tokenize(state.Reader, allTokens.Slice(splits[1], splits[2] - splits[1]), splits[1]); + var tokenState4 = (factory4 ??= RowFactory.Default).Tokenize(state.Reader, allTokens.Slice(splits[2], splits[3] - splits[2]), splits[2]); + var tokenState5 = (factory5 ??= RowFactory.Default).Tokenize(state.Reader, allTokens.Slice(splits[3], splits[4] - splits[3]), splits[3]); + var tokenState6 = (factory6 ??= RowFactory.Default).Tokenize(state.Reader, allTokens.Slice(splits[4], splits[5] - splits[4]), splits[4]); + var tokenState7 = (factory7 ??= RowFactory.Default).Tokenize(state.Reader, allTokens.Slice(splits[5]), splits[5]); + + do + { + var allTokensReadOnly = state.Tokens; + var obj1 = factory1.Read(state.Reader, allTokensReadOnly.Slice(0, splits[0]), 0, tokenState1); + var obj2 = factory2.Read(state.Reader, allTokensReadOnly.Slice(splits[0], splits[1] - splits[0]), splits[0], tokenState2); + var obj3 = factory3.Read(state.Reader, allTokensReadOnly.Slice(splits[1], splits[2] - splits[1]), splits[1], tokenState3); + var obj4 = factory4.Read(state.Reader, allTokensReadOnly.Slice(splits[2], splits[3] - splits[2]), splits[2], tokenState4); + var obj5 = factory5.Read(state.Reader, allTokensReadOnly.Slice(splits[3], splits[4] - splits[3]), splits[3], tokenState5); + var obj6 = factory6.Read(state.Reader, allTokensReadOnly.Slice(splits[4], splits[5] - splits[4]), splits[4], tokenState6); + var obj7 = factory7.Read(state.Reader, allTokensReadOnly.Slice(splits[5]), splits[5], tokenState7); + yield return map(obj1, obj2, obj3, obj4, obj5, obj6, obj7); + } + while (state.Reader.Read()); + state.Return(); + } + + while (state.Reader.NextResult()) { } + PostProcessAndRecycle(ref state, args, state.Reader.CloseAndCapture()); + } + finally + { + state.Dispose(); + } + } + + /// + /// Reads unbuffered rows from a multi-map query with 7 types (async) + /// + public async IAsyncEnumerable QueryUnbufferedAsync( + TArgs args, + Func map, + [DapperAot] RowFactory? factory1 = null, + [DapperAot] RowFactory? factory2 = null, + [DapperAot] RowFactory? factory3 = null, + [DapperAot] RowFactory? factory4 = null, + [DapperAot] RowFactory? factory5 = null, + [DapperAot] RowFactory? factory6 = null, + [DapperAot] RowFactory? factory7 = null, + string splitOn = "Id", + [EnumeratorCancellation] CancellationToken cancellationToken = default) + { + AsyncQueryState state = new(); + try + { + cancellationToken = GetCancellationToken(args, cancellationToken); + await state.ExecuteReaderAsync(GetCommand(args), CommandBehavior.SingleResult | CommandBehavior.SequentialAccess, cancellationToken); + + if (await state.Reader.ReadAsync(cancellationToken)) + { + var splits = FindSplits(state.Reader, splitOn, 6); + var allTokens = state.Lease(); + var tokenState1 = (factory1 ??= RowFactory.Default).Tokenize(state.Reader, allTokens.Slice(0, splits[0]), 0); + var tokenState2 = (factory2 ??= RowFactory.Default).Tokenize(state.Reader, allTokens.Slice(splits[0], splits[1] - splits[0]), splits[0]); + var tokenState3 = (factory3 ??= RowFactory.Default).Tokenize(state.Reader, allTokens.Slice(splits[1], splits[2] - splits[1]), splits[1]); + var tokenState4 = (factory4 ??= RowFactory.Default).Tokenize(state.Reader, allTokens.Slice(splits[2], splits[3] - splits[2]), splits[2]); + var tokenState5 = (factory5 ??= RowFactory.Default).Tokenize(state.Reader, allTokens.Slice(splits[3], splits[4] - splits[3]), splits[3]); + var tokenState6 = (factory6 ??= RowFactory.Default).Tokenize(state.Reader, allTokens.Slice(splits[4], splits[5] - splits[4]), splits[4]); + var tokenState7 = (factory7 ??= RowFactory.Default).Tokenize(state.Reader, allTokens.Slice(splits[5]), splits[5]); + + do + { + var allTokensReadOnly = state.Tokens; + var obj1 = factory1.Read(state.Reader, allTokensReadOnly.Slice(0, splits[0]), 0, tokenState1); + var obj2 = factory2.Read(state.Reader, allTokensReadOnly.Slice(splits[0], splits[1] - splits[0]), splits[0], tokenState2); + var obj3 = factory3.Read(state.Reader, allTokensReadOnly.Slice(splits[1], splits[2] - splits[1]), splits[1], tokenState3); + var obj4 = factory4.Read(state.Reader, allTokensReadOnly.Slice(splits[2], splits[3] - splits[2]), splits[2], tokenState4); + var obj5 = factory5.Read(state.Reader, allTokensReadOnly.Slice(splits[3], splits[4] - splits[3]), splits[3], tokenState5); + var obj6 = factory6.Read(state.Reader, allTokensReadOnly.Slice(splits[4], splits[5] - splits[4]), splits[4], tokenState6); + var obj7 = factory7.Read(state.Reader, allTokensReadOnly.Slice(splits[5]), splits[5], tokenState7); + yield return map(obj1, obj2, obj3, obj4, obj5, obj6, obj7); + } + while (await state.Reader.ReadAsync(cancellationToken)); + state.Return(); + } + + while (await state.Reader.NextResultAsync(cancellationToken)) { } + PostProcessAndRecycle(state, args, await state.Reader.CloseAndCaptureAsync()); + } + finally + { + await state.DisposeAsync(); + } + } +} + diff --git a/src/Dapper.AOT/Internal/CommandUtils.cs b/src/Dapper.AOT/Internal/CommandUtils.cs index 00ac941..7edf031 100644 --- a/src/Dapper.AOT/Internal/CommandUtils.cs +++ b/src/Dapper.AOT/Internal/CommandUtils.cs @@ -303,7 +303,40 @@ internal static T As(object? value) } else { - return (T)Convert.ChangeType(value, Nullable.GetUnderlyingType(typeof(T)) ?? typeof(T), CultureInfo.InvariantCulture); + // Handle enum types: match original Dapper's enum conversion logic + // Enums require special handling because: + // 1. Databases may return different integer types (e.g., SQLite returns Int64 for INTEGER columns) + // 2. The enum's underlying type might differ (e.g., enum with int underlying type vs Int64 from DB) + // 3. Floating point values need conversion to the enum's underlying integral type first + // Enum.ToObject handles the conversion from any integral type to the enum automatically + var targetType = typeof(T); + var underlyingType = Nullable.GetUnderlyingType(targetType); + + // Unwrap nullable first, like original Dapper does + var effectiveType = underlyingType ?? targetType; + + if (effectiveType.IsEnum) + { + // Special handling for float/double/decimal like original Dapper + if (value is float || value is double || value is decimal) + { + value = Convert.ChangeType(value, Enum.GetUnderlyingType(effectiveType), CultureInfo.InvariantCulture); + } + // Enum.ToObject returns the enum value boxed as object + // For nullable enums, the cast from object will fail, so we need to use Convert.ChangeType + // which properly handles boxing/unboxing for nullable types + return (T)Enum.ToObject(effectiveType, value); + } + else if (underlyingType is not null) + { + // Other nullable types + return (T)Convert.ChangeType(value, underlyingType, CultureInfo.InvariantCulture); + } + else + { + // Non-nullable, non-enum fallback + return (T)Convert.ChangeType(value, targetType, CultureInfo.InvariantCulture); + } } } } diff --git a/test/Dapper.AOT.Test/Interceptors/Cancellation.output.cs b/test/Dapper.AOT.Test/Interceptors/Cancellation.output.cs index 5e7b41f..d6f99f0 100644 --- a/test/Dapper.AOT.Test/Interceptors/Cancellation.output.cs +++ b/test/Dapper.AOT.Test/Interceptors/Cancellation.output.cs @@ -59,7 +59,9 @@ private sealed class RowFactory0 : global::Dapper.RowFactory private RowFactory0() {} public override object? Tokenize(global::System.Data.Common.DbDataReader reader, global::System.Span tokens, int columnOffset) { - for (int i = 0; i < tokens.Length; i++) + int availableColumns = reader.FieldCount - columnOffset; + int tokenCount = global::System.Math.Min(tokens.Length, availableColumns); + for (int i = 0; i < tokenCount; i++) { int token = -1; var name = reader.GetName(columnOffset); @@ -75,13 +77,21 @@ private RowFactory0() {} columnOffset++; } - return null; + // Initialize remaining tokens to -1 (unmapped) + for (int i = tokenCount; i < tokens.Length; i++) + { + tokens[i] = -1; + } + return tokenCount; } public override global::Foo.MyType Read(global::System.Data.Common.DbDataReader reader, global::System.ReadOnlySpan tokens, int columnOffset, object? state) { + if (columnOffset > 0 && reader.IsDBNull(columnOffset)) return default!; global::Foo.MyType result = new(); - foreach (var token in tokens) + int tokenCount = state is int count ? count : tokens.Length; + for (int i = 0; i < tokenCount; i++) { + var token = tokens[i]; switch (token) { case 0: diff --git a/test/Dapper.AOT.Test/Interceptors/Cancellation.output.netfx.cs b/test/Dapper.AOT.Test/Interceptors/Cancellation.output.netfx.cs index 5e7b41f..d6f99f0 100644 --- a/test/Dapper.AOT.Test/Interceptors/Cancellation.output.netfx.cs +++ b/test/Dapper.AOT.Test/Interceptors/Cancellation.output.netfx.cs @@ -59,7 +59,9 @@ private sealed class RowFactory0 : global::Dapper.RowFactory private RowFactory0() {} public override object? Tokenize(global::System.Data.Common.DbDataReader reader, global::System.Span tokens, int columnOffset) { - for (int i = 0; i < tokens.Length; i++) + int availableColumns = reader.FieldCount - columnOffset; + int tokenCount = global::System.Math.Min(tokens.Length, availableColumns); + for (int i = 0; i < tokenCount; i++) { int token = -1; var name = reader.GetName(columnOffset); @@ -75,13 +77,21 @@ private RowFactory0() {} columnOffset++; } - return null; + // Initialize remaining tokens to -1 (unmapped) + for (int i = tokenCount; i < tokens.Length; i++) + { + tokens[i] = -1; + } + return tokenCount; } public override global::Foo.MyType Read(global::System.Data.Common.DbDataReader reader, global::System.ReadOnlySpan tokens, int columnOffset, object? state) { + if (columnOffset > 0 && reader.IsDBNull(columnOffset)) return default!; global::Foo.MyType result = new(); - foreach (var token in tokens) + int tokenCount = state is int count ? count : tokens.Length; + for (int i = 0; i < tokenCount; i++) { + var token = tokens[i]; switch (token) { case 0: diff --git a/test/Dapper.AOT.Test/Interceptors/ColumnAttribute.output.cs b/test/Dapper.AOT.Test/Interceptors/ColumnAttribute.output.cs index 95e4a99..1cd7fca 100644 --- a/test/Dapper.AOT.Test/Interceptors/ColumnAttribute.output.cs +++ b/test/Dapper.AOT.Test/Interceptors/ColumnAttribute.output.cs @@ -44,7 +44,9 @@ private sealed class RowFactory0 : global::Dapper.RowFactory private RowFactory0() {} public override object? Tokenize(global::System.Data.Common.DbDataReader reader, global::System.Span tokens, int columnOffset) { - for (int i = 0; i < tokens.Length; i++) + int availableColumns = reader.FieldCount - columnOffset; + int tokenCount = global::System.Math.Min(tokens.Length, availableColumns); + for (int i = 0; i < tokenCount; i++) { int token = -1; var name = reader.GetName(columnOffset); @@ -72,13 +74,21 @@ private RowFactory0() {} columnOffset++; } - return null; + // Initialize remaining tokens to -1 (unmapped) + for (int i = tokenCount; i < tokens.Length; i++) + { + tokens[i] = -1; + } + return tokenCount; } public override global::Foo.MyType Read(global::System.Data.Common.DbDataReader reader, global::System.ReadOnlySpan tokens, int columnOffset, object? state) { + if (columnOffset > 0 && reader.IsDBNull(columnOffset)) return default!; global::Foo.MyType result = new(); - foreach (var token in tokens) + int tokenCount = state is int count ? count : tokens.Length; + for (int i = 0; i < tokenCount; i++) { + var token = tokens[i]; switch (token) { case 0: diff --git a/test/Dapper.AOT.Test/Interceptors/ColumnAttribute.output.netfx.cs b/test/Dapper.AOT.Test/Interceptors/ColumnAttribute.output.netfx.cs index 95e4a99..1cd7fca 100644 --- a/test/Dapper.AOT.Test/Interceptors/ColumnAttribute.output.netfx.cs +++ b/test/Dapper.AOT.Test/Interceptors/ColumnAttribute.output.netfx.cs @@ -44,7 +44,9 @@ private sealed class RowFactory0 : global::Dapper.RowFactory private RowFactory0() {} public override object? Tokenize(global::System.Data.Common.DbDataReader reader, global::System.Span tokens, int columnOffset) { - for (int i = 0; i < tokens.Length; i++) + int availableColumns = reader.FieldCount - columnOffset; + int tokenCount = global::System.Math.Min(tokens.Length, availableColumns); + for (int i = 0; i < tokenCount; i++) { int token = -1; var name = reader.GetName(columnOffset); @@ -72,13 +74,21 @@ private RowFactory0() {} columnOffset++; } - return null; + // Initialize remaining tokens to -1 (unmapped) + for (int i = tokenCount; i < tokens.Length; i++) + { + tokens[i] = -1; + } + return tokenCount; } public override global::Foo.MyType Read(global::System.Data.Common.DbDataReader reader, global::System.ReadOnlySpan tokens, int columnOffset, object? state) { + if (columnOffset > 0 && reader.IsDBNull(columnOffset)) return default!; global::Foo.MyType result = new(); - foreach (var token in tokens) + int tokenCount = state is int count ? count : tokens.Length; + for (int i = 0; i < tokenCount; i++) { + var token = tokens[i]; switch (token) { case 0: diff --git a/test/Dapper.AOT.Test/Interceptors/CommandProperties.output.cs b/test/Dapper.AOT.Test/Interceptors/CommandProperties.output.cs index 04baf13..b585259 100644 --- a/test/Dapper.AOT.Test/Interceptors/CommandProperties.output.cs +++ b/test/Dapper.AOT.Test/Interceptors/CommandProperties.output.cs @@ -111,7 +111,9 @@ private sealed class RowFactory0 : global::Dapper.RowFactory tokens, int columnOffset) { - for (int i = 0; i < tokens.Length; i++) + int availableColumns = reader.FieldCount - columnOffset; + int tokenCount = global::System.Math.Min(tokens.Length, availableColumns); + for (int i = 0; i < tokenCount; i++) { int token = -1; var name = reader.GetName(columnOffset); @@ -133,13 +135,21 @@ private RowFactory0() {} columnOffset++; } - return null; + // Initialize remaining tokens to -1 (unmapped) + for (int i = tokenCount; i < tokens.Length; i++) + { + tokens[i] = -1; + } + return tokenCount; } public override global::Foo.Customer Read(global::System.Data.Common.DbDataReader reader, global::System.ReadOnlySpan tokens, int columnOffset, object? state) { + if (columnOffset > 0 && reader.IsDBNull(columnOffset)) return default!; global::Foo.Customer result = new(); - foreach (var token in tokens) + int tokenCount = state is int count ? count : tokens.Length; + for (int i = 0; i < tokenCount; i++) { + var token = tokens[i]; switch (token) { case 0: diff --git a/test/Dapper.AOT.Test/Interceptors/CommandProperties.output.netfx.cs b/test/Dapper.AOT.Test/Interceptors/CommandProperties.output.netfx.cs index 04baf13..b585259 100644 --- a/test/Dapper.AOT.Test/Interceptors/CommandProperties.output.netfx.cs +++ b/test/Dapper.AOT.Test/Interceptors/CommandProperties.output.netfx.cs @@ -111,7 +111,9 @@ private sealed class RowFactory0 : global::Dapper.RowFactory tokens, int columnOffset) { - for (int i = 0; i < tokens.Length; i++) + int availableColumns = reader.FieldCount - columnOffset; + int tokenCount = global::System.Math.Min(tokens.Length, availableColumns); + for (int i = 0; i < tokenCount; i++) { int token = -1; var name = reader.GetName(columnOffset); @@ -133,13 +135,21 @@ private RowFactory0() {} columnOffset++; } - return null; + // Initialize remaining tokens to -1 (unmapped) + for (int i = tokenCount; i < tokens.Length; i++) + { + tokens[i] = -1; + } + return tokenCount; } public override global::Foo.Customer Read(global::System.Data.Common.DbDataReader reader, global::System.ReadOnlySpan tokens, int columnOffset, object? state) { + if (columnOffset > 0 && reader.IsDBNull(columnOffset)) return default!; global::Foo.Customer result = new(); - foreach (var token in tokens) + int tokenCount = state is int count ? count : tokens.Length; + for (int i = 0; i < tokenCount; i++) { + var token = tokens[i]; switch (token) { case 0: diff --git a/test/Dapper.AOT.Test/Interceptors/DateOnly.net6.output.cs b/test/Dapper.AOT.Test/Interceptors/DateOnly.net6.output.cs index 65050cd..52f520a 100644 --- a/test/Dapper.AOT.Test/Interceptors/DateOnly.net6.output.cs +++ b/test/Dapper.AOT.Test/Interceptors/DateOnly.net6.output.cs @@ -62,7 +62,9 @@ private sealed class RowFactory0 : global::Dapper.RowFactory private RowFactory0() {} public override object? Tokenize(global::System.Data.Common.DbDataReader reader, global::System.Span tokens, int columnOffset) { - for (int i = 0; i < tokens.Length; i++) + int availableColumns = reader.FieldCount - columnOffset; + int tokenCount = global::System.Math.Min(tokens.Length, availableColumns); + for (int i = 0; i < tokenCount; i++) { int token = -1; var name = reader.GetName(columnOffset); @@ -84,13 +86,21 @@ private RowFactory0() {} columnOffset++; } - return null; + // Initialize remaining tokens to -1 (unmapped) + for (int i = tokenCount; i < tokens.Length; i++) + { + tokens[i] = -1; + } + return tokenCount; } public override global::Foo.User Read(global::System.Data.Common.DbDataReader reader, global::System.ReadOnlySpan tokens, int columnOffset, object? state) { + if (columnOffset > 0 && reader.IsDBNull(columnOffset)) return default!; global::Foo.User result = new(); - foreach (var token in tokens) + int tokenCount = state is int count ? count : tokens.Length; + for (int i = 0; i < tokenCount; i++) { + var token = tokens[i]; switch (token) { case 0: diff --git a/test/Dapper.AOT.Test/Interceptors/DateOnly.net6.output.netfx.cs b/test/Dapper.AOT.Test/Interceptors/DateOnly.net6.output.netfx.cs deleted file mode 100644 index c1abf85..0000000 --- a/test/Dapper.AOT.Test/Interceptors/DateOnly.net6.output.netfx.cs +++ /dev/null @@ -1,190 +0,0 @@ -#nullable enable -namespace Dapper.AOT // interceptors must be in a known namespace -{ - file static class DapperGeneratedInterceptors - { - [global::System.Runtime.CompilerServices.InterceptsLocationAttribute("Interceptors\\DateOnly.net6.input.cs", 12, 30)] - internal static global::System.Threading.Tasks.Task> QueryAsync0(this global::System.Data.IDbConnection cnn, string sql, object? param, global::System.Data.IDbTransaction? transaction, int? commandTimeout, global::System.Data.CommandType? commandType) - { - // Query, Async, TypedResult, HasParameters, Buffered, Text, BindResultsByName, KnownParameters - // takes parameter: - // parameter map: BirthDate - // returns data: global::Foo.User - global::System.Diagnostics.Debug.Assert(!string.IsNullOrWhiteSpace(sql)); - global::System.Diagnostics.Debug.Assert((commandType ?? global::Dapper.DapperAotExtensions.GetCommandType(sql)) == global::System.Data.CommandType.Text); - global::System.Diagnostics.Debug.Assert(param is not null); - - return global::Dapper.DapperAotExtensions.AsEnumerableAsync( - global::Dapper.DapperAotExtensions.Command(cnn, transaction, sql, global::System.Data.CommandType.Text, commandTimeout.GetValueOrDefault(), CommandFactory0.Instance).QueryBufferedAsync(param, RowFactory0.Instance)); - - } - - [global::System.Runtime.CompilerServices.InterceptsLocationAttribute("Interceptors\\DateOnly.net6.input.cs", 17, 30)] - internal static global::System.Threading.Tasks.Task> QueryAsync1(this global::System.Data.IDbConnection cnn, string sql, object? param, global::System.Data.IDbTransaction? transaction, int? commandTimeout, global::System.Data.CommandType? commandType) - { - // Query, Async, TypedResult, HasParameters, Buffered, Text, BindResultsByName, KnownParameters - // takes parameter: global::Foo.QueryModel - // parameter map: BirthDate - // returns data: global::Foo.User - global::System.Diagnostics.Debug.Assert(!string.IsNullOrWhiteSpace(sql)); - global::System.Diagnostics.Debug.Assert((commandType ?? global::Dapper.DapperAotExtensions.GetCommandType(sql)) == global::System.Data.CommandType.Text); - global::System.Diagnostics.Debug.Assert(param is not null); - - return global::Dapper.DapperAotExtensions.AsEnumerableAsync( - global::Dapper.DapperAotExtensions.Command(cnn, transaction, sql, global::System.Data.CommandType.Text, commandTimeout.GetValueOrDefault(), CommandFactory1.Instance).QueryBufferedAsync((global::Foo.QueryModel)param!, RowFactory0.Instance)); - - } - - private class CommonCommandFactory : global::Dapper.CommandFactory - { - public override global::System.Data.Common.DbCommand GetCommand(global::System.Data.Common.DbConnection connection, string sql, global::System.Data.CommandType commandType, T args) - { - var cmd = base.GetCommand(connection, sql, commandType, args); - // apply special per-provider command initialization logic for OracleCommand - if (cmd is global::Oracle.ManagedDataAccess.Client.OracleCommand cmd0) - { - cmd0.BindByName = true; - cmd0.InitialLONGFetchSize = -1; - - } - return cmd; - } - - } - - private static readonly CommonCommandFactory DefaultCommandFactory = new(); - - private sealed class RowFactory0 : global::Dapper.RowFactory - { - internal static readonly RowFactory0 Instance = new(); - private RowFactory0() {} - public override object? Tokenize(global::System.Data.Common.DbDataReader reader, global::System.Span tokens, int columnOffset) - { - for (int i = 0; i < tokens.Length; i++) - { - int token = -1; - var name = reader.GetName(columnOffset); - var type = reader.GetFieldType(columnOffset); - switch (NormalizedHash(name)) - { - case 926444256U when NormalizedEquals(name, "id"): - token = type == typeof(int) ? 0 : 3; // two tokens for right-typed and type-flexible - break; - case 2369371622U when NormalizedEquals(name, "name"): - token = type == typeof(string) ? 1 : 4; - break; - case 4237030186U when NormalizedEquals(name, "birthdate"): - token = type == typeof(DateOnly) ? 2 : 5; - break; - - } - tokens[i] = token; - columnOffset++; - - } - return null; - } - public override global::Foo.User Read(global::System.Data.Common.DbDataReader reader, global::System.ReadOnlySpan tokens, int columnOffset, object? state) - { - global::Foo.User result = new(); - foreach (var token in tokens) - { - switch (token) - { - case 0: - result.Id = reader.GetInt32(columnOffset); - break; - case 3: - result.Id = GetValue(reader, columnOffset); - break; - case 1: - result.Name = reader.IsDBNull(columnOffset) ? (string?)null : reader.GetString(columnOffset); - break; - case 4: - result.Name = reader.IsDBNull(columnOffset) ? (string?)null : GetValue(reader, columnOffset); - break; - case 2: - result.BirthDate = reader.IsDBNull(columnOffset) ? (DateOnly?)null : reader.GetFieldValue(columnOffset); - break; - case 5: - result.BirthDate = reader.IsDBNull(columnOffset) ? (DateOnly?)null : GetValue(reader, columnOffset); - break; - - } - columnOffset++; - - } - return result; - - } - - } - - private sealed class CommandFactory0 : CommonCommandFactory // - { - internal static readonly CommandFactory0 Instance = new(); - public override void AddParameters(in global::Dapper.UnifiedCommand cmd, object? args) - { - var typed = Cast(args, static () => new { BirthDate = default()! }); // expected shape - var ps = cmd.Parameters; - global::System.Data.Common.DbParameter p; - p = cmd.CreateParameter(); - p.ParameterName = "BirthDate"; - p.Direction = global::System.Data.ParameterDirection.Input; - p.Value = AsValue(typed.BirthDate); - ps.Add(p); - - } - public override void UpdateParameters(in global::Dapper.UnifiedCommand cmd, object? args) - { - var typed = Cast(args, static () => new { BirthDate = default()! }); // expected shape - var ps = cmd.Parameters; - ps[0].Value = AsValue(typed.BirthDate); - - } - - } - - private sealed class CommandFactory1 : CommonCommandFactory - { - internal static readonly CommandFactory1 Instance = new(); - public override void AddParameters(in global::Dapper.UnifiedCommand cmd, global::Foo.QueryModel args) - { - var ps = cmd.Parameters; - global::System.Data.Common.DbParameter p; - p = cmd.CreateParameter(); - p.ParameterName = "BirthDate"; - p.Direction = global::System.Data.ParameterDirection.Input; - p.Value = AsValue(args.BirthDate); - ps.Add(p); - - } - public override void UpdateParameters(in global::Dapper.UnifiedCommand cmd, global::Foo.QueryModel args) - { - var ps = cmd.Parameters; - ps[0].Value = AsValue(args.BirthDate); - - } - - } - - - } -} -namespace System.Runtime.CompilerServices -{ - // this type is needed by the compiler to implement interceptors - it doesn't need to - // come from the runtime itself, though - - [global::System.Diagnostics.Conditional("DEBUG")] // not needed post-build, so: evaporate - [global::System.AttributeUsage(global::System.AttributeTargets.Method, AllowMultiple = true)] - sealed file class InterceptsLocationAttribute : global::System.Attribute - { - public InterceptsLocationAttribute(string path, int lineNumber, int columnNumber) - { - _ = path; - _ = lineNumber; - _ = columnNumber; - } - } -} \ No newline at end of file diff --git a/test/Dapper.AOT.Test/Interceptors/DateOnly.net6.output.netfx.txt b/test/Dapper.AOT.Test/Interceptors/DateOnly.net6.output.netfx.txt deleted file mode 100644 index 5ba1f0d..0000000 --- a/test/Dapper.AOT.Test/Interceptors/DateOnly.net6.output.netfx.txt +++ /dev/null @@ -1,52 +0,0 @@ -Input code has 4 diagnostics from 'Interceptors/DateOnly.net6.input.cs': - -Error CS0103 Interceptors/DateOnly.net6.input.cs L14 C25 -The name 'DateOnly' does not exist in the current context - -Error CS0103 Interceptors/DateOnly.net6.input.cs L19 C25 -The name 'DateOnly' does not exist in the current context - -Error CS0246 Interceptors/DateOnly.net6.input.cs L25 C16 -The type or namespace name 'DateOnly' could not be found (are you missing a using directive or an assembly reference?) - -Error CS0246 Interceptors/DateOnly.net6.input.cs L32 C16 -The type or namespace name 'DateOnly' could not be found (are you missing a using directive or an assembly reference?) -Generator produced 1 diagnostics: - -Hidden DAP000 L1 C1 -Dapper.AOT handled 2 of 2 possible call-sites using 2 interceptors, 2 commands and 1 readers -Output code has 4 diagnostics from 'Interceptors/DateOnly.net6.input.cs': - -Error CS0103 Interceptors/DateOnly.net6.input.cs L14 C25 -The name 'DateOnly' does not exist in the current context - -Error CS0103 Interceptors/DateOnly.net6.input.cs L19 C25 -The name 'DateOnly' does not exist in the current context - -Error CS0246 Interceptors/DateOnly.net6.input.cs L25 C16 -The type or namespace name 'DateOnly' could not be found (are you missing a using directive or an assembly reference?) - -Error CS0246 Interceptors/DateOnly.net6.input.cs L32 C16 -The type or namespace name 'DateOnly' could not be found (are you missing a using directive or an assembly reference?) -Output code has 7 diagnostics from 'Dapper.AOT.Analyzers/Dapper.CodeAnalysis.DapperInterceptorGenerator/Test.generated.cs': - -Error CS0246 Dapper.AOT.Analyzers/Dapper.CodeAnalysis.DapperInterceptorGenerator/Test.generated.cs L77 C52 -The type or namespace name 'DateOnly' could not be found (are you missing a using directive or an assembly reference?) - -Error CS0246 Dapper.AOT.Analyzers/Dapper.CodeAnalysis.DapperInterceptorGenerator/Test.generated.cs L107 C81 -The type or namespace name 'DateOnly' could not be found (are you missing a using directive or an assembly reference?) - -Error CS0246 Dapper.AOT.Analyzers/Dapper.CodeAnalysis.DapperInterceptorGenerator/Test.generated.cs L107 C119 -The type or namespace name 'DateOnly' could not be found (are you missing a using directive or an assembly reference?) - -Error CS0246 Dapper.AOT.Analyzers/Dapper.CodeAnalysis.DapperInterceptorGenerator/Test.generated.cs L110 C81 -The type or namespace name 'DateOnly' could not be found (are you missing a using directive or an assembly reference?) - -Error CS0246 Dapper.AOT.Analyzers/Dapper.CodeAnalysis.DapperInterceptorGenerator/Test.generated.cs L110 C107 -The type or namespace name 'DateOnly' could not be found (are you missing a using directive or an assembly reference?) - -Error CS1031 Dapper.AOT.Analyzers/Dapper.CodeAnalysis.DapperInterceptorGenerator/Test.generated.cs L128 C79 -Type expected - -Error CS1031 Dapper.AOT.Analyzers/Dapper.CodeAnalysis.DapperInterceptorGenerator/Test.generated.cs L140 C79 -Type expected diff --git a/test/Dapper.AOT.Test/Interceptors/DbString.output.cs b/test/Dapper.AOT.Test/Interceptors/DbString.output.cs index a935249..7eafd3d 100644 --- a/test/Dapper.AOT.Test/Interceptors/DbString.output.cs +++ b/test/Dapper.AOT.Test/Interceptors/DbString.output.cs @@ -62,7 +62,9 @@ private sealed class RowFactory0 : global::Dapper.RowFactory tokens, int columnOffset) { - for (int i = 0; i < tokens.Length; i++) + int availableColumns = reader.FieldCount - columnOffset; + int tokenCount = global::System.Math.Min(tokens.Length, availableColumns); + for (int i = 0; i < tokenCount; i++) { int token = -1; var name = reader.GetName(columnOffset); @@ -84,13 +86,21 @@ private RowFactory0() {} columnOffset++; } - return null; + // Initialize remaining tokens to -1 (unmapped) + for (int i = tokenCount; i < tokens.Length; i++) + { + tokens[i] = -1; + } + return tokenCount; } public override global::Foo.Product Read(global::System.Data.Common.DbDataReader reader, global::System.ReadOnlySpan tokens, int columnOffset, object? state) { + if (columnOffset > 0 && reader.IsDBNull(columnOffset)) return default!; global::Foo.Product result = new(); - foreach (var token in tokens) + int tokenCount = state is int count ? count : tokens.Length; + for (int i = 0; i < tokenCount; i++) { + var token = tokens[i]; switch (token) { case 0: diff --git a/test/Dapper.AOT.Test/Interceptors/DbString.output.netfx.cs b/test/Dapper.AOT.Test/Interceptors/DbString.output.netfx.cs index a935249..7eafd3d 100644 --- a/test/Dapper.AOT.Test/Interceptors/DbString.output.netfx.cs +++ b/test/Dapper.AOT.Test/Interceptors/DbString.output.netfx.cs @@ -62,7 +62,9 @@ private sealed class RowFactory0 : global::Dapper.RowFactory tokens, int columnOffset) { - for (int i = 0; i < tokens.Length; i++) + int availableColumns = reader.FieldCount - columnOffset; + int tokenCount = global::System.Math.Min(tokens.Length, availableColumns); + for (int i = 0; i < tokenCount; i++) { int token = -1; var name = reader.GetName(columnOffset); @@ -84,13 +86,21 @@ private RowFactory0() {} columnOffset++; } - return null; + // Initialize remaining tokens to -1 (unmapped) + for (int i = tokenCount; i < tokens.Length; i++) + { + tokens[i] = -1; + } + return tokenCount; } public override global::Foo.Product Read(global::System.Data.Common.DbDataReader reader, global::System.ReadOnlySpan tokens, int columnOffset, object? state) { + if (columnOffset > 0 && reader.IsDBNull(columnOffset)) return default!; global::Foo.Product result = new(); - foreach (var token in tokens) + int tokenCount = state is int count ? count : tokens.Length; + for (int i = 0; i < tokenCount; i++) { + var token = tokens[i]; switch (token) { case 0: diff --git a/test/Dapper.AOT.Test/Interceptors/GetRowParser.output.cs b/test/Dapper.AOT.Test/Interceptors/GetRowParser.output.cs index ae7dcb1..750ac22 100644 --- a/test/Dapper.AOT.Test/Interceptors/GetRowParser.output.cs +++ b/test/Dapper.AOT.Test/Interceptors/GetRowParser.output.cs @@ -52,7 +52,9 @@ private sealed class RowFactory0 : global::Dapper.RowFactory private RowFactory0() {} public override object? Tokenize(global::System.Data.Common.DbDataReader reader, global::System.Span tokens, int columnOffset) { - for (int i = 0; i < tokens.Length; i++) + int availableColumns = reader.FieldCount - columnOffset; + int tokenCount = global::System.Math.Min(tokens.Length, availableColumns); + for (int i = 0; i < tokenCount; i++) { int token = -1; var name = reader.GetName(columnOffset); @@ -71,13 +73,21 @@ private RowFactory0() {} columnOffset++; } - return null; + // Initialize remaining tokens to -1 (unmapped) + for (int i = tokenCount; i < tokens.Length; i++) + { + tokens[i] = -1; + } + return tokenCount; } public override global::HazNameId Read(global::System.Data.Common.DbDataReader reader, global::System.ReadOnlySpan tokens, int columnOffset, object? state) { + if (columnOffset > 0 && reader.IsDBNull(columnOffset)) return default!; global::HazNameId result = new(); - foreach (var token in tokens) + int tokenCount = state is int count ? count : tokens.Length; + for (int i = 0; i < tokenCount; i++) { + var token = tokens[i]; switch (token) { case 0: diff --git a/test/Dapper.AOT.Test/Interceptors/GetRowParser.output.netfx.cs b/test/Dapper.AOT.Test/Interceptors/GetRowParser.output.netfx.cs index ae7dcb1..750ac22 100644 --- a/test/Dapper.AOT.Test/Interceptors/GetRowParser.output.netfx.cs +++ b/test/Dapper.AOT.Test/Interceptors/GetRowParser.output.netfx.cs @@ -52,7 +52,9 @@ private sealed class RowFactory0 : global::Dapper.RowFactory private RowFactory0() {} public override object? Tokenize(global::System.Data.Common.DbDataReader reader, global::System.Span tokens, int columnOffset) { - for (int i = 0; i < tokens.Length; i++) + int availableColumns = reader.FieldCount - columnOffset; + int tokenCount = global::System.Math.Min(tokens.Length, availableColumns); + for (int i = 0; i < tokenCount; i++) { int token = -1; var name = reader.GetName(columnOffset); @@ -71,13 +73,21 @@ private RowFactory0() {} columnOffset++; } - return null; + // Initialize remaining tokens to -1 (unmapped) + for (int i = tokenCount; i < tokens.Length; i++) + { + tokens[i] = -1; + } + return tokenCount; } public override global::HazNameId Read(global::System.Data.Common.DbDataReader reader, global::System.ReadOnlySpan tokens, int columnOffset, object? state) { + if (columnOffset > 0 && reader.IsDBNull(columnOffset)) return default!; global::HazNameId result = new(); - foreach (var token in tokens) + int tokenCount = state is int count ? count : tokens.Length; + for (int i = 0; i < tokenCount; i++) { + var token = tokens[i]; switch (token) { case 0: diff --git a/test/Dapper.AOT.Test/Interceptors/GlobalFetchSize.output.cs b/test/Dapper.AOT.Test/Interceptors/GlobalFetchSize.output.cs index f3df746..c88c902 100644 --- a/test/Dapper.AOT.Test/Interceptors/GlobalFetchSize.output.cs +++ b/test/Dapper.AOT.Test/Interceptors/GlobalFetchSize.output.cs @@ -46,7 +46,9 @@ private sealed class RowFactory0 : global::Dapper.RowFactory tokens, int columnOffset) { - for (int i = 0; i < tokens.Length; i++) + int availableColumns = reader.FieldCount - columnOffset; + int tokenCount = global::System.Math.Min(tokens.Length, availableColumns); + for (int i = 0; i < tokenCount; i++) { int token = -1; var name = reader.GetName(columnOffset); @@ -62,13 +64,21 @@ private RowFactory0() {} columnOffset++; } - return null; + // Initialize remaining tokens to -1 (unmapped) + for (int i = tokenCount; i < tokens.Length; i++) + { + tokens[i] = -1; + } + return tokenCount; } public override global::SomeApp.SomeQueryType Read(global::System.Data.Common.DbDataReader reader, global::System.ReadOnlySpan tokens, int columnOffset, object? state) { + if (columnOffset > 0 && reader.IsDBNull(columnOffset)) return default!; global::SomeApp.SomeQueryType result = new(); - foreach (var token in tokens) + int tokenCount = state is int count ? count : tokens.Length; + for (int i = 0; i < tokenCount; i++) { + var token = tokens[i]; switch (token) { case 0: diff --git a/test/Dapper.AOT.Test/Interceptors/GlobalFetchSize.output.netfx.cs b/test/Dapper.AOT.Test/Interceptors/GlobalFetchSize.output.netfx.cs index f3df746..c88c902 100644 --- a/test/Dapper.AOT.Test/Interceptors/GlobalFetchSize.output.netfx.cs +++ b/test/Dapper.AOT.Test/Interceptors/GlobalFetchSize.output.netfx.cs @@ -46,7 +46,9 @@ private sealed class RowFactory0 : global::Dapper.RowFactory tokens, int columnOffset) { - for (int i = 0; i < tokens.Length; i++) + int availableColumns = reader.FieldCount - columnOffset; + int tokenCount = global::System.Math.Min(tokens.Length, availableColumns); + for (int i = 0; i < tokenCount; i++) { int token = -1; var name = reader.GetName(columnOffset); @@ -62,13 +64,21 @@ private RowFactory0() {} columnOffset++; } - return null; + // Initialize remaining tokens to -1 (unmapped) + for (int i = tokenCount; i < tokens.Length; i++) + { + tokens[i] = -1; + } + return tokenCount; } public override global::SomeApp.SomeQueryType Read(global::System.Data.Common.DbDataReader reader, global::System.ReadOnlySpan tokens, int columnOffset, object? state) { + if (columnOffset > 0 && reader.IsDBNull(columnOffset)) return default!; global::SomeApp.SomeQueryType result = new(); - foreach (var token in tokens) + int tokenCount = state is int count ? count : tokens.Length; + for (int i = 0; i < tokenCount; i++) { + var token = tokens[i]; switch (token) { case 0: diff --git a/test/Dapper.AOT.Test/Interceptors/InheritedMembers.output.cs b/test/Dapper.AOT.Test/Interceptors/InheritedMembers.output.cs index 06c4243..3db150a 100644 --- a/test/Dapper.AOT.Test/Interceptors/InheritedMembers.output.cs +++ b/test/Dapper.AOT.Test/Interceptors/InheritedMembers.output.cs @@ -59,7 +59,9 @@ private sealed class RowFactory0 : global::Dapper.RowFactory private RowFactory0() {} public override object? Tokenize(global::System.Data.Common.DbDataReader reader, global::System.Span tokens, int columnOffset) { - for (int i = 0; i < tokens.Length; i++) + int availableColumns = reader.FieldCount - columnOffset; + int tokenCount = global::System.Math.Min(tokens.Length, availableColumns); + for (int i = 0; i < tokenCount; i++) { int token = -1; var name = reader.GetName(columnOffset); @@ -78,13 +80,21 @@ private RowFactory0() {} columnOffset++; } - return null; + // Initialize remaining tokens to -1 (unmapped) + for (int i = tokenCount; i < tokens.Length; i++) + { + tokens[i] = -1; + } + return tokenCount; } public override global::Entity1 Read(global::System.Data.Common.DbDataReader reader, global::System.ReadOnlySpan tokens, int columnOffset, object? state) { + if (columnOffset > 0 && reader.IsDBNull(columnOffset)) return default!; global::Entity1 result = new(); - foreach (var token in tokens) + int tokenCount = state is int count ? count : tokens.Length; + for (int i = 0; i < tokenCount; i++) { + var token = tokens[i]; switch (token) { case 0: diff --git a/test/Dapper.AOT.Test/Interceptors/InheritedMembers.output.netfx.cs b/test/Dapper.AOT.Test/Interceptors/InheritedMembers.output.netfx.cs index 06c4243..3db150a 100644 --- a/test/Dapper.AOT.Test/Interceptors/InheritedMembers.output.netfx.cs +++ b/test/Dapper.AOT.Test/Interceptors/InheritedMembers.output.netfx.cs @@ -59,7 +59,9 @@ private sealed class RowFactory0 : global::Dapper.RowFactory private RowFactory0() {} public override object? Tokenize(global::System.Data.Common.DbDataReader reader, global::System.Span tokens, int columnOffset) { - for (int i = 0; i < tokens.Length; i++) + int availableColumns = reader.FieldCount - columnOffset; + int tokenCount = global::System.Math.Min(tokens.Length, availableColumns); + for (int i = 0; i < tokenCount; i++) { int token = -1; var name = reader.GetName(columnOffset); @@ -78,13 +80,21 @@ private RowFactory0() {} columnOffset++; } - return null; + // Initialize remaining tokens to -1 (unmapped) + for (int i = tokenCount; i < tokens.Length; i++) + { + tokens[i] = -1; + } + return tokenCount; } public override global::Entity1 Read(global::System.Data.Common.DbDataReader reader, global::System.ReadOnlySpan tokens, int columnOffset, object? state) { + if (columnOffset > 0 && reader.IsDBNull(columnOffset)) return default!; global::Entity1 result = new(); - foreach (var token in tokens) + int tokenCount = state is int count ? count : tokens.Length; + for (int i = 0; i < tokenCount; i++) { + var token = tokens[i]; switch (token) { case 0: diff --git a/test/Dapper.AOT.Test/Interceptors/MiscDiagnostics.output.cs b/test/Dapper.AOT.Test/Interceptors/MiscDiagnostics.output.cs index 7585f73..9f7e990 100644 --- a/test/Dapper.AOT.Test/Interceptors/MiscDiagnostics.output.cs +++ b/test/Dapper.AOT.Test/Interceptors/MiscDiagnostics.output.cs @@ -135,7 +135,9 @@ private sealed class RowFactory0 : global::Dapper.RowFactory tokens, int columnOffset) { - for (int i = 0; i < tokens.Length; i++) + int availableColumns = reader.FieldCount - columnOffset; + int tokenCount = global::System.Math.Min(tokens.Length, availableColumns); + for (int i = 0; i < tokenCount; i++) { int token = -1; var name = reader.GetName(columnOffset); @@ -151,13 +153,21 @@ private RowFactory0() {} columnOffset++; } - return null; + // Initialize remaining tokens to -1 (unmapped) + for (int i = tokenCount; i < tokens.Length; i++) + { + tokens[i] = -1; + } + return tokenCount; } public override global::SomeCode.InternalNesting.SomePublicType Read(global::System.Data.Common.DbDataReader reader, global::System.ReadOnlySpan tokens, int columnOffset, object? state) { + if (columnOffset > 0 && reader.IsDBNull(columnOffset)) return default!; global::SomeCode.InternalNesting.SomePublicType result = new(); - foreach (var token in tokens) + int tokenCount = state is int count ? count : tokens.Length; + for (int i = 0; i < tokenCount; i++) { + var token = tokens[i]; switch (token) { case 0: @@ -183,7 +193,9 @@ private sealed class RowFactory1 : global::Dapper.RowFactory tokens, int columnOffset) { - for (int i = 0; i < tokens.Length; i++) + int availableColumns = reader.FieldCount - columnOffset; + int tokenCount = global::System.Math.Min(tokens.Length, availableColumns); + for (int i = 0; i < tokenCount; i++) { int token = -1; var name = reader.GetName(columnOffset); @@ -199,13 +211,21 @@ private RowFactory1() {} columnOffset++; } - return null; + // Initialize remaining tokens to -1 (unmapped) + for (int i = tokenCount; i < tokens.Length; i++) + { + tokens[i] = -1; + } + return tokenCount; } public override global::SomeCode.InternalNesting.SomeInternalType Read(global::System.Data.Common.DbDataReader reader, global::System.ReadOnlySpan tokens, int columnOffset, object? state) { + if (columnOffset > 0 && reader.IsDBNull(columnOffset)) return default!; global::SomeCode.InternalNesting.SomeInternalType result = new(); - foreach (var token in tokens) + int tokenCount = state is int count ? count : tokens.Length; + for (int i = 0; i < tokenCount; i++) { + var token = tokens[i]; switch (token) { case 0: @@ -231,7 +251,9 @@ private sealed class RowFactory2 : global::Dapper.RowFactory tokens, int columnOffset) { - for (int i = 0; i < tokens.Length; i++) + int availableColumns = reader.FieldCount - columnOffset; + int tokenCount = global::System.Math.Min(tokens.Length, availableColumns); + for (int i = 0; i < tokenCount; i++) { int token = -1; var name = reader.GetName(columnOffset); @@ -247,13 +269,21 @@ private RowFactory2() {} columnOffset++; } - return null; + // Initialize remaining tokens to -1 (unmapped) + for (int i = tokenCount; i < tokens.Length; i++) + { + tokens[i] = -1; + } + return tokenCount; } public override global::SomeCode.InternalNesting.SomeProtectedInternalType Read(global::System.Data.Common.DbDataReader reader, global::System.ReadOnlySpan tokens, int columnOffset, object? state) { + if (columnOffset > 0 && reader.IsDBNull(columnOffset)) return default!; global::SomeCode.InternalNesting.SomeProtectedInternalType result = new(); - foreach (var token in tokens) + int tokenCount = state is int count ? count : tokens.Length; + for (int i = 0; i < tokenCount; i++) { + var token = tokens[i]; switch (token) { case 0: diff --git a/test/Dapper.AOT.Test/Interceptors/MiscDiagnostics.output.netfx.cs b/test/Dapper.AOT.Test/Interceptors/MiscDiagnostics.output.netfx.cs index 7585f73..9f7e990 100644 --- a/test/Dapper.AOT.Test/Interceptors/MiscDiagnostics.output.netfx.cs +++ b/test/Dapper.AOT.Test/Interceptors/MiscDiagnostics.output.netfx.cs @@ -135,7 +135,9 @@ private sealed class RowFactory0 : global::Dapper.RowFactory tokens, int columnOffset) { - for (int i = 0; i < tokens.Length; i++) + int availableColumns = reader.FieldCount - columnOffset; + int tokenCount = global::System.Math.Min(tokens.Length, availableColumns); + for (int i = 0; i < tokenCount; i++) { int token = -1; var name = reader.GetName(columnOffset); @@ -151,13 +153,21 @@ private RowFactory0() {} columnOffset++; } - return null; + // Initialize remaining tokens to -1 (unmapped) + for (int i = tokenCount; i < tokens.Length; i++) + { + tokens[i] = -1; + } + return tokenCount; } public override global::SomeCode.InternalNesting.SomePublicType Read(global::System.Data.Common.DbDataReader reader, global::System.ReadOnlySpan tokens, int columnOffset, object? state) { + if (columnOffset > 0 && reader.IsDBNull(columnOffset)) return default!; global::SomeCode.InternalNesting.SomePublicType result = new(); - foreach (var token in tokens) + int tokenCount = state is int count ? count : tokens.Length; + for (int i = 0; i < tokenCount; i++) { + var token = tokens[i]; switch (token) { case 0: @@ -183,7 +193,9 @@ private sealed class RowFactory1 : global::Dapper.RowFactory tokens, int columnOffset) { - for (int i = 0; i < tokens.Length; i++) + int availableColumns = reader.FieldCount - columnOffset; + int tokenCount = global::System.Math.Min(tokens.Length, availableColumns); + for (int i = 0; i < tokenCount; i++) { int token = -1; var name = reader.GetName(columnOffset); @@ -199,13 +211,21 @@ private RowFactory1() {} columnOffset++; } - return null; + // Initialize remaining tokens to -1 (unmapped) + for (int i = tokenCount; i < tokens.Length; i++) + { + tokens[i] = -1; + } + return tokenCount; } public override global::SomeCode.InternalNesting.SomeInternalType Read(global::System.Data.Common.DbDataReader reader, global::System.ReadOnlySpan tokens, int columnOffset, object? state) { + if (columnOffset > 0 && reader.IsDBNull(columnOffset)) return default!; global::SomeCode.InternalNesting.SomeInternalType result = new(); - foreach (var token in tokens) + int tokenCount = state is int count ? count : tokens.Length; + for (int i = 0; i < tokenCount; i++) { + var token = tokens[i]; switch (token) { case 0: @@ -231,7 +251,9 @@ private sealed class RowFactory2 : global::Dapper.RowFactory tokens, int columnOffset) { - for (int i = 0; i < tokens.Length; i++) + int availableColumns = reader.FieldCount - columnOffset; + int tokenCount = global::System.Math.Min(tokens.Length, availableColumns); + for (int i = 0; i < tokenCount; i++) { int token = -1; var name = reader.GetName(columnOffset); @@ -247,13 +269,21 @@ private RowFactory2() {} columnOffset++; } - return null; + // Initialize remaining tokens to -1 (unmapped) + for (int i = tokenCount; i < tokens.Length; i++) + { + tokens[i] = -1; + } + return tokenCount; } public override global::SomeCode.InternalNesting.SomeProtectedInternalType Read(global::System.Data.Common.DbDataReader reader, global::System.ReadOnlySpan tokens, int columnOffset, object? state) { + if (columnOffset > 0 && reader.IsDBNull(columnOffset)) return default!; global::SomeCode.InternalNesting.SomeProtectedInternalType result = new(); - foreach (var token in tokens) + int tokenCount = state is int count ? count : tokens.Length; + for (int i = 0; i < tokenCount; i++) { + var token = tokens[i]; switch (token) { case 0: diff --git a/test/Dapper.AOT.Test/Interceptors/NonFactoryMethod.output.cs b/test/Dapper.AOT.Test/Interceptors/NonFactoryMethod.output.cs index cdc7f27..043a4a4 100644 --- a/test/Dapper.AOT.Test/Interceptors/NonFactoryMethod.output.cs +++ b/test/Dapper.AOT.Test/Interceptors/NonFactoryMethod.output.cs @@ -59,7 +59,9 @@ private sealed class RowFactory0 : global::Dapper.RowFactory tokens, int columnOffset) { - for (int i = 0; i < tokens.Length; i++) + int availableColumns = reader.FieldCount - columnOffset; + int tokenCount = global::System.Math.Min(tokens.Length, availableColumns); + for (int i = 0; i < tokenCount; i++) { int token = -1; var name = reader.GetName(columnOffset); @@ -147,13 +149,21 @@ private RowFactory0() {} columnOffset++; } - return null; + // Initialize remaining tokens to -1 (unmapped) + for (int i = tokenCount; i < tokens.Length; i++) + { + tokens[i] = -1; + } + return tokenCount; } public override global::UsageLinker.Product Read(global::System.Data.Common.DbDataReader reader, global::System.ReadOnlySpan tokens, int columnOffset, object? state) { + if (columnOffset > 0 && reader.IsDBNull(columnOffset)) return default!; global::UsageLinker.Product result = new(); - foreach (var token in tokens) + int tokenCount = state is int count ? count : tokens.Length; + for (int i = 0; i < tokenCount; i++) { + var token = tokens[i]; switch (token) { case 0: diff --git a/test/Dapper.AOT.Test/Interceptors/NonFactoryMethod.output.netfx.cs b/test/Dapper.AOT.Test/Interceptors/NonFactoryMethod.output.netfx.cs index cdc7f27..043a4a4 100644 --- a/test/Dapper.AOT.Test/Interceptors/NonFactoryMethod.output.netfx.cs +++ b/test/Dapper.AOT.Test/Interceptors/NonFactoryMethod.output.netfx.cs @@ -59,7 +59,9 @@ private sealed class RowFactory0 : global::Dapper.RowFactory tokens, int columnOffset) { - for (int i = 0; i < tokens.Length; i++) + int availableColumns = reader.FieldCount - columnOffset; + int tokenCount = global::System.Math.Min(tokens.Length, availableColumns); + for (int i = 0; i < tokenCount; i++) { int token = -1; var name = reader.GetName(columnOffset); @@ -147,13 +149,21 @@ private RowFactory0() {} columnOffset++; } - return null; + // Initialize remaining tokens to -1 (unmapped) + for (int i = tokenCount; i < tokens.Length; i++) + { + tokens[i] = -1; + } + return tokenCount; } public override global::UsageLinker.Product Read(global::System.Data.Common.DbDataReader reader, global::System.ReadOnlySpan tokens, int columnOffset, object? state) { + if (columnOffset > 0 && reader.IsDBNull(columnOffset)) return default!; global::UsageLinker.Product result = new(); - foreach (var token in tokens) + int tokenCount = state is int count ? count : tokens.Length; + for (int i = 0; i < tokenCount; i++) { + var token = tokens[i]; switch (token) { case 0: diff --git a/test/Dapper.AOT.Test/Interceptors/Query.output.cs b/test/Dapper.AOT.Test/Interceptors/Query.output.cs index 9fec975..ed3f2e1 100644 --- a/test/Dapper.AOT.Test/Interceptors/Query.output.cs +++ b/test/Dapper.AOT.Test/Interceptors/Query.output.cs @@ -163,7 +163,9 @@ private sealed class RowFactory0 : global::Dapper.RowFactory tokens, int columnOffset) { - for (int i = 0; i < tokens.Length; i++) + int availableColumns = reader.FieldCount - columnOffset; + int tokenCount = global::System.Math.Min(tokens.Length, availableColumns); + for (int i = 0; i < tokenCount; i++) { int token = -1; var name = reader.GetName(columnOffset); @@ -185,13 +187,21 @@ private RowFactory0() {} columnOffset++; } - return null; + // Initialize remaining tokens to -1 (unmapped) + for (int i = tokenCount; i < tokens.Length; i++) + { + tokens[i] = -1; + } + return tokenCount; } public override global::Foo.Customer Read(global::System.Data.Common.DbDataReader reader, global::System.ReadOnlySpan tokens, int columnOffset, object? state) { + if (columnOffset > 0 && reader.IsDBNull(columnOffset)) return default!; global::Foo.Customer result = new(); - foreach (var token in tokens) + int tokenCount = state is int count ? count : tokens.Length; + for (int i = 0; i < tokenCount; i++) { + var token = tokens[i]; switch (token) { case 0: diff --git a/test/Dapper.AOT.Test/Interceptors/Query.output.netfx.cs b/test/Dapper.AOT.Test/Interceptors/Query.output.netfx.cs index f39237c..146e233 100644 --- a/test/Dapper.AOT.Test/Interceptors/Query.output.netfx.cs +++ b/test/Dapper.AOT.Test/Interceptors/Query.output.netfx.cs @@ -135,7 +135,9 @@ private sealed class RowFactory0 : global::Dapper.RowFactory tokens, int columnOffset) { - for (int i = 0; i < tokens.Length; i++) + int availableColumns = reader.FieldCount - columnOffset; + int tokenCount = global::System.Math.Min(tokens.Length, availableColumns); + for (int i = 0; i < tokenCount; i++) { int token = -1; var name = reader.GetName(columnOffset); @@ -157,13 +159,21 @@ private RowFactory0() {} columnOffset++; } - return null; + // Initialize remaining tokens to -1 (unmapped) + for (int i = tokenCount; i < tokens.Length; i++) + { + tokens[i] = -1; + } + return tokenCount; } public override global::Foo.Customer Read(global::System.Data.Common.DbDataReader reader, global::System.ReadOnlySpan tokens, int columnOffset, object? state) { + if (columnOffset > 0 && reader.IsDBNull(columnOffset)) return default!; global::Foo.Customer result = new(); - foreach (var token in tokens) + int tokenCount = state is int count ? count : tokens.Length; + for (int i = 0; i < tokenCount; i++) { + var token = tokens[i]; switch (token) { case 0: diff --git a/test/Dapper.AOT.Test/Interceptors/QueryCustomConstructionWithConstructor.output.cs b/test/Dapper.AOT.Test/Interceptors/QueryCustomConstructionWithConstructor.output.cs index df82c4d..e61953b 100644 --- a/test/Dapper.AOT.Test/Interceptors/QueryCustomConstructionWithConstructor.output.cs +++ b/test/Dapper.AOT.Test/Interceptors/QueryCustomConstructionWithConstructor.output.cs @@ -198,7 +198,9 @@ private sealed class RowFactory0 : global::Dapper.RowFactory tokens, int columnOffset) { - for (int i = 0; i < tokens.Length; i++) + int availableColumns = reader.FieldCount - columnOffset; + int tokenCount = global::System.Math.Min(tokens.Length, availableColumns); + for (int i = 0; i < tokenCount; i++) { int token = -1; var name = reader.GetName(columnOffset); @@ -220,13 +222,21 @@ private RowFactory0() {} columnOffset++; } - return null; + // Initialize remaining tokens to -1 (unmapped) + for (int i = tokenCount; i < tokens.Length; i++) + { + tokens[i] = -1; + } + return tokenCount; } public override global::Foo.ParameterlessCtor Read(global::System.Data.Common.DbDataReader reader, global::System.ReadOnlySpan tokens, int columnOffset, object? state) { + if (columnOffset > 0 && reader.IsDBNull(columnOffset)) return default!; global::Foo.ParameterlessCtor result = new(); - foreach (var token in tokens) + int tokenCount = state is int count ? count : tokens.Length; + for (int i = 0; i < tokenCount; i++) { + var token = tokens[i]; switch (token) { case 0: @@ -264,7 +274,9 @@ private sealed class RowFactory1 : global::Dapper.RowFactory tokens, int columnOffset) { - for (int i = 0; i < tokens.Length; i++) + int availableColumns = reader.FieldCount - columnOffset; + int tokenCount = global::System.Math.Min(tokens.Length, availableColumns); + for (int i = 0; i < tokenCount; i++) { int token = -1; var name = reader.GetName(columnOffset); @@ -286,15 +298,23 @@ private RowFactory1() {} columnOffset++; } - return null; + // Initialize remaining tokens to -1 (unmapped) + for (int i = tokenCount; i < tokens.Length; i++) + { + tokens[i] = -1; + } + return tokenCount; } public override global::Foo.GetOnlyPropertiesViaConstructor Read(global::System.Data.Common.DbDataReader reader, global::System.ReadOnlySpan tokens, int columnOffset, object? state) { + if (columnOffset > 0 && reader.IsDBNull(columnOffset)) return default!; int value0 = default; string? value1 = default; double? value2 = default; - foreach (var token in tokens) + int tokenCount = state is int count ? count : tokens.Length; + for (int i = 0; i < tokenCount; i++) { + var token = tokens[i]; switch (token) { case 0: @@ -330,7 +350,9 @@ private sealed class RowFactory2 : global::Dapper.RowFactory tokens, int columnOffset) { - for (int i = 0; i < tokens.Length; i++) + int availableColumns = reader.FieldCount - columnOffset; + int tokenCount = global::System.Math.Min(tokens.Length, availableColumns); + for (int i = 0; i < tokenCount; i++) { int token = -1; var name = reader.GetName(columnOffset); @@ -352,13 +374,21 @@ private RowFactory2() {} columnOffset++; } - return null; + // Initialize remaining tokens to -1 (unmapped) + for (int i = tokenCount; i < tokens.Length; i++) + { + tokens[i] = -1; + } + return tokenCount; } public override global::Foo.RecordClass Read(global::System.Data.Common.DbDataReader reader, global::System.ReadOnlySpan tokens, int columnOffset, object? state) { + if (columnOffset > 0 && reader.IsDBNull(columnOffset)) return default!; global::Foo.RecordClass result = new(); - foreach (var token in tokens) + int tokenCount = state is int count ? count : tokens.Length; + for (int i = 0; i < tokenCount; i++) { + var token = tokens[i]; switch (token) { case 0: @@ -396,7 +426,9 @@ private sealed class RowFactory3 : global::Dapper.RowFactory tokens, int columnOffset) { - for (int i = 0; i < tokens.Length; i++) + int availableColumns = reader.FieldCount - columnOffset; + int tokenCount = global::System.Math.Min(tokens.Length, availableColumns); + for (int i = 0; i < tokenCount; i++) { int token = -1; var name = reader.GetName(columnOffset); @@ -418,15 +450,23 @@ private RowFactory3() {} columnOffset++; } - return null; + // Initialize remaining tokens to -1 (unmapped) + for (int i = tokenCount; i < tokens.Length; i++) + { + tokens[i] = -1; + } + return tokenCount; } public override global::Foo.RecordClassSimpleCtor Read(global::System.Data.Common.DbDataReader reader, global::System.ReadOnlySpan tokens, int columnOffset, object? state) { + if (columnOffset > 0 && reader.IsDBNull(columnOffset)) return default!; int value0 = default; string? value1 = default; double? value2 = default; - foreach (var token in tokens) + int tokenCount = state is int count ? count : tokens.Length; + for (int i = 0; i < tokenCount; i++) { + var token = tokens[i]; switch (token) { case 0: @@ -462,7 +502,9 @@ private sealed class RowFactory4 : global::Dapper.RowFactory tokens, int columnOffset) { - for (int i = 0; i < tokens.Length; i++) + int availableColumns = reader.FieldCount - columnOffset; + int tokenCount = global::System.Math.Min(tokens.Length, availableColumns); + for (int i = 0; i < tokenCount; i++) { int token = -1; var name = reader.GetName(columnOffset); @@ -484,13 +526,21 @@ private RowFactory4() {} columnOffset++; } - return null; + // Initialize remaining tokens to -1 (unmapped) + for (int i = tokenCount; i < tokens.Length; i++) + { + tokens[i] = -1; + } + return tokenCount; } public override global::Foo.RecordStruct Read(global::System.Data.Common.DbDataReader reader, global::System.ReadOnlySpan tokens, int columnOffset, object? state) { + if (columnOffset > 0 && reader.IsDBNull(columnOffset)) return default!; global::Foo.RecordStruct result = new(); - foreach (var token in tokens) + int tokenCount = state is int count ? count : tokens.Length; + for (int i = 0; i < tokenCount; i++) { + var token = tokens[i]; switch (token) { case 0: @@ -528,7 +578,9 @@ private sealed class RowFactory5 : global::Dapper.RowFactory tokens, int columnOffset) { - for (int i = 0; i < tokens.Length; i++) + int availableColumns = reader.FieldCount - columnOffset; + int tokenCount = global::System.Math.Min(tokens.Length, availableColumns); + for (int i = 0; i < tokenCount; i++) { int token = -1; var name = reader.GetName(columnOffset); @@ -550,15 +602,23 @@ private RowFactory5() {} columnOffset++; } - return null; + // Initialize remaining tokens to -1 (unmapped) + for (int i = tokenCount; i < tokens.Length; i++) + { + tokens[i] = -1; + } + return tokenCount; } public override global::Foo.RecordStructSimpleCtor Read(global::System.Data.Common.DbDataReader reader, global::System.ReadOnlySpan tokens, int columnOffset, object? state) { + if (columnOffset > 0 && reader.IsDBNull(columnOffset)) return default!; int value0 = default; string? value1 = default; double? value2 = default; - foreach (var token in tokens) + int tokenCount = state is int count ? count : tokens.Length; + for (int i = 0; i < tokenCount; i++) { + var token = tokens[i]; switch (token) { case 0: @@ -594,7 +654,9 @@ private sealed class RowFactory6 : global::Dapper.RowFactory tokens, int columnOffset) { - for (int i = 0; i < tokens.Length; i++) + int availableColumns = reader.FieldCount - columnOffset; + int tokenCount = global::System.Math.Min(tokens.Length, availableColumns); + for (int i = 0; i < tokenCount; i++) { int token = -1; var name = reader.GetName(columnOffset); @@ -616,15 +678,23 @@ private RowFactory6() {} columnOffset++; } - return null; + // Initialize remaining tokens to -1 (unmapped) + for (int i = tokenCount; i < tokens.Length; i++) + { + tokens[i] = -1; + } + return tokenCount; } public override global::Foo.InitPropsOnly Read(global::System.Data.Common.DbDataReader reader, global::System.ReadOnlySpan tokens, int columnOffset, object? state) { + if (columnOffset > 0 && reader.IsDBNull(columnOffset)) return default!; int value0 = default; string? value1 = default; double? value2 = default; - foreach (var token in tokens) + int tokenCount = state is int count ? count : tokens.Length; + for (int i = 0; i < tokenCount; i++) { + var token = tokens[i]; switch (token) { case 0: @@ -665,7 +735,9 @@ private sealed class RowFactory7 : global::Dapper.RowFactory tokens, int columnOffset) { - for (int i = 0; i < tokens.Length; i++) + int availableColumns = reader.FieldCount - columnOffset; + int tokenCount = global::System.Math.Min(tokens.Length, availableColumns); + for (int i = 0; i < tokenCount; i++) { int token = -1; var name = reader.GetName(columnOffset); @@ -687,15 +759,23 @@ private RowFactory7() {} columnOffset++; } - return null; + // Initialize remaining tokens to -1 (unmapped) + for (int i = tokenCount; i < tokens.Length; i++) + { + tokens[i] = -1; + } + return tokenCount; } public override global::Foo.InitPropsAndDapperAotCtor Read(global::System.Data.Common.DbDataReader reader, global::System.ReadOnlySpan tokens, int columnOffset, object? state) { + if (columnOffset > 0 && reader.IsDBNull(columnOffset)) return default!; int value0 = default; string? value1 = default; double? value2 = default; - foreach (var token in tokens) + int tokenCount = state is int count ? count : tokens.Length; + for (int i = 0; i < tokenCount; i++) { + var token = tokens[i]; switch (token) { case 0: @@ -735,7 +815,9 @@ private sealed class RowFactory8 : global::Dapper.RowFactory tokens, int columnOffset) { - for (int i = 0; i < tokens.Length; i++) + int availableColumns = reader.FieldCount - columnOffset; + int tokenCount = global::System.Math.Min(tokens.Length, availableColumns); + for (int i = 0; i < tokenCount; i++) { int token = -1; var name = reader.GetName(columnOffset); @@ -757,13 +839,21 @@ private RowFactory8() {} columnOffset++; } - return null; + // Initialize remaining tokens to -1 (unmapped) + for (int i = tokenCount; i < tokens.Length; i++) + { + tokens[i] = -1; + } + return tokenCount; } public override global::Foo.OnlyNonDapperAotCtor Read(global::System.Data.Common.DbDataReader reader, global::System.ReadOnlySpan tokens, int columnOffset, object? state) { + if (columnOffset > 0 && reader.IsDBNull(columnOffset)) return default!; global::Foo.OnlyNonDapperAotCtor result = new(); - foreach (var token in tokens) + int tokenCount = state is int count ? count : tokens.Length; + for (int i = 0; i < tokenCount; i++) { + var token = tokens[i]; switch (token) { case 0: @@ -801,7 +891,9 @@ private sealed class RowFactory9 : global::Dapper.RowFactory tokens, int columnOffset) { - for (int i = 0; i < tokens.Length; i++) + int availableColumns = reader.FieldCount - columnOffset; + int tokenCount = global::System.Math.Min(tokens.Length, availableColumns); + for (int i = 0; i < tokenCount; i++) { int token = -1; var name = reader.GetName(columnOffset); @@ -823,13 +915,21 @@ private RowFactory9() {} columnOffset++; } - return null; + // Initialize remaining tokens to -1 (unmapped) + for (int i = tokenCount; i < tokens.Length; i++) + { + tokens[i] = -1; + } + return tokenCount; } public override global::Foo.SingleDefaultCtor Read(global::System.Data.Common.DbDataReader reader, global::System.ReadOnlySpan tokens, int columnOffset, object? state) { + if (columnOffset > 0 && reader.IsDBNull(columnOffset)) return default!; global::Foo.SingleDefaultCtor result = new(); - foreach (var token in tokens) + int tokenCount = state is int count ? count : tokens.Length; + for (int i = 0; i < tokenCount; i++) { + var token = tokens[i]; switch (token) { case 0: @@ -867,7 +967,9 @@ private sealed class RowFactory10 : global::Dapper.RowFactory tokens, int columnOffset) { - for (int i = 0; i < tokens.Length; i++) + int availableColumns = reader.FieldCount - columnOffset; + int tokenCount = global::System.Math.Min(tokens.Length, availableColumns); + for (int i = 0; i < tokenCount; i++) { int token = -1; var name = reader.GetName(columnOffset); @@ -889,13 +991,21 @@ private RowFactory10() {} columnOffset++; } - return null; + // Initialize remaining tokens to -1 (unmapped) + for (int i = tokenCount; i < tokens.Length; i++) + { + tokens[i] = -1; + } + return tokenCount; } public override global::Foo.MultipleDapperAotCtors Read(global::System.Data.Common.DbDataReader reader, global::System.ReadOnlySpan tokens, int columnOffset, object? state) { + if (columnOffset > 0 && reader.IsDBNull(columnOffset)) return default!; global::Foo.MultipleDapperAotCtors result = new(); - foreach (var token in tokens) + int tokenCount = state is int count ? count : tokens.Length; + for (int i = 0; i < tokenCount; i++) { + var token = tokens[i]; switch (token) { case 0: @@ -933,7 +1043,9 @@ private sealed class RowFactory11 : global::Dapper.RowFactory tokens, int columnOffset) { - for (int i = 0; i < tokens.Length; i++) + int availableColumns = reader.FieldCount - columnOffset; + int tokenCount = global::System.Math.Min(tokens.Length, availableColumns); + for (int i = 0; i < tokenCount; i++) { int token = -1; var name = reader.GetName(columnOffset); @@ -955,13 +1067,21 @@ private RowFactory11() {} columnOffset++; } - return null; + // Initialize remaining tokens to -1 (unmapped) + for (int i = tokenCount; i < tokens.Length; i++) + { + tokens[i] = -1; + } + return tokenCount; } public override global::Foo.SingleDapperAotCtor Read(global::System.Data.Common.DbDataReader reader, global::System.ReadOnlySpan tokens, int columnOffset, object? state) { + if (columnOffset > 0 && reader.IsDBNull(columnOffset)) return default!; global::Foo.SingleDapperAotCtor result = new(); - foreach (var token in tokens) + int tokenCount = state is int count ? count : tokens.Length; + for (int i = 0; i < tokenCount; i++) { + var token = tokens[i]; switch (token) { case 0: diff --git a/test/Dapper.AOT.Test/Interceptors/QueryCustomConstructionWithConstructor.output.netfx.cs b/test/Dapper.AOT.Test/Interceptors/QueryCustomConstructionWithConstructor.output.netfx.cs index df82c4d..e61953b 100644 --- a/test/Dapper.AOT.Test/Interceptors/QueryCustomConstructionWithConstructor.output.netfx.cs +++ b/test/Dapper.AOT.Test/Interceptors/QueryCustomConstructionWithConstructor.output.netfx.cs @@ -198,7 +198,9 @@ private sealed class RowFactory0 : global::Dapper.RowFactory tokens, int columnOffset) { - for (int i = 0; i < tokens.Length; i++) + int availableColumns = reader.FieldCount - columnOffset; + int tokenCount = global::System.Math.Min(tokens.Length, availableColumns); + for (int i = 0; i < tokenCount; i++) { int token = -1; var name = reader.GetName(columnOffset); @@ -220,13 +222,21 @@ private RowFactory0() {} columnOffset++; } - return null; + // Initialize remaining tokens to -1 (unmapped) + for (int i = tokenCount; i < tokens.Length; i++) + { + tokens[i] = -1; + } + return tokenCount; } public override global::Foo.ParameterlessCtor Read(global::System.Data.Common.DbDataReader reader, global::System.ReadOnlySpan tokens, int columnOffset, object? state) { + if (columnOffset > 0 && reader.IsDBNull(columnOffset)) return default!; global::Foo.ParameterlessCtor result = new(); - foreach (var token in tokens) + int tokenCount = state is int count ? count : tokens.Length; + for (int i = 0; i < tokenCount; i++) { + var token = tokens[i]; switch (token) { case 0: @@ -264,7 +274,9 @@ private sealed class RowFactory1 : global::Dapper.RowFactory tokens, int columnOffset) { - for (int i = 0; i < tokens.Length; i++) + int availableColumns = reader.FieldCount - columnOffset; + int tokenCount = global::System.Math.Min(tokens.Length, availableColumns); + for (int i = 0; i < tokenCount; i++) { int token = -1; var name = reader.GetName(columnOffset); @@ -286,15 +298,23 @@ private RowFactory1() {} columnOffset++; } - return null; + // Initialize remaining tokens to -1 (unmapped) + for (int i = tokenCount; i < tokens.Length; i++) + { + tokens[i] = -1; + } + return tokenCount; } public override global::Foo.GetOnlyPropertiesViaConstructor Read(global::System.Data.Common.DbDataReader reader, global::System.ReadOnlySpan tokens, int columnOffset, object? state) { + if (columnOffset > 0 && reader.IsDBNull(columnOffset)) return default!; int value0 = default; string? value1 = default; double? value2 = default; - foreach (var token in tokens) + int tokenCount = state is int count ? count : tokens.Length; + for (int i = 0; i < tokenCount; i++) { + var token = tokens[i]; switch (token) { case 0: @@ -330,7 +350,9 @@ private sealed class RowFactory2 : global::Dapper.RowFactory tokens, int columnOffset) { - for (int i = 0; i < tokens.Length; i++) + int availableColumns = reader.FieldCount - columnOffset; + int tokenCount = global::System.Math.Min(tokens.Length, availableColumns); + for (int i = 0; i < tokenCount; i++) { int token = -1; var name = reader.GetName(columnOffset); @@ -352,13 +374,21 @@ private RowFactory2() {} columnOffset++; } - return null; + // Initialize remaining tokens to -1 (unmapped) + for (int i = tokenCount; i < tokens.Length; i++) + { + tokens[i] = -1; + } + return tokenCount; } public override global::Foo.RecordClass Read(global::System.Data.Common.DbDataReader reader, global::System.ReadOnlySpan tokens, int columnOffset, object? state) { + if (columnOffset > 0 && reader.IsDBNull(columnOffset)) return default!; global::Foo.RecordClass result = new(); - foreach (var token in tokens) + int tokenCount = state is int count ? count : tokens.Length; + for (int i = 0; i < tokenCount; i++) { + var token = tokens[i]; switch (token) { case 0: @@ -396,7 +426,9 @@ private sealed class RowFactory3 : global::Dapper.RowFactory tokens, int columnOffset) { - for (int i = 0; i < tokens.Length; i++) + int availableColumns = reader.FieldCount - columnOffset; + int tokenCount = global::System.Math.Min(tokens.Length, availableColumns); + for (int i = 0; i < tokenCount; i++) { int token = -1; var name = reader.GetName(columnOffset); @@ -418,15 +450,23 @@ private RowFactory3() {} columnOffset++; } - return null; + // Initialize remaining tokens to -1 (unmapped) + for (int i = tokenCount; i < tokens.Length; i++) + { + tokens[i] = -1; + } + return tokenCount; } public override global::Foo.RecordClassSimpleCtor Read(global::System.Data.Common.DbDataReader reader, global::System.ReadOnlySpan tokens, int columnOffset, object? state) { + if (columnOffset > 0 && reader.IsDBNull(columnOffset)) return default!; int value0 = default; string? value1 = default; double? value2 = default; - foreach (var token in tokens) + int tokenCount = state is int count ? count : tokens.Length; + for (int i = 0; i < tokenCount; i++) { + var token = tokens[i]; switch (token) { case 0: @@ -462,7 +502,9 @@ private sealed class RowFactory4 : global::Dapper.RowFactory tokens, int columnOffset) { - for (int i = 0; i < tokens.Length; i++) + int availableColumns = reader.FieldCount - columnOffset; + int tokenCount = global::System.Math.Min(tokens.Length, availableColumns); + for (int i = 0; i < tokenCount; i++) { int token = -1; var name = reader.GetName(columnOffset); @@ -484,13 +526,21 @@ private RowFactory4() {} columnOffset++; } - return null; + // Initialize remaining tokens to -1 (unmapped) + for (int i = tokenCount; i < tokens.Length; i++) + { + tokens[i] = -1; + } + return tokenCount; } public override global::Foo.RecordStruct Read(global::System.Data.Common.DbDataReader reader, global::System.ReadOnlySpan tokens, int columnOffset, object? state) { + if (columnOffset > 0 && reader.IsDBNull(columnOffset)) return default!; global::Foo.RecordStruct result = new(); - foreach (var token in tokens) + int tokenCount = state is int count ? count : tokens.Length; + for (int i = 0; i < tokenCount; i++) { + var token = tokens[i]; switch (token) { case 0: @@ -528,7 +578,9 @@ private sealed class RowFactory5 : global::Dapper.RowFactory tokens, int columnOffset) { - for (int i = 0; i < tokens.Length; i++) + int availableColumns = reader.FieldCount - columnOffset; + int tokenCount = global::System.Math.Min(tokens.Length, availableColumns); + for (int i = 0; i < tokenCount; i++) { int token = -1; var name = reader.GetName(columnOffset); @@ -550,15 +602,23 @@ private RowFactory5() {} columnOffset++; } - return null; + // Initialize remaining tokens to -1 (unmapped) + for (int i = tokenCount; i < tokens.Length; i++) + { + tokens[i] = -1; + } + return tokenCount; } public override global::Foo.RecordStructSimpleCtor Read(global::System.Data.Common.DbDataReader reader, global::System.ReadOnlySpan tokens, int columnOffset, object? state) { + if (columnOffset > 0 && reader.IsDBNull(columnOffset)) return default!; int value0 = default; string? value1 = default; double? value2 = default; - foreach (var token in tokens) + int tokenCount = state is int count ? count : tokens.Length; + for (int i = 0; i < tokenCount; i++) { + var token = tokens[i]; switch (token) { case 0: @@ -594,7 +654,9 @@ private sealed class RowFactory6 : global::Dapper.RowFactory tokens, int columnOffset) { - for (int i = 0; i < tokens.Length; i++) + int availableColumns = reader.FieldCount - columnOffset; + int tokenCount = global::System.Math.Min(tokens.Length, availableColumns); + for (int i = 0; i < tokenCount; i++) { int token = -1; var name = reader.GetName(columnOffset); @@ -616,15 +678,23 @@ private RowFactory6() {} columnOffset++; } - return null; + // Initialize remaining tokens to -1 (unmapped) + for (int i = tokenCount; i < tokens.Length; i++) + { + tokens[i] = -1; + } + return tokenCount; } public override global::Foo.InitPropsOnly Read(global::System.Data.Common.DbDataReader reader, global::System.ReadOnlySpan tokens, int columnOffset, object? state) { + if (columnOffset > 0 && reader.IsDBNull(columnOffset)) return default!; int value0 = default; string? value1 = default; double? value2 = default; - foreach (var token in tokens) + int tokenCount = state is int count ? count : tokens.Length; + for (int i = 0; i < tokenCount; i++) { + var token = tokens[i]; switch (token) { case 0: @@ -665,7 +735,9 @@ private sealed class RowFactory7 : global::Dapper.RowFactory tokens, int columnOffset) { - for (int i = 0; i < tokens.Length; i++) + int availableColumns = reader.FieldCount - columnOffset; + int tokenCount = global::System.Math.Min(tokens.Length, availableColumns); + for (int i = 0; i < tokenCount; i++) { int token = -1; var name = reader.GetName(columnOffset); @@ -687,15 +759,23 @@ private RowFactory7() {} columnOffset++; } - return null; + // Initialize remaining tokens to -1 (unmapped) + for (int i = tokenCount; i < tokens.Length; i++) + { + tokens[i] = -1; + } + return tokenCount; } public override global::Foo.InitPropsAndDapperAotCtor Read(global::System.Data.Common.DbDataReader reader, global::System.ReadOnlySpan tokens, int columnOffset, object? state) { + if (columnOffset > 0 && reader.IsDBNull(columnOffset)) return default!; int value0 = default; string? value1 = default; double? value2 = default; - foreach (var token in tokens) + int tokenCount = state is int count ? count : tokens.Length; + for (int i = 0; i < tokenCount; i++) { + var token = tokens[i]; switch (token) { case 0: @@ -735,7 +815,9 @@ private sealed class RowFactory8 : global::Dapper.RowFactory tokens, int columnOffset) { - for (int i = 0; i < tokens.Length; i++) + int availableColumns = reader.FieldCount - columnOffset; + int tokenCount = global::System.Math.Min(tokens.Length, availableColumns); + for (int i = 0; i < tokenCount; i++) { int token = -1; var name = reader.GetName(columnOffset); @@ -757,13 +839,21 @@ private RowFactory8() {} columnOffset++; } - return null; + // Initialize remaining tokens to -1 (unmapped) + for (int i = tokenCount; i < tokens.Length; i++) + { + tokens[i] = -1; + } + return tokenCount; } public override global::Foo.OnlyNonDapperAotCtor Read(global::System.Data.Common.DbDataReader reader, global::System.ReadOnlySpan tokens, int columnOffset, object? state) { + if (columnOffset > 0 && reader.IsDBNull(columnOffset)) return default!; global::Foo.OnlyNonDapperAotCtor result = new(); - foreach (var token in tokens) + int tokenCount = state is int count ? count : tokens.Length; + for (int i = 0; i < tokenCount; i++) { + var token = tokens[i]; switch (token) { case 0: @@ -801,7 +891,9 @@ private sealed class RowFactory9 : global::Dapper.RowFactory tokens, int columnOffset) { - for (int i = 0; i < tokens.Length; i++) + int availableColumns = reader.FieldCount - columnOffset; + int tokenCount = global::System.Math.Min(tokens.Length, availableColumns); + for (int i = 0; i < tokenCount; i++) { int token = -1; var name = reader.GetName(columnOffset); @@ -823,13 +915,21 @@ private RowFactory9() {} columnOffset++; } - return null; + // Initialize remaining tokens to -1 (unmapped) + for (int i = tokenCount; i < tokens.Length; i++) + { + tokens[i] = -1; + } + return tokenCount; } public override global::Foo.SingleDefaultCtor Read(global::System.Data.Common.DbDataReader reader, global::System.ReadOnlySpan tokens, int columnOffset, object? state) { + if (columnOffset > 0 && reader.IsDBNull(columnOffset)) return default!; global::Foo.SingleDefaultCtor result = new(); - foreach (var token in tokens) + int tokenCount = state is int count ? count : tokens.Length; + for (int i = 0; i < tokenCount; i++) { + var token = tokens[i]; switch (token) { case 0: @@ -867,7 +967,9 @@ private sealed class RowFactory10 : global::Dapper.RowFactory tokens, int columnOffset) { - for (int i = 0; i < tokens.Length; i++) + int availableColumns = reader.FieldCount - columnOffset; + int tokenCount = global::System.Math.Min(tokens.Length, availableColumns); + for (int i = 0; i < tokenCount; i++) { int token = -1; var name = reader.GetName(columnOffset); @@ -889,13 +991,21 @@ private RowFactory10() {} columnOffset++; } - return null; + // Initialize remaining tokens to -1 (unmapped) + for (int i = tokenCount; i < tokens.Length; i++) + { + tokens[i] = -1; + } + return tokenCount; } public override global::Foo.MultipleDapperAotCtors Read(global::System.Data.Common.DbDataReader reader, global::System.ReadOnlySpan tokens, int columnOffset, object? state) { + if (columnOffset > 0 && reader.IsDBNull(columnOffset)) return default!; global::Foo.MultipleDapperAotCtors result = new(); - foreach (var token in tokens) + int tokenCount = state is int count ? count : tokens.Length; + for (int i = 0; i < tokenCount; i++) { + var token = tokens[i]; switch (token) { case 0: @@ -933,7 +1043,9 @@ private sealed class RowFactory11 : global::Dapper.RowFactory tokens, int columnOffset) { - for (int i = 0; i < tokens.Length; i++) + int availableColumns = reader.FieldCount - columnOffset; + int tokenCount = global::System.Math.Min(tokens.Length, availableColumns); + for (int i = 0; i < tokenCount; i++) { int token = -1; var name = reader.GetName(columnOffset); @@ -955,13 +1067,21 @@ private RowFactory11() {} columnOffset++; } - return null; + // Initialize remaining tokens to -1 (unmapped) + for (int i = tokenCount; i < tokens.Length; i++) + { + tokens[i] = -1; + } + return tokenCount; } public override global::Foo.SingleDapperAotCtor Read(global::System.Data.Common.DbDataReader reader, global::System.ReadOnlySpan tokens, int columnOffset, object? state) { + if (columnOffset > 0 && reader.IsDBNull(columnOffset)) return default!; global::Foo.SingleDapperAotCtor result = new(); - foreach (var token in tokens) + int tokenCount = state is int count ? count : tokens.Length; + for (int i = 0; i < tokenCount; i++) { + var token = tokens[i]; switch (token) { case 0: diff --git a/test/Dapper.AOT.Test/Interceptors/QueryCustomConstructionWithFactoryMethod.output.cs b/test/Dapper.AOT.Test/Interceptors/QueryCustomConstructionWithFactoryMethod.output.cs index 2690c41..6575253 100644 --- a/test/Dapper.AOT.Test/Interceptors/QueryCustomConstructionWithFactoryMethod.output.cs +++ b/test/Dapper.AOT.Test/Interceptors/QueryCustomConstructionWithFactoryMethod.output.cs @@ -86,7 +86,9 @@ private sealed class RowFactory0 : global::Dapper.RowFactory tokens, int columnOffset) { - for (int i = 0; i < tokens.Length; i++) + int availableColumns = reader.FieldCount - columnOffset; + int tokenCount = global::System.Math.Min(tokens.Length, availableColumns); + for (int i = 0; i < tokenCount; i++) { int token = -1; var name = reader.GetName(columnOffset); @@ -108,15 +110,23 @@ private RowFactory0() {} columnOffset++; } - return null; + // Initialize remaining tokens to -1 (unmapped) + for (int i = tokenCount; i < tokens.Length; i++) + { + tokens[i] = -1; + } + return tokenCount; } public override global::Foo.PublicPropertiesNoConstructor Read(global::System.Data.Common.DbDataReader reader, global::System.ReadOnlySpan tokens, int columnOffset, object? state) { + if (columnOffset > 0 && reader.IsDBNull(columnOffset)) return default!; int value0 = default; string? value1 = default; double? value2 = default; - foreach (var token in tokens) + int tokenCount = state is int count ? count : tokens.Length; + for (int i = 0; i < tokenCount; i++) { + var token = tokens[i]; switch (token) { case 0: @@ -152,7 +162,9 @@ private sealed class RowFactory1 : global::Dapper.RowFactory tokens, int columnOffset) { - for (int i = 0; i < tokens.Length; i++) + int availableColumns = reader.FieldCount - columnOffset; + int tokenCount = global::System.Math.Min(tokens.Length, availableColumns); + for (int i = 0; i < tokenCount; i++) { int token = -1; var name = reader.GetName(columnOffset); @@ -174,13 +186,21 @@ private RowFactory1() {} columnOffset++; } - return null; + // Initialize remaining tokens to -1 (unmapped) + for (int i = tokenCount; i < tokens.Length; i++) + { + tokens[i] = -1; + } + return tokenCount; } public override global::Foo.MultipleDapperAotFactoryMethods Read(global::System.Data.Common.DbDataReader reader, global::System.ReadOnlySpan tokens, int columnOffset, object? state) { + if (columnOffset > 0 && reader.IsDBNull(columnOffset)) return default!; global::Foo.MultipleDapperAotFactoryMethods result = new(); - foreach (var token in tokens) + int tokenCount = state is int count ? count : tokens.Length; + for (int i = 0; i < tokenCount; i++) { + var token = tokens[i]; switch (token) { case 0: @@ -218,7 +238,9 @@ private sealed class RowFactory2 : global::Dapper.RowFactory tokens, int columnOffset) { - for (int i = 0; i < tokens.Length; i++) + int availableColumns = reader.FieldCount - columnOffset; + int tokenCount = global::System.Math.Min(tokens.Length, availableColumns); + for (int i = 0; i < tokenCount; i++) { int token = -1; var name = reader.GetName(columnOffset); @@ -240,13 +262,21 @@ private RowFactory2() {} columnOffset++; } - return null; + // Initialize remaining tokens to -1 (unmapped) + for (int i = tokenCount; i < tokens.Length; i++) + { + tokens[i] = -1; + } + return tokenCount; } public override global::Foo.SingleFactoryNotMarkedWithDapperAot Read(global::System.Data.Common.DbDataReader reader, global::System.ReadOnlySpan tokens, int columnOffset, object? state) { + if (columnOffset > 0 && reader.IsDBNull(columnOffset)) return default!; global::Foo.SingleFactoryNotMarkedWithDapperAot result = new(); - foreach (var token in tokens) + int tokenCount = state is int count ? count : tokens.Length; + for (int i = 0; i < tokenCount; i++) { + var token = tokens[i]; switch (token) { case 0: @@ -284,7 +314,9 @@ private sealed class RowFactory3 : global::Dapper.RowFactory tokens, int columnOffset) { - for (int i = 0; i < tokens.Length; i++) + int availableColumns = reader.FieldCount - columnOffset; + int tokenCount = global::System.Math.Min(tokens.Length, availableColumns); + for (int i = 0; i < tokenCount; i++) { int token = -1; var name = reader.GetName(columnOffset); @@ -306,13 +338,21 @@ private RowFactory3() {} columnOffset++; } - return null; + // Initialize remaining tokens to -1 (unmapped) + for (int i = tokenCount; i < tokens.Length; i++) + { + tokens[i] = -1; + } + return tokenCount; } public override global::Foo.MultipleStandardFactoryMethods Read(global::System.Data.Common.DbDataReader reader, global::System.ReadOnlySpan tokens, int columnOffset, object? state) { + if (columnOffset > 0 && reader.IsDBNull(columnOffset)) return default!; global::Foo.MultipleStandardFactoryMethods result = new(); - foreach (var token in tokens) + int tokenCount = state is int count ? count : tokens.Length; + for (int i = 0; i < tokenCount; i++) { + var token = tokens[i]; switch (token) { case 0: diff --git a/test/Dapper.AOT.Test/Interceptors/QueryCustomConstructionWithFactoryMethod.output.netfx.cs b/test/Dapper.AOT.Test/Interceptors/QueryCustomConstructionWithFactoryMethod.output.netfx.cs index 2690c41..6575253 100644 --- a/test/Dapper.AOT.Test/Interceptors/QueryCustomConstructionWithFactoryMethod.output.netfx.cs +++ b/test/Dapper.AOT.Test/Interceptors/QueryCustomConstructionWithFactoryMethod.output.netfx.cs @@ -86,7 +86,9 @@ private sealed class RowFactory0 : global::Dapper.RowFactory tokens, int columnOffset) { - for (int i = 0; i < tokens.Length; i++) + int availableColumns = reader.FieldCount - columnOffset; + int tokenCount = global::System.Math.Min(tokens.Length, availableColumns); + for (int i = 0; i < tokenCount; i++) { int token = -1; var name = reader.GetName(columnOffset); @@ -108,15 +110,23 @@ private RowFactory0() {} columnOffset++; } - return null; + // Initialize remaining tokens to -1 (unmapped) + for (int i = tokenCount; i < tokens.Length; i++) + { + tokens[i] = -1; + } + return tokenCount; } public override global::Foo.PublicPropertiesNoConstructor Read(global::System.Data.Common.DbDataReader reader, global::System.ReadOnlySpan tokens, int columnOffset, object? state) { + if (columnOffset > 0 && reader.IsDBNull(columnOffset)) return default!; int value0 = default; string? value1 = default; double? value2 = default; - foreach (var token in tokens) + int tokenCount = state is int count ? count : tokens.Length; + for (int i = 0; i < tokenCount; i++) { + var token = tokens[i]; switch (token) { case 0: @@ -152,7 +162,9 @@ private sealed class RowFactory1 : global::Dapper.RowFactory tokens, int columnOffset) { - for (int i = 0; i < tokens.Length; i++) + int availableColumns = reader.FieldCount - columnOffset; + int tokenCount = global::System.Math.Min(tokens.Length, availableColumns); + for (int i = 0; i < tokenCount; i++) { int token = -1; var name = reader.GetName(columnOffset); @@ -174,13 +186,21 @@ private RowFactory1() {} columnOffset++; } - return null; + // Initialize remaining tokens to -1 (unmapped) + for (int i = tokenCount; i < tokens.Length; i++) + { + tokens[i] = -1; + } + return tokenCount; } public override global::Foo.MultipleDapperAotFactoryMethods Read(global::System.Data.Common.DbDataReader reader, global::System.ReadOnlySpan tokens, int columnOffset, object? state) { + if (columnOffset > 0 && reader.IsDBNull(columnOffset)) return default!; global::Foo.MultipleDapperAotFactoryMethods result = new(); - foreach (var token in tokens) + int tokenCount = state is int count ? count : tokens.Length; + for (int i = 0; i < tokenCount; i++) { + var token = tokens[i]; switch (token) { case 0: @@ -218,7 +238,9 @@ private sealed class RowFactory2 : global::Dapper.RowFactory tokens, int columnOffset) { - for (int i = 0; i < tokens.Length; i++) + int availableColumns = reader.FieldCount - columnOffset; + int tokenCount = global::System.Math.Min(tokens.Length, availableColumns); + for (int i = 0; i < tokenCount; i++) { int token = -1; var name = reader.GetName(columnOffset); @@ -240,13 +262,21 @@ private RowFactory2() {} columnOffset++; } - return null; + // Initialize remaining tokens to -1 (unmapped) + for (int i = tokenCount; i < tokens.Length; i++) + { + tokens[i] = -1; + } + return tokenCount; } public override global::Foo.SingleFactoryNotMarkedWithDapperAot Read(global::System.Data.Common.DbDataReader reader, global::System.ReadOnlySpan tokens, int columnOffset, object? state) { + if (columnOffset > 0 && reader.IsDBNull(columnOffset)) return default!; global::Foo.SingleFactoryNotMarkedWithDapperAot result = new(); - foreach (var token in tokens) + int tokenCount = state is int count ? count : tokens.Length; + for (int i = 0; i < tokenCount; i++) { + var token = tokens[i]; switch (token) { case 0: @@ -284,7 +314,9 @@ private sealed class RowFactory3 : global::Dapper.RowFactory tokens, int columnOffset) { - for (int i = 0; i < tokens.Length; i++) + int availableColumns = reader.FieldCount - columnOffset; + int tokenCount = global::System.Math.Min(tokens.Length, availableColumns); + for (int i = 0; i < tokenCount; i++) { int token = -1; var name = reader.GetName(columnOffset); @@ -306,13 +338,21 @@ private RowFactory3() {} columnOffset++; } - return null; + // Initialize remaining tokens to -1 (unmapped) + for (int i = tokenCount; i < tokens.Length; i++) + { + tokens[i] = -1; + } + return tokenCount; } public override global::Foo.MultipleStandardFactoryMethods Read(global::System.Data.Common.DbDataReader reader, global::System.ReadOnlySpan tokens, int columnOffset, object? state) { + if (columnOffset > 0 && reader.IsDBNull(columnOffset)) return default!; global::Foo.MultipleStandardFactoryMethods result = new(); - foreach (var token in tokens) + int tokenCount = state is int count ? count : tokens.Length; + for (int i = 0; i < tokenCount; i++) { + var token = tokens[i]; switch (token) { case 0: diff --git a/test/Dapper.AOT.Test/Interceptors/QueryDetection.output.cs b/test/Dapper.AOT.Test/Interceptors/QueryDetection.output.cs index 3a49981..25a9d44 100644 --- a/test/Dapper.AOT.Test/Interceptors/QueryDetection.output.cs +++ b/test/Dapper.AOT.Test/Interceptors/QueryDetection.output.cs @@ -129,7 +129,9 @@ private sealed class RowFactory0 : global::Dapper.RowFactory tokens, int columnOffset) { - for (int i = 0; i < tokens.Length; i++) + int availableColumns = reader.FieldCount - columnOffset; + int tokenCount = global::System.Math.Min(tokens.Length, availableColumns); + for (int i = 0; i < tokenCount; i++) { int token = -1; var name = reader.GetName(columnOffset); @@ -148,13 +150,21 @@ private RowFactory0() {} columnOffset++; } - return null; + // Initialize remaining tokens to -1 (unmapped) + for (int i = tokenCount; i < tokens.Length; i++) + { + tokens[i] = -1; + } + return tokenCount; } public override global::Foo.Customer Read(global::System.Data.Common.DbDataReader reader, global::System.ReadOnlySpan tokens, int columnOffset, object? state) { + if (columnOffset > 0 && reader.IsDBNull(columnOffset)) return default!; global::Foo.Customer result = new(); - foreach (var token in tokens) + int tokenCount = state is int count ? count : tokens.Length; + for (int i = 0; i < tokenCount; i++) { + var token = tokens[i]; switch (token) { case 0: diff --git a/test/Dapper.AOT.Test/Interceptors/QueryDetection.output.netfx.cs b/test/Dapper.AOT.Test/Interceptors/QueryDetection.output.netfx.cs index 3a49981..25a9d44 100644 --- a/test/Dapper.AOT.Test/Interceptors/QueryDetection.output.netfx.cs +++ b/test/Dapper.AOT.Test/Interceptors/QueryDetection.output.netfx.cs @@ -129,7 +129,9 @@ private sealed class RowFactory0 : global::Dapper.RowFactory tokens, int columnOffset) { - for (int i = 0; i < tokens.Length; i++) + int availableColumns = reader.FieldCount - columnOffset; + int tokenCount = global::System.Math.Min(tokens.Length, availableColumns); + for (int i = 0; i < tokenCount; i++) { int token = -1; var name = reader.GetName(columnOffset); @@ -148,13 +150,21 @@ private RowFactory0() {} columnOffset++; } - return null; + // Initialize remaining tokens to -1 (unmapped) + for (int i = tokenCount; i < tokens.Length; i++) + { + tokens[i] = -1; + } + return tokenCount; } public override global::Foo.Customer Read(global::System.Data.Common.DbDataReader reader, global::System.ReadOnlySpan tokens, int columnOffset, object? state) { + if (columnOffset > 0 && reader.IsDBNull(columnOffset)) return default!; global::Foo.Customer result = new(); - foreach (var token in tokens) + int tokenCount = state is int count ? count : tokens.Length; + for (int i = 0; i < tokenCount; i++) { + var token = tokens[i]; switch (token) { case 0: diff --git a/test/Dapper.AOT.Test/Interceptors/QueryMultiType.output.cs b/test/Dapper.AOT.Test/Interceptors/QueryMultiType.output.cs new file mode 100644 index 0000000..5f07e91 --- /dev/null +++ b/test/Dapper.AOT.Test/Interceptors/QueryMultiType.output.cs @@ -0,0 +1,296 @@ +#nullable enable +#pragma warning disable IDE0078 // unnecessary suppression is necessary +#pragma warning disable CS9270 // SDK-dependent change to interceptors usage +namespace Dapper.AOT // interceptors must be in a known namespace +{ + file static class DapperGeneratedInterceptors + { + [global::System.Runtime.CompilerServices.InterceptsLocationAttribute("Interceptors\\QueryMultiType.input.cs", 12, 24)] + internal static global::System.Collections.Generic.IEnumerable Query0(this global::System.Data.IDbConnection cnn, string sql, global::System.Func map, object? param, global::System.Data.IDbTransaction? transaction, bool buffered, string splitOn, int? commandTimeout, global::System.Data.CommandType? commandType) + { + // Query, TypedResult, Buffered, StoredProcedure, BindResultsByName, MultiMap + // returns data: global::Foo.Customer + global::System.Diagnostics.Debug.Assert(!string.IsNullOrWhiteSpace(sql)); + global::System.Diagnostics.Debug.Assert((commandType ?? global::Dapper.DapperAotExtensions.GetCommandType(sql)) == global::System.Data.CommandType.StoredProcedure); + global::System.Diagnostics.Debug.Assert(buffered is true); + global::System.Diagnostics.Debug.Assert(param is null); + + return global::Dapper.DapperAotExtensions.Command(cnn, transaction, sql, global::System.Data.CommandType.StoredProcedure, commandTimeout.GetValueOrDefault(), DefaultCommandFactory).QueryBuffered(param, map, RowFactory0.Instance, RowFactory0.Instance, splitOn); + + } + + [global::System.Runtime.CompilerServices.InterceptsLocationAttribute("Interceptors\\QueryMultiType.input.cs", 13, 24)] + internal static global::System.Collections.Generic.IEnumerable Query1(this global::System.Data.IDbConnection cnn, string sql, global::System.Func map, object? param, global::System.Data.IDbTransaction? transaction, bool buffered, string splitOn, int? commandTimeout, global::System.Data.CommandType? commandType) + { + // Query, TypedResult, Buffered, StoredProcedure, BindResultsByName, MultiMap + // returns data: global::Foo.Customer + global::System.Diagnostics.Debug.Assert(!string.IsNullOrWhiteSpace(sql)); + global::System.Diagnostics.Debug.Assert((commandType ?? global::Dapper.DapperAotExtensions.GetCommandType(sql)) == global::System.Data.CommandType.StoredProcedure); + global::System.Diagnostics.Debug.Assert(buffered is true); + global::System.Diagnostics.Debug.Assert(param is null); + + return global::Dapper.DapperAotExtensions.Command(cnn, transaction, sql, global::System.Data.CommandType.StoredProcedure, commandTimeout.GetValueOrDefault(), DefaultCommandFactory).QueryBuffered(param, map, RowFactory0.Instance, RowFactory0.Instance, RowFactory0.Instance, splitOn); + + } + + [global::System.Runtime.CompilerServices.InterceptsLocationAttribute("Interceptors\\QueryMultiType.input.cs", 14, 24)] + internal static global::System.Collections.Generic.IEnumerable Query2(this global::System.Data.IDbConnection cnn, string sql, global::System.Func map, object? param, global::System.Data.IDbTransaction? transaction, bool buffered, string splitOn, int? commandTimeout, global::System.Data.CommandType? commandType) + { + // Query, TypedResult, Buffered, StoredProcedure, BindResultsByName, MultiMap + // returns data: global::Foo.Customer + global::System.Diagnostics.Debug.Assert(!string.IsNullOrWhiteSpace(sql)); + global::System.Diagnostics.Debug.Assert((commandType ?? global::Dapper.DapperAotExtensions.GetCommandType(sql)) == global::System.Data.CommandType.StoredProcedure); + global::System.Diagnostics.Debug.Assert(buffered is true); + global::System.Diagnostics.Debug.Assert(param is null); + + return global::Dapper.DapperAotExtensions.Command(cnn, transaction, sql, global::System.Data.CommandType.StoredProcedure, commandTimeout.GetValueOrDefault(), DefaultCommandFactory).QueryBuffered(param, map, RowFactory0.Instance, RowFactory0.Instance, RowFactory0.Instance, RowFactory0.Instance, splitOn); + + } + + [global::System.Runtime.CompilerServices.InterceptsLocationAttribute("Interceptors\\QueryMultiType.input.cs", 15, 24)] + internal static global::System.Collections.Generic.IEnumerable Query3(this global::System.Data.IDbConnection cnn, string sql, global::System.Func map, object? param, global::System.Data.IDbTransaction? transaction, bool buffered, string splitOn, int? commandTimeout, global::System.Data.CommandType? commandType) + { + // Query, TypedResult, Buffered, StoredProcedure, BindResultsByName, MultiMap + // returns data: global::Foo.Customer + global::System.Diagnostics.Debug.Assert(!string.IsNullOrWhiteSpace(sql)); + global::System.Diagnostics.Debug.Assert((commandType ?? global::Dapper.DapperAotExtensions.GetCommandType(sql)) == global::System.Data.CommandType.StoredProcedure); + global::System.Diagnostics.Debug.Assert(buffered is true); + global::System.Diagnostics.Debug.Assert(param is null); + + return global::Dapper.DapperAotExtensions.Command(cnn, transaction, sql, global::System.Data.CommandType.StoredProcedure, commandTimeout.GetValueOrDefault(), DefaultCommandFactory).QueryBuffered(param, map, RowFactory0.Instance, RowFactory0.Instance, RowFactory0.Instance, RowFactory0.Instance, RowFactory0.Instance, splitOn); + + } + + [global::System.Runtime.CompilerServices.InterceptsLocationAttribute("Interceptors\\QueryMultiType.input.cs", 16, 24)] + internal static global::System.Collections.Generic.IEnumerable Query4(this global::System.Data.IDbConnection cnn, string sql, global::System.Func map, object? param, global::System.Data.IDbTransaction? transaction, bool buffered, string splitOn, int? commandTimeout, global::System.Data.CommandType? commandType) + { + // Query, TypedResult, Buffered, StoredProcedure, BindResultsByName, MultiMap + // returns data: global::Foo.Customer + global::System.Diagnostics.Debug.Assert(!string.IsNullOrWhiteSpace(sql)); + global::System.Diagnostics.Debug.Assert((commandType ?? global::Dapper.DapperAotExtensions.GetCommandType(sql)) == global::System.Data.CommandType.StoredProcedure); + global::System.Diagnostics.Debug.Assert(buffered is true); + global::System.Diagnostics.Debug.Assert(param is null); + + return global::Dapper.DapperAotExtensions.Command(cnn, transaction, sql, global::System.Data.CommandType.StoredProcedure, commandTimeout.GetValueOrDefault(), DefaultCommandFactory).QueryBuffered(param, map, RowFactory0.Instance, RowFactory0.Instance, RowFactory0.Instance, RowFactory0.Instance, RowFactory0.Instance, RowFactory0.Instance, splitOn); + + } + + [global::System.Runtime.CompilerServices.InterceptsLocationAttribute("Interceptors\\QueryMultiType.input.cs", 17, 24)] + internal static global::System.Collections.Generic.IEnumerable Query5(this global::System.Data.IDbConnection cnn, string sql, global::System.Func map, object? param, global::System.Data.IDbTransaction? transaction, bool buffered, string splitOn, int? commandTimeout, global::System.Data.CommandType? commandType) + { + // Query, TypedResult, Buffered, StoredProcedure, BindResultsByName, MultiMap + // returns data: global::Foo.Customer + global::System.Diagnostics.Debug.Assert(!string.IsNullOrWhiteSpace(sql)); + global::System.Diagnostics.Debug.Assert((commandType ?? global::Dapper.DapperAotExtensions.GetCommandType(sql)) == global::System.Data.CommandType.StoredProcedure); + global::System.Diagnostics.Debug.Assert(buffered is true); + global::System.Diagnostics.Debug.Assert(param is null); + + return global::Dapper.DapperAotExtensions.Command(cnn, transaction, sql, global::System.Data.CommandType.StoredProcedure, commandTimeout.GetValueOrDefault(), DefaultCommandFactory).QueryBuffered(param, map, RowFactory0.Instance, RowFactory0.Instance, RowFactory0.Instance, RowFactory0.Instance, RowFactory0.Instance, RowFactory0.Instance, RowFactory0.Instance, splitOn); + + } + + [global::System.Runtime.CompilerServices.InterceptsLocationAttribute("Interceptors\\QueryMultiType.input.cs", 19, 30)] + internal static global::System.Threading.Tasks.Task> QueryAsync6(this global::System.Data.IDbConnection cnn, string sql, global::System.Func map, object? param, global::System.Data.IDbTransaction? transaction, bool buffered, string splitOn, int? commandTimeout, global::System.Data.CommandType? commandType) + { + // Query, Async, TypedResult, Buffered, StoredProcedure, BindResultsByName, MultiMap + // returns data: global::Foo.Customer + global::System.Diagnostics.Debug.Assert(!string.IsNullOrWhiteSpace(sql)); + global::System.Diagnostics.Debug.Assert((commandType ?? global::Dapper.DapperAotExtensions.GetCommandType(sql)) == global::System.Data.CommandType.StoredProcedure); + global::System.Diagnostics.Debug.Assert(buffered is true); + global::System.Diagnostics.Debug.Assert(param is null); + + return global::Dapper.DapperAotExtensions.AsEnumerableAsync( + global::Dapper.DapperAotExtensions.Command(cnn, transaction, sql, global::System.Data.CommandType.StoredProcedure, commandTimeout.GetValueOrDefault(), DefaultCommandFactory).QueryBufferedAsync(param, map, RowFactory0.Instance, RowFactory0.Instance, splitOn)); + + } + + [global::System.Runtime.CompilerServices.InterceptsLocationAttribute("Interceptors\\QueryMultiType.input.cs", 20, 30)] + internal static global::System.Threading.Tasks.Task> QueryAsync7(this global::System.Data.IDbConnection cnn, string sql, global::System.Func map, object? param, global::System.Data.IDbTransaction? transaction, bool buffered, string splitOn, int? commandTimeout, global::System.Data.CommandType? commandType) + { + // Query, Async, TypedResult, Buffered, StoredProcedure, BindResultsByName, MultiMap + // returns data: global::Foo.Customer + global::System.Diagnostics.Debug.Assert(!string.IsNullOrWhiteSpace(sql)); + global::System.Diagnostics.Debug.Assert((commandType ?? global::Dapper.DapperAotExtensions.GetCommandType(sql)) == global::System.Data.CommandType.StoredProcedure); + global::System.Diagnostics.Debug.Assert(buffered is true); + global::System.Diagnostics.Debug.Assert(param is null); + + return global::Dapper.DapperAotExtensions.AsEnumerableAsync( + global::Dapper.DapperAotExtensions.Command(cnn, transaction, sql, global::System.Data.CommandType.StoredProcedure, commandTimeout.GetValueOrDefault(), DefaultCommandFactory).QueryBufferedAsync(param, map, RowFactory0.Instance, RowFactory0.Instance, RowFactory0.Instance, splitOn)); + + } + + [global::System.Runtime.CompilerServices.InterceptsLocationAttribute("Interceptors\\QueryMultiType.input.cs", 21, 30)] + internal static global::System.Threading.Tasks.Task> QueryAsync8(this global::System.Data.IDbConnection cnn, string sql, global::System.Func map, object? param, global::System.Data.IDbTransaction? transaction, bool buffered, string splitOn, int? commandTimeout, global::System.Data.CommandType? commandType) + { + // Query, Async, TypedResult, Buffered, StoredProcedure, BindResultsByName, MultiMap + // returns data: global::Foo.Customer + global::System.Diagnostics.Debug.Assert(!string.IsNullOrWhiteSpace(sql)); + global::System.Diagnostics.Debug.Assert((commandType ?? global::Dapper.DapperAotExtensions.GetCommandType(sql)) == global::System.Data.CommandType.StoredProcedure); + global::System.Diagnostics.Debug.Assert(buffered is true); + global::System.Diagnostics.Debug.Assert(param is null); + + return global::Dapper.DapperAotExtensions.AsEnumerableAsync( + global::Dapper.DapperAotExtensions.Command(cnn, transaction, sql, global::System.Data.CommandType.StoredProcedure, commandTimeout.GetValueOrDefault(), DefaultCommandFactory).QueryBufferedAsync(param, map, RowFactory0.Instance, RowFactory0.Instance, RowFactory0.Instance, RowFactory0.Instance, splitOn)); + + } + + [global::System.Runtime.CompilerServices.InterceptsLocationAttribute("Interceptors\\QueryMultiType.input.cs", 22, 30)] + internal static global::System.Threading.Tasks.Task> QueryAsync9(this global::System.Data.IDbConnection cnn, string sql, global::System.Func map, object? param, global::System.Data.IDbTransaction? transaction, bool buffered, string splitOn, int? commandTimeout, global::System.Data.CommandType? commandType) + { + // Query, Async, TypedResult, Buffered, StoredProcedure, BindResultsByName, MultiMap + // returns data: global::Foo.Customer + global::System.Diagnostics.Debug.Assert(!string.IsNullOrWhiteSpace(sql)); + global::System.Diagnostics.Debug.Assert((commandType ?? global::Dapper.DapperAotExtensions.GetCommandType(sql)) == global::System.Data.CommandType.StoredProcedure); + global::System.Diagnostics.Debug.Assert(buffered is true); + global::System.Diagnostics.Debug.Assert(param is null); + + return global::Dapper.DapperAotExtensions.AsEnumerableAsync( + global::Dapper.DapperAotExtensions.Command(cnn, transaction, sql, global::System.Data.CommandType.StoredProcedure, commandTimeout.GetValueOrDefault(), DefaultCommandFactory).QueryBufferedAsync(param, map, RowFactory0.Instance, RowFactory0.Instance, RowFactory0.Instance, RowFactory0.Instance, RowFactory0.Instance, splitOn)); + + } + + [global::System.Runtime.CompilerServices.InterceptsLocationAttribute("Interceptors\\QueryMultiType.input.cs", 23, 30)] + internal static global::System.Threading.Tasks.Task> QueryAsync10(this global::System.Data.IDbConnection cnn, string sql, global::System.Func map, object? param, global::System.Data.IDbTransaction? transaction, bool buffered, string splitOn, int? commandTimeout, global::System.Data.CommandType? commandType) + { + // Query, Async, TypedResult, Buffered, StoredProcedure, BindResultsByName, MultiMap + // returns data: global::Foo.Customer + global::System.Diagnostics.Debug.Assert(!string.IsNullOrWhiteSpace(sql)); + global::System.Diagnostics.Debug.Assert((commandType ?? global::Dapper.DapperAotExtensions.GetCommandType(sql)) == global::System.Data.CommandType.StoredProcedure); + global::System.Diagnostics.Debug.Assert(buffered is true); + global::System.Diagnostics.Debug.Assert(param is null); + + return global::Dapper.DapperAotExtensions.AsEnumerableAsync( + global::Dapper.DapperAotExtensions.Command(cnn, transaction, sql, global::System.Data.CommandType.StoredProcedure, commandTimeout.GetValueOrDefault(), DefaultCommandFactory).QueryBufferedAsync(param, map, RowFactory0.Instance, RowFactory0.Instance, RowFactory0.Instance, RowFactory0.Instance, RowFactory0.Instance, RowFactory0.Instance, splitOn)); + + } + + [global::System.Runtime.CompilerServices.InterceptsLocationAttribute("Interceptors\\QueryMultiType.input.cs", 24, 30)] + internal static global::System.Threading.Tasks.Task> QueryAsync11(this global::System.Data.IDbConnection cnn, string sql, global::System.Func map, object? param, global::System.Data.IDbTransaction? transaction, bool buffered, string splitOn, int? commandTimeout, global::System.Data.CommandType? commandType) + { + // Query, Async, TypedResult, Buffered, StoredProcedure, BindResultsByName, MultiMap + // returns data: global::Foo.Customer + global::System.Diagnostics.Debug.Assert(!string.IsNullOrWhiteSpace(sql)); + global::System.Diagnostics.Debug.Assert((commandType ?? global::Dapper.DapperAotExtensions.GetCommandType(sql)) == global::System.Data.CommandType.StoredProcedure); + global::System.Diagnostics.Debug.Assert(buffered is true); + global::System.Diagnostics.Debug.Assert(param is null); + + return global::Dapper.DapperAotExtensions.AsEnumerableAsync( + global::Dapper.DapperAotExtensions.Command(cnn, transaction, sql, global::System.Data.CommandType.StoredProcedure, commandTimeout.GetValueOrDefault(), DefaultCommandFactory).QueryBufferedAsync(param, map, RowFactory0.Instance, RowFactory0.Instance, RowFactory0.Instance, RowFactory0.Instance, RowFactory0.Instance, RowFactory0.Instance, RowFactory0.Instance, splitOn)); + + } + + private class CommonCommandFactory : global::Dapper.CommandFactory + { + public override global::System.Data.Common.DbCommand GetCommand(global::System.Data.Common.DbConnection connection, string sql, global::System.Data.CommandType commandType, T args) + { + var cmd = base.GetCommand(connection, sql, commandType, args); + // apply special per-provider command initialization logic for OracleCommand + if (cmd is global::Oracle.ManagedDataAccess.Client.OracleCommand cmd0) + { + cmd0.BindByName = true; + cmd0.InitialLONGFetchSize = -1; + + } + return cmd; + } + + } + + private static readonly CommonCommandFactory DefaultCommandFactory = new(); + + private sealed class RowFactory0 : global::Dapper.RowFactory + { + internal static readonly RowFactory0 Instance = new(); + private RowFactory0() {} + public override object? Tokenize(global::System.Data.Common.DbDataReader reader, global::System.Span tokens, int columnOffset) + { + int availableColumns = reader.FieldCount - columnOffset; + int tokenCount = global::System.Math.Min(tokens.Length, availableColumns); + for (int i = 0; i < tokenCount; i++) + { + int token = -1; + var name = reader.GetName(columnOffset); + var type = reader.GetFieldType(columnOffset); + switch (NormalizedHash(name)) + { + case 4245442695U when NormalizedEquals(name, "x"): + token = type == typeof(int) ? 0 : 3; // two tokens for right-typed and type-flexible + break; + case 4228665076U when NormalizedEquals(name, "y"): + token = type == typeof(string) ? 1 : 4; + break; + case 4278997933U when NormalizedEquals(name, "z"): + token = type == typeof(double) ? 2 : 5; + break; + + } + tokens[i] = token; + columnOffset++; + + } + // Initialize remaining tokens to -1 (unmapped) + for (int i = tokenCount; i < tokens.Length; i++) + { + tokens[i] = -1; + } + return tokenCount; + } + public override global::Foo.Customer Read(global::System.Data.Common.DbDataReader reader, global::System.ReadOnlySpan tokens, int columnOffset, object? state) + { + if (columnOffset > 0 && reader.IsDBNull(columnOffset)) return default!; + global::Foo.Customer result = new(); + int tokenCount = state is int count ? count : tokens.Length; + for (int i = 0; i < tokenCount; i++) + { + var token = tokens[i]; + switch (token) + { + case 0: + result.X = reader.GetInt32(columnOffset); + break; + case 3: + result.X = GetValue(reader, columnOffset); + break; + case 1: + result.Y = reader.IsDBNull(columnOffset) ? (string?)null : reader.GetString(columnOffset); + break; + case 4: + result.Y = reader.IsDBNull(columnOffset) ? (string?)null : GetValue(reader, columnOffset); + break; + case 2: + result.Z = reader.IsDBNull(columnOffset) ? (double?)null : reader.GetDouble(columnOffset); + break; + case 5: + result.Z = reader.IsDBNull(columnOffset) ? (double?)null : GetValue(reader, columnOffset); + break; + + } + columnOffset++; + + } + return result; + + } + + } + + + } +} +namespace System.Runtime.CompilerServices +{ + // this type is needed by the compiler to implement interceptors - it doesn't need to + // come from the runtime itself, though + + [global::System.Diagnostics.Conditional("DEBUG")] // not needed post-build, so: evaporate + [global::System.AttributeUsage(global::System.AttributeTargets.Method, AllowMultiple = true)] + sealed file class InterceptsLocationAttribute : global::System.Attribute + { + public InterceptsLocationAttribute(string path, int lineNumber, int columnNumber) + { + _ = path; + _ = lineNumber; + _ = columnNumber; + } + } +} \ No newline at end of file diff --git a/test/Dapper.AOT.Test/Interceptors/QueryMultiType.output.netfx.cs b/test/Dapper.AOT.Test/Interceptors/QueryMultiType.output.netfx.cs new file mode 100644 index 0000000..5f07e91 --- /dev/null +++ b/test/Dapper.AOT.Test/Interceptors/QueryMultiType.output.netfx.cs @@ -0,0 +1,296 @@ +#nullable enable +#pragma warning disable IDE0078 // unnecessary suppression is necessary +#pragma warning disable CS9270 // SDK-dependent change to interceptors usage +namespace Dapper.AOT // interceptors must be in a known namespace +{ + file static class DapperGeneratedInterceptors + { + [global::System.Runtime.CompilerServices.InterceptsLocationAttribute("Interceptors\\QueryMultiType.input.cs", 12, 24)] + internal static global::System.Collections.Generic.IEnumerable Query0(this global::System.Data.IDbConnection cnn, string sql, global::System.Func map, object? param, global::System.Data.IDbTransaction? transaction, bool buffered, string splitOn, int? commandTimeout, global::System.Data.CommandType? commandType) + { + // Query, TypedResult, Buffered, StoredProcedure, BindResultsByName, MultiMap + // returns data: global::Foo.Customer + global::System.Diagnostics.Debug.Assert(!string.IsNullOrWhiteSpace(sql)); + global::System.Diagnostics.Debug.Assert((commandType ?? global::Dapper.DapperAotExtensions.GetCommandType(sql)) == global::System.Data.CommandType.StoredProcedure); + global::System.Diagnostics.Debug.Assert(buffered is true); + global::System.Diagnostics.Debug.Assert(param is null); + + return global::Dapper.DapperAotExtensions.Command(cnn, transaction, sql, global::System.Data.CommandType.StoredProcedure, commandTimeout.GetValueOrDefault(), DefaultCommandFactory).QueryBuffered(param, map, RowFactory0.Instance, RowFactory0.Instance, splitOn); + + } + + [global::System.Runtime.CompilerServices.InterceptsLocationAttribute("Interceptors\\QueryMultiType.input.cs", 13, 24)] + internal static global::System.Collections.Generic.IEnumerable Query1(this global::System.Data.IDbConnection cnn, string sql, global::System.Func map, object? param, global::System.Data.IDbTransaction? transaction, bool buffered, string splitOn, int? commandTimeout, global::System.Data.CommandType? commandType) + { + // Query, TypedResult, Buffered, StoredProcedure, BindResultsByName, MultiMap + // returns data: global::Foo.Customer + global::System.Diagnostics.Debug.Assert(!string.IsNullOrWhiteSpace(sql)); + global::System.Diagnostics.Debug.Assert((commandType ?? global::Dapper.DapperAotExtensions.GetCommandType(sql)) == global::System.Data.CommandType.StoredProcedure); + global::System.Diagnostics.Debug.Assert(buffered is true); + global::System.Diagnostics.Debug.Assert(param is null); + + return global::Dapper.DapperAotExtensions.Command(cnn, transaction, sql, global::System.Data.CommandType.StoredProcedure, commandTimeout.GetValueOrDefault(), DefaultCommandFactory).QueryBuffered(param, map, RowFactory0.Instance, RowFactory0.Instance, RowFactory0.Instance, splitOn); + + } + + [global::System.Runtime.CompilerServices.InterceptsLocationAttribute("Interceptors\\QueryMultiType.input.cs", 14, 24)] + internal static global::System.Collections.Generic.IEnumerable Query2(this global::System.Data.IDbConnection cnn, string sql, global::System.Func map, object? param, global::System.Data.IDbTransaction? transaction, bool buffered, string splitOn, int? commandTimeout, global::System.Data.CommandType? commandType) + { + // Query, TypedResult, Buffered, StoredProcedure, BindResultsByName, MultiMap + // returns data: global::Foo.Customer + global::System.Diagnostics.Debug.Assert(!string.IsNullOrWhiteSpace(sql)); + global::System.Diagnostics.Debug.Assert((commandType ?? global::Dapper.DapperAotExtensions.GetCommandType(sql)) == global::System.Data.CommandType.StoredProcedure); + global::System.Diagnostics.Debug.Assert(buffered is true); + global::System.Diagnostics.Debug.Assert(param is null); + + return global::Dapper.DapperAotExtensions.Command(cnn, transaction, sql, global::System.Data.CommandType.StoredProcedure, commandTimeout.GetValueOrDefault(), DefaultCommandFactory).QueryBuffered(param, map, RowFactory0.Instance, RowFactory0.Instance, RowFactory0.Instance, RowFactory0.Instance, splitOn); + + } + + [global::System.Runtime.CompilerServices.InterceptsLocationAttribute("Interceptors\\QueryMultiType.input.cs", 15, 24)] + internal static global::System.Collections.Generic.IEnumerable Query3(this global::System.Data.IDbConnection cnn, string sql, global::System.Func map, object? param, global::System.Data.IDbTransaction? transaction, bool buffered, string splitOn, int? commandTimeout, global::System.Data.CommandType? commandType) + { + // Query, TypedResult, Buffered, StoredProcedure, BindResultsByName, MultiMap + // returns data: global::Foo.Customer + global::System.Diagnostics.Debug.Assert(!string.IsNullOrWhiteSpace(sql)); + global::System.Diagnostics.Debug.Assert((commandType ?? global::Dapper.DapperAotExtensions.GetCommandType(sql)) == global::System.Data.CommandType.StoredProcedure); + global::System.Diagnostics.Debug.Assert(buffered is true); + global::System.Diagnostics.Debug.Assert(param is null); + + return global::Dapper.DapperAotExtensions.Command(cnn, transaction, sql, global::System.Data.CommandType.StoredProcedure, commandTimeout.GetValueOrDefault(), DefaultCommandFactory).QueryBuffered(param, map, RowFactory0.Instance, RowFactory0.Instance, RowFactory0.Instance, RowFactory0.Instance, RowFactory0.Instance, splitOn); + + } + + [global::System.Runtime.CompilerServices.InterceptsLocationAttribute("Interceptors\\QueryMultiType.input.cs", 16, 24)] + internal static global::System.Collections.Generic.IEnumerable Query4(this global::System.Data.IDbConnection cnn, string sql, global::System.Func map, object? param, global::System.Data.IDbTransaction? transaction, bool buffered, string splitOn, int? commandTimeout, global::System.Data.CommandType? commandType) + { + // Query, TypedResult, Buffered, StoredProcedure, BindResultsByName, MultiMap + // returns data: global::Foo.Customer + global::System.Diagnostics.Debug.Assert(!string.IsNullOrWhiteSpace(sql)); + global::System.Diagnostics.Debug.Assert((commandType ?? global::Dapper.DapperAotExtensions.GetCommandType(sql)) == global::System.Data.CommandType.StoredProcedure); + global::System.Diagnostics.Debug.Assert(buffered is true); + global::System.Diagnostics.Debug.Assert(param is null); + + return global::Dapper.DapperAotExtensions.Command(cnn, transaction, sql, global::System.Data.CommandType.StoredProcedure, commandTimeout.GetValueOrDefault(), DefaultCommandFactory).QueryBuffered(param, map, RowFactory0.Instance, RowFactory0.Instance, RowFactory0.Instance, RowFactory0.Instance, RowFactory0.Instance, RowFactory0.Instance, splitOn); + + } + + [global::System.Runtime.CompilerServices.InterceptsLocationAttribute("Interceptors\\QueryMultiType.input.cs", 17, 24)] + internal static global::System.Collections.Generic.IEnumerable Query5(this global::System.Data.IDbConnection cnn, string sql, global::System.Func map, object? param, global::System.Data.IDbTransaction? transaction, bool buffered, string splitOn, int? commandTimeout, global::System.Data.CommandType? commandType) + { + // Query, TypedResult, Buffered, StoredProcedure, BindResultsByName, MultiMap + // returns data: global::Foo.Customer + global::System.Diagnostics.Debug.Assert(!string.IsNullOrWhiteSpace(sql)); + global::System.Diagnostics.Debug.Assert((commandType ?? global::Dapper.DapperAotExtensions.GetCommandType(sql)) == global::System.Data.CommandType.StoredProcedure); + global::System.Diagnostics.Debug.Assert(buffered is true); + global::System.Diagnostics.Debug.Assert(param is null); + + return global::Dapper.DapperAotExtensions.Command(cnn, transaction, sql, global::System.Data.CommandType.StoredProcedure, commandTimeout.GetValueOrDefault(), DefaultCommandFactory).QueryBuffered(param, map, RowFactory0.Instance, RowFactory0.Instance, RowFactory0.Instance, RowFactory0.Instance, RowFactory0.Instance, RowFactory0.Instance, RowFactory0.Instance, splitOn); + + } + + [global::System.Runtime.CompilerServices.InterceptsLocationAttribute("Interceptors\\QueryMultiType.input.cs", 19, 30)] + internal static global::System.Threading.Tasks.Task> QueryAsync6(this global::System.Data.IDbConnection cnn, string sql, global::System.Func map, object? param, global::System.Data.IDbTransaction? transaction, bool buffered, string splitOn, int? commandTimeout, global::System.Data.CommandType? commandType) + { + // Query, Async, TypedResult, Buffered, StoredProcedure, BindResultsByName, MultiMap + // returns data: global::Foo.Customer + global::System.Diagnostics.Debug.Assert(!string.IsNullOrWhiteSpace(sql)); + global::System.Diagnostics.Debug.Assert((commandType ?? global::Dapper.DapperAotExtensions.GetCommandType(sql)) == global::System.Data.CommandType.StoredProcedure); + global::System.Diagnostics.Debug.Assert(buffered is true); + global::System.Diagnostics.Debug.Assert(param is null); + + return global::Dapper.DapperAotExtensions.AsEnumerableAsync( + global::Dapper.DapperAotExtensions.Command(cnn, transaction, sql, global::System.Data.CommandType.StoredProcedure, commandTimeout.GetValueOrDefault(), DefaultCommandFactory).QueryBufferedAsync(param, map, RowFactory0.Instance, RowFactory0.Instance, splitOn)); + + } + + [global::System.Runtime.CompilerServices.InterceptsLocationAttribute("Interceptors\\QueryMultiType.input.cs", 20, 30)] + internal static global::System.Threading.Tasks.Task> QueryAsync7(this global::System.Data.IDbConnection cnn, string sql, global::System.Func map, object? param, global::System.Data.IDbTransaction? transaction, bool buffered, string splitOn, int? commandTimeout, global::System.Data.CommandType? commandType) + { + // Query, Async, TypedResult, Buffered, StoredProcedure, BindResultsByName, MultiMap + // returns data: global::Foo.Customer + global::System.Diagnostics.Debug.Assert(!string.IsNullOrWhiteSpace(sql)); + global::System.Diagnostics.Debug.Assert((commandType ?? global::Dapper.DapperAotExtensions.GetCommandType(sql)) == global::System.Data.CommandType.StoredProcedure); + global::System.Diagnostics.Debug.Assert(buffered is true); + global::System.Diagnostics.Debug.Assert(param is null); + + return global::Dapper.DapperAotExtensions.AsEnumerableAsync( + global::Dapper.DapperAotExtensions.Command(cnn, transaction, sql, global::System.Data.CommandType.StoredProcedure, commandTimeout.GetValueOrDefault(), DefaultCommandFactory).QueryBufferedAsync(param, map, RowFactory0.Instance, RowFactory0.Instance, RowFactory0.Instance, splitOn)); + + } + + [global::System.Runtime.CompilerServices.InterceptsLocationAttribute("Interceptors\\QueryMultiType.input.cs", 21, 30)] + internal static global::System.Threading.Tasks.Task> QueryAsync8(this global::System.Data.IDbConnection cnn, string sql, global::System.Func map, object? param, global::System.Data.IDbTransaction? transaction, bool buffered, string splitOn, int? commandTimeout, global::System.Data.CommandType? commandType) + { + // Query, Async, TypedResult, Buffered, StoredProcedure, BindResultsByName, MultiMap + // returns data: global::Foo.Customer + global::System.Diagnostics.Debug.Assert(!string.IsNullOrWhiteSpace(sql)); + global::System.Diagnostics.Debug.Assert((commandType ?? global::Dapper.DapperAotExtensions.GetCommandType(sql)) == global::System.Data.CommandType.StoredProcedure); + global::System.Diagnostics.Debug.Assert(buffered is true); + global::System.Diagnostics.Debug.Assert(param is null); + + return global::Dapper.DapperAotExtensions.AsEnumerableAsync( + global::Dapper.DapperAotExtensions.Command(cnn, transaction, sql, global::System.Data.CommandType.StoredProcedure, commandTimeout.GetValueOrDefault(), DefaultCommandFactory).QueryBufferedAsync(param, map, RowFactory0.Instance, RowFactory0.Instance, RowFactory0.Instance, RowFactory0.Instance, splitOn)); + + } + + [global::System.Runtime.CompilerServices.InterceptsLocationAttribute("Interceptors\\QueryMultiType.input.cs", 22, 30)] + internal static global::System.Threading.Tasks.Task> QueryAsync9(this global::System.Data.IDbConnection cnn, string sql, global::System.Func map, object? param, global::System.Data.IDbTransaction? transaction, bool buffered, string splitOn, int? commandTimeout, global::System.Data.CommandType? commandType) + { + // Query, Async, TypedResult, Buffered, StoredProcedure, BindResultsByName, MultiMap + // returns data: global::Foo.Customer + global::System.Diagnostics.Debug.Assert(!string.IsNullOrWhiteSpace(sql)); + global::System.Diagnostics.Debug.Assert((commandType ?? global::Dapper.DapperAotExtensions.GetCommandType(sql)) == global::System.Data.CommandType.StoredProcedure); + global::System.Diagnostics.Debug.Assert(buffered is true); + global::System.Diagnostics.Debug.Assert(param is null); + + return global::Dapper.DapperAotExtensions.AsEnumerableAsync( + global::Dapper.DapperAotExtensions.Command(cnn, transaction, sql, global::System.Data.CommandType.StoredProcedure, commandTimeout.GetValueOrDefault(), DefaultCommandFactory).QueryBufferedAsync(param, map, RowFactory0.Instance, RowFactory0.Instance, RowFactory0.Instance, RowFactory0.Instance, RowFactory0.Instance, splitOn)); + + } + + [global::System.Runtime.CompilerServices.InterceptsLocationAttribute("Interceptors\\QueryMultiType.input.cs", 23, 30)] + internal static global::System.Threading.Tasks.Task> QueryAsync10(this global::System.Data.IDbConnection cnn, string sql, global::System.Func map, object? param, global::System.Data.IDbTransaction? transaction, bool buffered, string splitOn, int? commandTimeout, global::System.Data.CommandType? commandType) + { + // Query, Async, TypedResult, Buffered, StoredProcedure, BindResultsByName, MultiMap + // returns data: global::Foo.Customer + global::System.Diagnostics.Debug.Assert(!string.IsNullOrWhiteSpace(sql)); + global::System.Diagnostics.Debug.Assert((commandType ?? global::Dapper.DapperAotExtensions.GetCommandType(sql)) == global::System.Data.CommandType.StoredProcedure); + global::System.Diagnostics.Debug.Assert(buffered is true); + global::System.Diagnostics.Debug.Assert(param is null); + + return global::Dapper.DapperAotExtensions.AsEnumerableAsync( + global::Dapper.DapperAotExtensions.Command(cnn, transaction, sql, global::System.Data.CommandType.StoredProcedure, commandTimeout.GetValueOrDefault(), DefaultCommandFactory).QueryBufferedAsync(param, map, RowFactory0.Instance, RowFactory0.Instance, RowFactory0.Instance, RowFactory0.Instance, RowFactory0.Instance, RowFactory0.Instance, splitOn)); + + } + + [global::System.Runtime.CompilerServices.InterceptsLocationAttribute("Interceptors\\QueryMultiType.input.cs", 24, 30)] + internal static global::System.Threading.Tasks.Task> QueryAsync11(this global::System.Data.IDbConnection cnn, string sql, global::System.Func map, object? param, global::System.Data.IDbTransaction? transaction, bool buffered, string splitOn, int? commandTimeout, global::System.Data.CommandType? commandType) + { + // Query, Async, TypedResult, Buffered, StoredProcedure, BindResultsByName, MultiMap + // returns data: global::Foo.Customer + global::System.Diagnostics.Debug.Assert(!string.IsNullOrWhiteSpace(sql)); + global::System.Diagnostics.Debug.Assert((commandType ?? global::Dapper.DapperAotExtensions.GetCommandType(sql)) == global::System.Data.CommandType.StoredProcedure); + global::System.Diagnostics.Debug.Assert(buffered is true); + global::System.Diagnostics.Debug.Assert(param is null); + + return global::Dapper.DapperAotExtensions.AsEnumerableAsync( + global::Dapper.DapperAotExtensions.Command(cnn, transaction, sql, global::System.Data.CommandType.StoredProcedure, commandTimeout.GetValueOrDefault(), DefaultCommandFactory).QueryBufferedAsync(param, map, RowFactory0.Instance, RowFactory0.Instance, RowFactory0.Instance, RowFactory0.Instance, RowFactory0.Instance, RowFactory0.Instance, RowFactory0.Instance, splitOn)); + + } + + private class CommonCommandFactory : global::Dapper.CommandFactory + { + public override global::System.Data.Common.DbCommand GetCommand(global::System.Data.Common.DbConnection connection, string sql, global::System.Data.CommandType commandType, T args) + { + var cmd = base.GetCommand(connection, sql, commandType, args); + // apply special per-provider command initialization logic for OracleCommand + if (cmd is global::Oracle.ManagedDataAccess.Client.OracleCommand cmd0) + { + cmd0.BindByName = true; + cmd0.InitialLONGFetchSize = -1; + + } + return cmd; + } + + } + + private static readonly CommonCommandFactory DefaultCommandFactory = new(); + + private sealed class RowFactory0 : global::Dapper.RowFactory + { + internal static readonly RowFactory0 Instance = new(); + private RowFactory0() {} + public override object? Tokenize(global::System.Data.Common.DbDataReader reader, global::System.Span tokens, int columnOffset) + { + int availableColumns = reader.FieldCount - columnOffset; + int tokenCount = global::System.Math.Min(tokens.Length, availableColumns); + for (int i = 0; i < tokenCount; i++) + { + int token = -1; + var name = reader.GetName(columnOffset); + var type = reader.GetFieldType(columnOffset); + switch (NormalizedHash(name)) + { + case 4245442695U when NormalizedEquals(name, "x"): + token = type == typeof(int) ? 0 : 3; // two tokens for right-typed and type-flexible + break; + case 4228665076U when NormalizedEquals(name, "y"): + token = type == typeof(string) ? 1 : 4; + break; + case 4278997933U when NormalizedEquals(name, "z"): + token = type == typeof(double) ? 2 : 5; + break; + + } + tokens[i] = token; + columnOffset++; + + } + // Initialize remaining tokens to -1 (unmapped) + for (int i = tokenCount; i < tokens.Length; i++) + { + tokens[i] = -1; + } + return tokenCount; + } + public override global::Foo.Customer Read(global::System.Data.Common.DbDataReader reader, global::System.ReadOnlySpan tokens, int columnOffset, object? state) + { + if (columnOffset > 0 && reader.IsDBNull(columnOffset)) return default!; + global::Foo.Customer result = new(); + int tokenCount = state is int count ? count : tokens.Length; + for (int i = 0; i < tokenCount; i++) + { + var token = tokens[i]; + switch (token) + { + case 0: + result.X = reader.GetInt32(columnOffset); + break; + case 3: + result.X = GetValue(reader, columnOffset); + break; + case 1: + result.Y = reader.IsDBNull(columnOffset) ? (string?)null : reader.GetString(columnOffset); + break; + case 4: + result.Y = reader.IsDBNull(columnOffset) ? (string?)null : GetValue(reader, columnOffset); + break; + case 2: + result.Z = reader.IsDBNull(columnOffset) ? (double?)null : reader.GetDouble(columnOffset); + break; + case 5: + result.Z = reader.IsDBNull(columnOffset) ? (double?)null : GetValue(reader, columnOffset); + break; + + } + columnOffset++; + + } + return result; + + } + + } + + + } +} +namespace System.Runtime.CompilerServices +{ + // this type is needed by the compiler to implement interceptors - it doesn't need to + // come from the runtime itself, though + + [global::System.Diagnostics.Conditional("DEBUG")] // not needed post-build, so: evaporate + [global::System.AttributeUsage(global::System.AttributeTargets.Method, AllowMultiple = true)] + sealed file class InterceptsLocationAttribute : global::System.Attribute + { + public InterceptsLocationAttribute(string path, int lineNumber, int columnNumber) + { + _ = path; + _ = lineNumber; + _ = columnNumber; + } + } +} \ No newline at end of file diff --git a/test/Dapper.AOT.Test/Interceptors/QueryMultiType.output.netfx.txt b/test/Dapper.AOT.Test/Interceptors/QueryMultiType.output.netfx.txt new file mode 100644 index 0000000..4a9f05b --- /dev/null +++ b/test/Dapper.AOT.Test/Interceptors/QueryMultiType.output.netfx.txt @@ -0,0 +1,4 @@ +Generator produced 1 diagnostics: + +Hidden DAP000 L1 C1 +Dapper.AOT handled 12 of 12 possible call-sites using 12 interceptors, 0 commands and 1 readers diff --git a/test/Dapper.AOT.Test/Interceptors/QueryMultiType.output.txt b/test/Dapper.AOT.Test/Interceptors/QueryMultiType.output.txt new file mode 100644 index 0000000..4a9f05b --- /dev/null +++ b/test/Dapper.AOT.Test/Interceptors/QueryMultiType.output.txt @@ -0,0 +1,4 @@ +Generator produced 1 diagnostics: + +Hidden DAP000 L1 C1 +Dapper.AOT handled 12 of 12 possible call-sites using 12 interceptors, 0 commands and 1 readers diff --git a/test/Dapper.AOT.Test/Interceptors/QueryStrictBind.output.cs b/test/Dapper.AOT.Test/Interceptors/QueryStrictBind.output.cs index 34e4571..e39b026 100644 --- a/test/Dapper.AOT.Test/Interceptors/QueryStrictBind.output.cs +++ b/test/Dapper.AOT.Test/Interceptors/QueryStrictBind.output.cs @@ -89,7 +89,9 @@ private RowFactory0() {} { global::System.Diagnostics.Debug.Assert(tokens.Length >= 5, "Query columns count mismatch"); // pre-defined columns, but still needs type map - for (int i = 0; i < tokens.Length; i++) + int availableColumns = reader.FieldCount - columnOffset; + int tokenCount = global::System.Math.Min(tokens.Length, availableColumns); + for (int i = 0; i < tokenCount; i++) { var type = reader.GetFieldType(columnOffset); tokens[i] = i switch @@ -99,14 +101,23 @@ private RowFactory0() {} 4 => type == typeof(string) ? 4 : 7, _ => -1, }; + columnOffset++; } - return null; + // Initialize remaining tokens to -1 (unmapped) + for (int i = tokenCount; i < tokens.Length; i++) + { + tokens[i] = -1; + } + return tokenCount; } public override global::Foo.Customer Read(global::System.Data.Common.DbDataReader reader, global::System.ReadOnlySpan tokens, int columnOffset, object? state) { + if (columnOffset > 0 && reader.IsDBNull(columnOffset)) return default!; global::Foo.Customer result = new(); - foreach (var token in tokens) + int tokenCount = state is int count ? count : tokens.Length; + for (int i = 0; i < tokenCount; i++) { + var token = tokens[i]; switch (token) { case 0: @@ -152,6 +163,7 @@ private RowFactory1() {} } public override global::Foo.Customer Read(global::System.Data.Common.DbDataReader reader, global::System.ReadOnlySpan tokens, int columnOffset, object? state) { + if (columnOffset > 0 && reader.IsDBNull(columnOffset)) return default!; global::Foo.Customer result = new(); int lim = global::System.Math.Min(tokens.Length, 5); for (int token = 0; token < lim; token++) // query-columns predefined @@ -185,7 +197,9 @@ private sealed class RowFactory2 : global::Dapper.RowFactory tokens, int columnOffset) { - for (int i = 0; i < tokens.Length; i++) + int availableColumns = reader.FieldCount - columnOffset; + int tokenCount = global::System.Math.Min(tokens.Length, availableColumns); + for (int i = 0; i < tokenCount; i++) { int token = -1; var name = reader.GetName(columnOffset); @@ -207,13 +221,21 @@ private RowFactory2() {} columnOffset++; } - return null; + // Initialize remaining tokens to -1 (unmapped) + for (int i = tokenCount; i < tokens.Length; i++) + { + tokens[i] = -1; + } + return tokenCount; } public override global::Foo.Customer Read(global::System.Data.Common.DbDataReader reader, global::System.ReadOnlySpan tokens, int columnOffset, object? state) { + if (columnOffset > 0 && reader.IsDBNull(columnOffset)) return default!; global::Foo.Customer result = new(); - foreach (var token in tokens) + int tokenCount = state is int count ? count : tokens.Length; + for (int i = 0; i < tokenCount; i++) { + var token = tokens[i]; switch (token) { case 0: @@ -242,7 +264,9 @@ private sealed class RowFactory3 : global::Dapper.RowFactory tokens, int columnOffset) { - for (int i = 0; i < tokens.Length; i++) + int availableColumns = reader.FieldCount - columnOffset; + int tokenCount = global::System.Math.Min(tokens.Length, availableColumns); + for (int i = 0; i < tokenCount; i++) { int token = -1; var name = reader.GetName(columnOffset); @@ -264,13 +288,21 @@ private RowFactory3() {} columnOffset++; } - return null; + // Initialize remaining tokens to -1 (unmapped) + for (int i = tokenCount; i < tokens.Length; i++) + { + tokens[i] = -1; + } + return tokenCount; } public override global::Foo.Customer Read(global::System.Data.Common.DbDataReader reader, global::System.ReadOnlySpan tokens, int columnOffset, object? state) { + if (columnOffset > 0 && reader.IsDBNull(columnOffset)) return default!; global::Foo.Customer result = new(); - foreach (var token in tokens) + int tokenCount = state is int count ? count : tokens.Length; + for (int i = 0; i < tokenCount; i++) { + var token = tokens[i]; switch (token) { case 0: diff --git a/test/Dapper.AOT.Test/Interceptors/QueryStrictBind.output.netfx.cs b/test/Dapper.AOT.Test/Interceptors/QueryStrictBind.output.netfx.cs index 34e4571..e39b026 100644 --- a/test/Dapper.AOT.Test/Interceptors/QueryStrictBind.output.netfx.cs +++ b/test/Dapper.AOT.Test/Interceptors/QueryStrictBind.output.netfx.cs @@ -89,7 +89,9 @@ private RowFactory0() {} { global::System.Diagnostics.Debug.Assert(tokens.Length >= 5, "Query columns count mismatch"); // pre-defined columns, but still needs type map - for (int i = 0; i < tokens.Length; i++) + int availableColumns = reader.FieldCount - columnOffset; + int tokenCount = global::System.Math.Min(tokens.Length, availableColumns); + for (int i = 0; i < tokenCount; i++) { var type = reader.GetFieldType(columnOffset); tokens[i] = i switch @@ -99,14 +101,23 @@ private RowFactory0() {} 4 => type == typeof(string) ? 4 : 7, _ => -1, }; + columnOffset++; } - return null; + // Initialize remaining tokens to -1 (unmapped) + for (int i = tokenCount; i < tokens.Length; i++) + { + tokens[i] = -1; + } + return tokenCount; } public override global::Foo.Customer Read(global::System.Data.Common.DbDataReader reader, global::System.ReadOnlySpan tokens, int columnOffset, object? state) { + if (columnOffset > 0 && reader.IsDBNull(columnOffset)) return default!; global::Foo.Customer result = new(); - foreach (var token in tokens) + int tokenCount = state is int count ? count : tokens.Length; + for (int i = 0; i < tokenCount; i++) { + var token = tokens[i]; switch (token) { case 0: @@ -152,6 +163,7 @@ private RowFactory1() {} } public override global::Foo.Customer Read(global::System.Data.Common.DbDataReader reader, global::System.ReadOnlySpan tokens, int columnOffset, object? state) { + if (columnOffset > 0 && reader.IsDBNull(columnOffset)) return default!; global::Foo.Customer result = new(); int lim = global::System.Math.Min(tokens.Length, 5); for (int token = 0; token < lim; token++) // query-columns predefined @@ -185,7 +197,9 @@ private sealed class RowFactory2 : global::Dapper.RowFactory tokens, int columnOffset) { - for (int i = 0; i < tokens.Length; i++) + int availableColumns = reader.FieldCount - columnOffset; + int tokenCount = global::System.Math.Min(tokens.Length, availableColumns); + for (int i = 0; i < tokenCount; i++) { int token = -1; var name = reader.GetName(columnOffset); @@ -207,13 +221,21 @@ private RowFactory2() {} columnOffset++; } - return null; + // Initialize remaining tokens to -1 (unmapped) + for (int i = tokenCount; i < tokens.Length; i++) + { + tokens[i] = -1; + } + return tokenCount; } public override global::Foo.Customer Read(global::System.Data.Common.DbDataReader reader, global::System.ReadOnlySpan tokens, int columnOffset, object? state) { + if (columnOffset > 0 && reader.IsDBNull(columnOffset)) return default!; global::Foo.Customer result = new(); - foreach (var token in tokens) + int tokenCount = state is int count ? count : tokens.Length; + for (int i = 0; i < tokenCount; i++) { + var token = tokens[i]; switch (token) { case 0: @@ -242,7 +264,9 @@ private sealed class RowFactory3 : global::Dapper.RowFactory tokens, int columnOffset) { - for (int i = 0; i < tokens.Length; i++) + int availableColumns = reader.FieldCount - columnOffset; + int tokenCount = global::System.Math.Min(tokens.Length, availableColumns); + for (int i = 0; i < tokenCount; i++) { int token = -1; var name = reader.GetName(columnOffset); @@ -264,13 +288,21 @@ private RowFactory3() {} columnOffset++; } - return null; + // Initialize remaining tokens to -1 (unmapped) + for (int i = tokenCount; i < tokens.Length; i++) + { + tokens[i] = -1; + } + return tokenCount; } public override global::Foo.Customer Read(global::System.Data.Common.DbDataReader reader, global::System.ReadOnlySpan tokens, int columnOffset, object? state) { + if (columnOffset > 0 && reader.IsDBNull(columnOffset)) return default!; global::Foo.Customer result = new(); - foreach (var token in tokens) + int tokenCount = state is int count ? count : tokens.Length; + for (int i = 0; i < tokenCount; i++) { + var token = tokens[i]; switch (token) { case 0: diff --git a/test/Dapper.AOT.Test/Interceptors/RequiredProperties.output.cs b/test/Dapper.AOT.Test/Interceptors/RequiredProperties.output.cs index 063e3ab..2c284bc 100644 --- a/test/Dapper.AOT.Test/Interceptors/RequiredProperties.output.cs +++ b/test/Dapper.AOT.Test/Interceptors/RequiredProperties.output.cs @@ -45,7 +45,9 @@ private sealed class RowFactory0 : global::Dapper.RowFactory private RowFactory0() {} public override object? Tokenize(global::System.Data.Common.DbDataReader reader, global::System.Span tokens, int columnOffset) { - for (int i = 0; i < tokens.Length; i++) + int availableColumns = reader.FieldCount - columnOffset; + int tokenCount = global::System.Math.Min(tokens.Length, availableColumns); + for (int i = 0; i < tokenCount; i++) { int token = -1; var name = reader.GetName(columnOffset); @@ -67,15 +69,23 @@ private RowFactory0() {} columnOffset++; } - return null; + // Initialize remaining tokens to -1 (unmapped) + for (int i = tokenCount; i < tokens.Length; i++) + { + tokens[i] = -1; + } + return tokenCount; } public override global::SomeType Read(global::System.Data.Common.DbDataReader reader, global::System.ReadOnlySpan tokens, int columnOffset, object? state) { + if (columnOffset > 0 && reader.IsDBNull(columnOffset)) return default!; int value0 = default; string? value1 = default; bool value2 = default; - foreach (var token in tokens) + int tokenCount = state is int count ? count : tokens.Length; + for (int i = 0; i < tokenCount; i++) { + var token = tokens[i]; switch (token) { case 0: diff --git a/test/Dapper.AOT.Test/Interceptors/RequiredProperties.output.netfx.cs b/test/Dapper.AOT.Test/Interceptors/RequiredProperties.output.netfx.cs index 063e3ab..2c284bc 100644 --- a/test/Dapper.AOT.Test/Interceptors/RequiredProperties.output.netfx.cs +++ b/test/Dapper.AOT.Test/Interceptors/RequiredProperties.output.netfx.cs @@ -45,7 +45,9 @@ private sealed class RowFactory0 : global::Dapper.RowFactory private RowFactory0() {} public override object? Tokenize(global::System.Data.Common.DbDataReader reader, global::System.Span tokens, int columnOffset) { - for (int i = 0; i < tokens.Length; i++) + int availableColumns = reader.FieldCount - columnOffset; + int tokenCount = global::System.Math.Min(tokens.Length, availableColumns); + for (int i = 0; i < tokenCount; i++) { int token = -1; var name = reader.GetName(columnOffset); @@ -67,15 +69,23 @@ private RowFactory0() {} columnOffset++; } - return null; + // Initialize remaining tokens to -1 (unmapped) + for (int i = tokenCount; i < tokens.Length; i++) + { + tokens[i] = -1; + } + return tokenCount; } public override global::SomeType Read(global::System.Data.Common.DbDataReader reader, global::System.ReadOnlySpan tokens, int columnOffset, object? state) { + if (columnOffset > 0 && reader.IsDBNull(columnOffset)) return default!; int value0 = default; string? value1 = default; bool value2 = default; - foreach (var token in tokens) + int tokenCount = state is int count ? count : tokens.Length; + for (int i = 0; i < tokenCount; i++) { + var token = tokens[i]; switch (token) { case 0: diff --git a/test/Dapper.AOT.Test/Interceptors/RowCountHint.output.cs b/test/Dapper.AOT.Test/Interceptors/RowCountHint.output.cs index c6165e5..e93e89f 100644 --- a/test/Dapper.AOT.Test/Interceptors/RowCountHint.output.cs +++ b/test/Dapper.AOT.Test/Interceptors/RowCountHint.output.cs @@ -76,7 +76,9 @@ private sealed class RowFactory0 : global::Dapper.RowFactory tokens, int columnOffset) { - for (int i = 0; i < tokens.Length; i++) + int availableColumns = reader.FieldCount - columnOffset; + int tokenCount = global::System.Math.Min(tokens.Length, availableColumns); + for (int i = 0; i < tokenCount; i++) { int token = -1; var name = reader.GetName(columnOffset); @@ -98,13 +100,21 @@ private RowFactory0() {} columnOffset++; } - return null; + // Initialize remaining tokens to -1 (unmapped) + for (int i = tokenCount; i < tokens.Length; i++) + { + tokens[i] = -1; + } + return tokenCount; } public override global::Foo.Customer Read(global::System.Data.Common.DbDataReader reader, global::System.ReadOnlySpan tokens, int columnOffset, object? state) { + if (columnOffset > 0 && reader.IsDBNull(columnOffset)) return default!; global::Foo.Customer result = new(); - foreach (var token in tokens) + int tokenCount = state is int count ? count : tokens.Length; + for (int i = 0; i < tokenCount; i++) { + var token = tokens[i]; switch (token) { case 0: diff --git a/test/Dapper.AOT.Test/Interceptors/RowCountHint.output.netfx.cs b/test/Dapper.AOT.Test/Interceptors/RowCountHint.output.netfx.cs index c6165e5..e93e89f 100644 --- a/test/Dapper.AOT.Test/Interceptors/RowCountHint.output.netfx.cs +++ b/test/Dapper.AOT.Test/Interceptors/RowCountHint.output.netfx.cs @@ -76,7 +76,9 @@ private sealed class RowFactory0 : global::Dapper.RowFactory tokens, int columnOffset) { - for (int i = 0; i < tokens.Length; i++) + int availableColumns = reader.FieldCount - columnOffset; + int tokenCount = global::System.Math.Min(tokens.Length, availableColumns); + for (int i = 0; i < tokenCount; i++) { int token = -1; var name = reader.GetName(columnOffset); @@ -98,13 +100,21 @@ private RowFactory0() {} columnOffset++; } - return null; + // Initialize remaining tokens to -1 (unmapped) + for (int i = tokenCount; i < tokens.Length; i++) + { + tokens[i] = -1; + } + return tokenCount; } public override global::Foo.Customer Read(global::System.Data.Common.DbDataReader reader, global::System.ReadOnlySpan tokens, int columnOffset, object? state) { + if (columnOffset > 0 && reader.IsDBNull(columnOffset)) return default!; global::Foo.Customer result = new(); - foreach (var token in tokens) + int tokenCount = state is int count ? count : tokens.Length; + for (int i = 0; i < tokenCount; i++) { + var token = tokens[i]; switch (token) { case 0: diff --git a/test/Dapper.AOT.Test/Interceptors/Single.output.cs b/test/Dapper.AOT.Test/Interceptors/Single.output.cs index 635767a..491f6b1 100644 --- a/test/Dapper.AOT.Test/Interceptors/Single.output.cs +++ b/test/Dapper.AOT.Test/Interceptors/Single.output.cs @@ -146,7 +146,9 @@ private sealed class RowFactory0 : global::Dapper.RowFactory tokens, int columnOffset) { - for (int i = 0; i < tokens.Length; i++) + int availableColumns = reader.FieldCount - columnOffset; + int tokenCount = global::System.Math.Min(tokens.Length, availableColumns); + for (int i = 0; i < tokenCount; i++) { int token = -1; var name = reader.GetName(columnOffset); @@ -168,13 +170,21 @@ private RowFactory0() {} columnOffset++; } - return null; + // Initialize remaining tokens to -1 (unmapped) + for (int i = tokenCount; i < tokens.Length; i++) + { + tokens[i] = -1; + } + return tokenCount; } public override global::Foo.Customer Read(global::System.Data.Common.DbDataReader reader, global::System.ReadOnlySpan tokens, int columnOffset, object? state) { + if (columnOffset > 0 && reader.IsDBNull(columnOffset)) return default!; global::Foo.Customer result = new(); - foreach (var token in tokens) + int tokenCount = state is int count ? count : tokens.Length; + for (int i = 0; i < tokenCount; i++) { + var token = tokens[i]; switch (token) { case 0: diff --git a/test/Dapper.AOT.Test/Interceptors/Single.output.netfx.cs b/test/Dapper.AOT.Test/Interceptors/Single.output.netfx.cs index 635767a..491f6b1 100644 --- a/test/Dapper.AOT.Test/Interceptors/Single.output.netfx.cs +++ b/test/Dapper.AOT.Test/Interceptors/Single.output.netfx.cs @@ -146,7 +146,9 @@ private sealed class RowFactory0 : global::Dapper.RowFactory tokens, int columnOffset) { - for (int i = 0; i < tokens.Length; i++) + int availableColumns = reader.FieldCount - columnOffset; + int tokenCount = global::System.Math.Min(tokens.Length, availableColumns); + for (int i = 0; i < tokenCount; i++) { int token = -1; var name = reader.GetName(columnOffset); @@ -168,13 +170,21 @@ private RowFactory0() {} columnOffset++; } - return null; + // Initialize remaining tokens to -1 (unmapped) + for (int i = tokenCount; i < tokens.Length; i++) + { + tokens[i] = -1; + } + return tokenCount; } public override global::Foo.Customer Read(global::System.Data.Common.DbDataReader reader, global::System.ReadOnlySpan tokens, int columnOffset, object? state) { + if (columnOffset > 0 && reader.IsDBNull(columnOffset)) return default!; global::Foo.Customer result = new(); - foreach (var token in tokens) + int tokenCount = state is int count ? count : tokens.Length; + for (int i = 0; i < tokenCount; i++) { + var token = tokens[i]; switch (token) { case 0: diff --git a/test/Dapper.AOT.Test/Interceptors/Techempower.output.cs b/test/Dapper.AOT.Test/Interceptors/Techempower.output.cs index 2377f02..06893a1 100644 --- a/test/Dapper.AOT.Test/Interceptors/Techempower.output.cs +++ b/test/Dapper.AOT.Test/Interceptors/Techempower.output.cs @@ -73,7 +73,9 @@ private sealed class RowFactory0 : global::Dapper.RowFactory private RowFactory0() {} public override object? Tokenize(global::System.Data.Common.DbDataReader reader, global::System.Span tokens, int columnOffset) { - for (int i = 0; i < tokens.Length; i++) + int availableColumns = reader.FieldCount - columnOffset; + int tokenCount = global::System.Math.Min(tokens.Length, availableColumns); + for (int i = 0; i < tokenCount; i++) { int token = -1; var name = reader.GetName(columnOffset); @@ -92,13 +94,21 @@ private RowFactory0() {} columnOffset++; } - return null; + // Initialize remaining tokens to -1 (unmapped) + for (int i = tokenCount; i < tokens.Length; i++) + { + tokens[i] = -1; + } + return tokenCount; } public override global::World Read(global::System.Data.Common.DbDataReader reader, global::System.ReadOnlySpan tokens, int columnOffset, object? state) { + if (columnOffset > 0 && reader.IsDBNull(columnOffset)) return default!; global::World result = new(); - foreach (var token in tokens) + int tokenCount = state is int count ? count : tokens.Length; + for (int i = 0; i < tokenCount; i++) { + var token = tokens[i]; switch (token) { case 0: diff --git a/test/Dapper.AOT.Test/Interceptors/Techempower.output.netfx.cs b/test/Dapper.AOT.Test/Interceptors/Techempower.output.netfx.cs index 2377f02..06893a1 100644 --- a/test/Dapper.AOT.Test/Interceptors/Techempower.output.netfx.cs +++ b/test/Dapper.AOT.Test/Interceptors/Techempower.output.netfx.cs @@ -73,7 +73,9 @@ private sealed class RowFactory0 : global::Dapper.RowFactory private RowFactory0() {} public override object? Tokenize(global::System.Data.Common.DbDataReader reader, global::System.Span tokens, int columnOffset) { - for (int i = 0; i < tokens.Length; i++) + int availableColumns = reader.FieldCount - columnOffset; + int tokenCount = global::System.Math.Min(tokens.Length, availableColumns); + for (int i = 0; i < tokenCount; i++) { int token = -1; var name = reader.GetName(columnOffset); @@ -92,13 +94,21 @@ private RowFactory0() {} columnOffset++; } - return null; + // Initialize remaining tokens to -1 (unmapped) + for (int i = tokenCount; i < tokens.Length; i++) + { + tokens[i] = -1; + } + return tokenCount; } public override global::World Read(global::System.Data.Common.DbDataReader reader, global::System.ReadOnlySpan tokens, int columnOffset, object? state) { + if (columnOffset > 0 && reader.IsDBNull(columnOffset)) return default!; global::World result = new(); - foreach (var token in tokens) + int tokenCount = state is int count ? count : tokens.Length; + for (int i = 0; i < tokenCount; i++) { + var token = tokens[i]; switch (token) { case 0: diff --git a/test/Dapper.AOT.Test/Interceptors/TopLevelStatements.output.cs b/test/Dapper.AOT.Test/Interceptors/TopLevelStatements.output.cs index 83e5bfe..72a09a2 100644 --- a/test/Dapper.AOT.Test/Interceptors/TopLevelStatements.output.cs +++ b/test/Dapper.AOT.Test/Interceptors/TopLevelStatements.output.cs @@ -44,7 +44,9 @@ private sealed class RowFactory0 : global::Dapper.RowFactory private RowFactory0() {} public override object? Tokenize(global::System.Data.Common.DbDataReader reader, global::System.Span tokens, int columnOffset) { - for (int i = 0; i < tokens.Length; i++) + int availableColumns = reader.FieldCount - columnOffset; + int tokenCount = global::System.Math.Min(tokens.Length, availableColumns); + for (int i = 0; i < tokenCount; i++) { int token = -1; var name = reader.GetName(columnOffset); @@ -63,14 +65,22 @@ private RowFactory0() {} columnOffset++; } - return null; + // Initialize remaining tokens to -1 (unmapped) + for (int i = tokenCount; i < tokens.Length; i++) + { + tokens[i] = -1; + } + return tokenCount; } public override global::SomeThing Read(global::System.Data.Common.DbDataReader reader, global::System.ReadOnlySpan tokens, int columnOffset, object? state) { + if (columnOffset > 0 && reader.IsDBNull(columnOffset)) return default!; int value0 = default; string? value1 = default; - foreach (var token in tokens) + int tokenCount = state is int count ? count : tokens.Length; + for (int i = 0; i < tokenCount; i++) { + var token = tokens[i]; switch (token) { case 0: diff --git a/test/Dapper.AOT.Test/Interceptors/TopLevelStatements.output.netfx.cs b/test/Dapper.AOT.Test/Interceptors/TopLevelStatements.output.netfx.cs index 83e5bfe..72a09a2 100644 --- a/test/Dapper.AOT.Test/Interceptors/TopLevelStatements.output.netfx.cs +++ b/test/Dapper.AOT.Test/Interceptors/TopLevelStatements.output.netfx.cs @@ -44,7 +44,9 @@ private sealed class RowFactory0 : global::Dapper.RowFactory private RowFactory0() {} public override object? Tokenize(global::System.Data.Common.DbDataReader reader, global::System.Span tokens, int columnOffset) { - for (int i = 0; i < tokens.Length; i++) + int availableColumns = reader.FieldCount - columnOffset; + int tokenCount = global::System.Math.Min(tokens.Length, availableColumns); + for (int i = 0; i < tokenCount; i++) { int token = -1; var name = reader.GetName(columnOffset); @@ -63,14 +65,22 @@ private RowFactory0() {} columnOffset++; } - return null; + // Initialize remaining tokens to -1 (unmapped) + for (int i = tokenCount; i < tokens.Length; i++) + { + tokens[i] = -1; + } + return tokenCount; } public override global::SomeThing Read(global::System.Data.Common.DbDataReader reader, global::System.ReadOnlySpan tokens, int columnOffset, object? state) { + if (columnOffset > 0 && reader.IsDBNull(columnOffset)) return default!; int value0 = default; string? value1 = default; - foreach (var token in tokens) + int tokenCount = state is int count ? count : tokens.Length; + for (int i = 0; i < tokenCount; i++) { + var token = tokens[i]; switch (token) { case 0: diff --git a/test/Dapper.AOT.Test/Interceptors/TsqlTips.output.cs b/test/Dapper.AOT.Test/Interceptors/TsqlTips.output.cs index 1be948c..8a3bdae 100644 --- a/test/Dapper.AOT.Test/Interceptors/TsqlTips.output.cs +++ b/test/Dapper.AOT.Test/Interceptors/TsqlTips.output.cs @@ -203,7 +203,9 @@ private sealed class RowFactory0 : global::Dapper.RowFactory tokens, int columnOffset) { - for (int i = 0; i < tokens.Length; i++) + int availableColumns = reader.FieldCount - columnOffset; + int tokenCount = global::System.Math.Min(tokens.Length, availableColumns); + for (int i = 0; i < tokenCount; i++) { int token = -1; var name = reader.GetName(columnOffset); @@ -219,13 +221,21 @@ private RowFactory0() {} columnOffset++; } - return null; + // Initialize remaining tokens to -1 (unmapped) + for (int i = tokenCount; i < tokens.Length; i++) + { + tokens[i] = -1; + } + return tokenCount; } public override global::Foo.Customer Read(global::System.Data.Common.DbDataReader reader, global::System.ReadOnlySpan tokens, int columnOffset, object? state) { + if (columnOffset > 0 && reader.IsDBNull(columnOffset)) return default!; global::Foo.Customer result = new(); - foreach (var token in tokens) + int tokenCount = state is int count ? count : tokens.Length; + for (int i = 0; i < tokenCount; i++) { + var token = tokens[i]; switch (token) { case 0: diff --git a/test/Dapper.AOT.Test/Interceptors/TsqlTips.output.netfx.cs b/test/Dapper.AOT.Test/Interceptors/TsqlTips.output.netfx.cs index 1be948c..8a3bdae 100644 --- a/test/Dapper.AOT.Test/Interceptors/TsqlTips.output.netfx.cs +++ b/test/Dapper.AOT.Test/Interceptors/TsqlTips.output.netfx.cs @@ -203,7 +203,9 @@ private sealed class RowFactory0 : global::Dapper.RowFactory tokens, int columnOffset) { - for (int i = 0; i < tokens.Length; i++) + int availableColumns = reader.FieldCount - columnOffset; + int tokenCount = global::System.Math.Min(tokens.Length, availableColumns); + for (int i = 0; i < tokenCount; i++) { int token = -1; var name = reader.GetName(columnOffset); @@ -219,13 +221,21 @@ private RowFactory0() {} columnOffset++; } - return null; + // Initialize remaining tokens to -1 (unmapped) + for (int i = tokenCount; i < tokens.Length; i++) + { + tokens[i] = -1; + } + return tokenCount; } public override global::Foo.Customer Read(global::System.Data.Common.DbDataReader reader, global::System.ReadOnlySpan tokens, int columnOffset, object? state) { + if (columnOffset > 0 && reader.IsDBNull(columnOffset)) return default!; global::Foo.Customer result = new(); - foreach (var token in tokens) + int tokenCount = state is int count ? count : tokens.Length; + for (int i = 0; i < tokenCount; i++) { + var token = tokens[i]; switch (token) { case 0: diff --git a/test/Dapper.AOT.Test/Verifiers/DAP001.cs b/test/Dapper.AOT.Test/Verifiers/DAP001.cs index fabc72f..d02ff94 100644 --- a/test/Dapper.AOT.Test/Verifiers/DAP001.cs +++ b/test/Dapper.AOT.Test/Verifiers/DAP001.cs @@ -8,7 +8,7 @@ namespace Dapper.AOT.Test.Verifiers; public class DAP001 : Verifier { [Fact] - public Task UnsupportedMethod() => CSVerifyAsync(""" + public Task SingleTypeQueryArity1Supported() => CSVerifyAsync(""" using Dapper; using System.Data.Common; @@ -17,11 +17,98 @@ class SomeCode { public void Foo(DbConnection conn) { - _ = conn.{|#0:Query|}("proc", null!); - _ = conn.Query("proc"); + _ = conn.Query("select 1"); } } - """, DefaultConfig, - [Diagnostic(Diagnostics.UnsupportedMethod).WithLocation(0) - .WithArguments("SqlMapper.Query(IDbConnection, string, Func, object?, IDbTransaction?, bool, string, int?, CommandType?)")]); + """, DefaultConfig, []); + + [Fact] + public Task MultiMapArity3Supported() => CSVerifyAsync(""" + using Dapper; + using System.Data.Common; + + [DapperAot(true)] + class SomeCode + { + public void Foo(DbConnection conn) + { + _ = conn.Query("select 1", (a,b) => a + b); + } + } + """, DefaultConfig, []); + + [Fact] + public Task MultiMapArity4Supported() => CSVerifyAsync(""" + using Dapper; + using System.Data.Common; + + [DapperAot(true)] + class SomeCode + { + public void Foo(DbConnection conn) + { + _ = conn.Query("select 1", (a,b,c) => a + b + c); + } + } + """, DefaultConfig, []); + + [Fact] + public Task MultiMapArity5Supported() => CSVerifyAsync(""" + using Dapper; + using System.Data.Common; + + [DapperAot(true)] + class SomeCode + { + public void Foo(DbConnection conn) + { + _ = conn.Query("select 1", (a,b,c,d) => a + b + c + d); + } + } + """, DefaultConfig, []); + + [Fact] + public Task MultiMapArity6Supported() => CSVerifyAsync(""" + using Dapper; + using System.Data.Common; + + [DapperAot(true)] + class SomeCode + { + public void Foo(DbConnection conn) + { + _ = conn.Query("select 1", (a,b,c,d,e) => a + b + c + d + e); + } + } + """, DefaultConfig, []); + + [Fact] + public Task MultiMapArity7Supported() => CSVerifyAsync(""" + using Dapper; + using System.Data.Common; + + [DapperAot(true)] + class SomeCode + { + public void Foo(DbConnection conn) + { + _ = conn.Query("select 1", (a,b,c,d,e,f) => a + b + c + d + e + f); + } + } + """, DefaultConfig, []); + + [Fact] + public Task MultiMapArity8Supported() => CSVerifyAsync(""" + using Dapper; + using System.Data.Common; + + [DapperAot(true)] + class SomeCode + { + public void Foo(DbConnection conn) + { + _ = conn.Query("select 1", (a,b,c,d,e,f,g) => a + b + c + d + e + f + g); + } + } + """, DefaultConfig, []); } \ No newline at end of file