Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Unbuffered QueryAsnyc multi-mapping throws an InvalidOperationException #596

Open
mburbea opened this issue Sep 1, 2016 · 8 comments
Open
Labels
v3.0 Changes awaiting the next breaking release
Milestone

Comments

@mburbea
Copy link
Contributor

mburbea commented Sep 1, 2016

using (var conn = new SqlConnection(appToken.ConnectionString))
{
    conn.Open();
    var ret = conn.Query("select a='dog',b=3", (string a, int b) => a + b, splitOn: "b", buffered: false);
    Console.WriteLine(string.Join("", ret));
    ret = conn.QueryAsync("select a='dog',b=3", (string a, int b) => a + b, splitOn: "b", buffered: false).Result;
    Console.WriteLine(string.Join("", ret));
}

The call to Query returns dog3, the QueryAsync throws the following exception:

System.InvalidOperationException was unhandled
  HResult=-2146233079
  Message=Invalid attempt to call FieldCount when reader is closed.
  Source=System.Data
  StackTrace:
       at System.Data.SqlClient.SqlDataReader.get_FieldCount()
       at Dapper.SqlMapper.GetColumnHash(IDataReader reader, Int32 startBound, Int32 length)
       at Dapper.SqlMapper.<MultiMapImpl>d__140`8.MoveNext()
       at System.String.Join(String separator, IEnumerable`1 values)
       at Program.Main(String[] args) in 

The store procedure I am calling takes a while to execute and I'd rather let it be called async before I start streaming data out of the query.

@mburbea
Copy link
Contributor Author

mburbea commented Sep 1, 2016

Figured out the cause.
https://github.com/StackExchange/dapper-dot-net/blob/master/Dapper/SqlMapper.Async.cs#L650
The error is that it returns the enumerable, but the using block closes the connection before enumeration begins.

                using (var reader = await ExecuteReaderWithFlagsFallbackAsync(cmd, wasClosed, CommandBehavior.SequentialAccess | CommandBehavior.SingleResult, command.CancellationToken).ConfigureAwait(false))
                {
                    if (!command.Buffered) wasClosed = false; // handing back open reader; rely on command-behavior
                    var results = MultiMapImpl<TFirst, TSecond, TThird, TFourth, TFifth, TSixth, TSeventh, TReturn>(null, CommandDefinition.ForCallback(command.Parameters), map, splitOn, reader, identity, true);
                    return command.Buffered ? results.ToList() : results;
                }

I'll spin up a PR to fix.

mburbea added a commit to mburbea/dapper-dot-net that referenced this issue Sep 1, 2016
See DapperLib#596 for more detail. Originally, the code would dispose the
dataReader that it opened before enumeration would begin. Now when you
dispose the enumerator, it will also close the command, which is line
with other behavior.
Buffered behavior remains consistent. Has no impact on sync version.
@NickCraver NickCraver added the v3.0 Changes awaiting the next breaking release label Jan 28, 2017
@NickCraver NickCraver modified the milestone: v2.0 Jan 30, 2017
@RodrigoMaf
Copy link

Read about and use async/await

Ex.:
using (var conn = new SqlConnection(appToken.ConnectionString))
{
conn.Open();
var ret = conn.Query("select a='dog',b=3", (string a, int b) => a + b, splitOn: "b", buffered: false);
Console.WriteLine(string.Join("", ret));
ret = await conn.QueryAsync("select a='dog',b=3", (string a, int b) => a + b, splitOn: "b", buffered: false).Result;
Console.WriteLine(string.Join("", ret));
}

@0xorial
Copy link

0xorial commented Oct 5, 2020

Any chance to get this patched?

@kaldas
Copy link

kaldas commented Feb 3, 2021

Please - Is there any way I can assist in resolving this?

@NickCraver
Copy link
Member

There isn't a change to be made here overall - a method using a connection and expected data needs to await the results before closing the connection (at the end of the using). Overall: this is a usage problem, and leaving the reader open won't help...since the connection door will be shut at the end of the using regardless.

I'll cleanup this and the PR, but overall: when reading data, that needs to be completed before severing the connection to the data source.

@bgrainger
Copy link
Contributor

I agree with the OP that this is a Dapper bug, not a usage problem. Here's the source of MultiMapAsync:

using var reader = await ExecuteReaderWithFlagsFallbackAsync(cmd, wasClosed, CommandBehavior.SequentialAccess | CommandBehavior.SingleResult, command.CancellationToken).ConfigureAwait(false);
if (!command.Buffered) wasClosed = false; // handing back open reader; rely on command-behavior
var results = MultiMapImpl<TFirst, TSecond, TThird, TFourth, TFifth, TSixth, TSeventh, TReturn>(null, CommandDefinition.ForCallback(command.Parameters), map, splitOn, reader, identity, true);
return command.Buffered ? results.ToList() : results;

On line 912, a DbDataReader is created using a using declaration. On line 915, an IEnumerable<T> from MultiMapImpl is returned. This is an iterator method that's implemented by a state machine. It still has a reference to the DbDataReader passed to the method, but that DbDataReader will be disposed after the return on line 915 is executed.

Thus, the IEnumerable state machine has a reference to a disposed DbDataReader, and reading it will fail.

The solution might be to transfer ownership of the DbDataReader to MultiMapImpl for non-buffered async reads.

@NickCraver
Copy link
Member

@bgrainger Oh geez, I misread the example missing the .Result up there - agreed, reopening this one.

@NickCraver NickCraver reopened this May 8, 2021
@wojtek-viirtue
Copy link

Bump. Is there a reason @mburbea 's PR can't be merged? Is our only recourse to use the sync version?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
v3.0 Changes awaiting the next breaking release
Projects
None yet
Development

No branches or pull requests

7 participants