Search code examples
c#sql-serverdapper

Is there an option to do multi mapping for QueryFirstAsync or QueryFirstOrDefaultAsync in Dapper?


I am trying to map the object from sql to multiple objects in C#. How do I multi map in the case of having QueryFirstAsync and QueryFirstOrDefaultAsync?

I have tried similar to the process in QueryAsync or Query.

ClassA record = 
await this.dbConnection.QueryFirstAsync<ClassA, ClassB, ClassA>(
sql,                   
(a, b) =>
{
  a.Id = b.Id;                                                                           
  return a;
},
splitOn: "Id",
param: new
{
  memberId
});

I expect the successful build but it does not work for QueryFirstAsync or QueryFirstOrDefaultAsync.


Solution

  • Current Issue:

    You are trying to use multi mapping with the dapper functions, QueryFirstAsync and QueryFirstOrDefaultAsync, but none of their overload supports Mutli-Mapping as done by QueryAsync, pasting the definition from the Dapper code:

    public static Task<IEnumerable<TReturn>> QueryAsync<TFirst, TSecond, TReturn>(this IDbConnection cnn, 
    CommandDefinition command, Func<TFirst, TSecond, TReturn> map, string splitOn = "Id") => 
    MultiMapAsync<TFirst, TSecond, DontMap, DontMap, DontMap, DontMap, DontMap, TReturn>(cnn, 
    command, map, splitOn);
    

    which calls MultiMapAsync, which utilize the SplitOn value to create multi map default being Id

    private static async Task<IEnumerable<TReturn>> MultiMapAsync<TReturn>(this IDbConnection cnn, CommandDefinition command, Type[] types, Func<object[], TReturn> map, string splitOn)
            {
                if (types.Length < 1)
                {
                    throw new ArgumentException("you must provide at least one type to deserialize");
                }
    
                object param = command.Parameters;
                var identity = new Identity(command.CommandText, command.CommandType, cnn, types[0], param?.GetType(), types);
                var info = GetCacheInfo(identity, param, command.AddToCache);
                bool wasClosed = cnn.State == ConnectionState.Closed;
                try
                {
                    if (wasClosed) await cnn.TryOpenAsync(command.CancellationToken).ConfigureAwait(false);
                    using (var cmd = command.TrySetupAsyncCommand(cnn, info.ParamReader))
                    using (var reader = await ExecuteReaderWithFlagsFallbackAsync(cmd, wasClosed, CommandBehavior.SequentialAccess | CommandBehavior.SingleResult, command.CancellationToken).ConfigureAwait(false))
                    {
                        var results = MultiMapImpl(null, default(CommandDefinition), types, map, splitOn, reader, identity, true);
                        return command.Buffered ? results.ToList() : results;
                    }
                }
                finally
                {
                    if (wasClosed) cnn.Close();
                }
            }
    

    Solution:

    Use standard QueryAsync, which has an overload which takes SplitOn and on the final result call FirstorDefault, since the result of the Dapper query is IEnumerable<T>, therefore any of the standard Linq extensions methods can be called