diff --git a/Dapper/SqlMapper.GridReader.cs b/Dapper/SqlMapper.GridReader.cs index 1370f7cc9..208382ebb 100644 --- a/Dapper/SqlMapper.GridReader.cs +++ b/Dapper/SqlMapper.GridReader.cs @@ -417,7 +417,35 @@ private IEnumerable ReadDeferred(int index, Func des /// Has the underlying reader been consumed? /// /// This also reports true if the current grid is actively being consumed - public bool IsConsumed => (_resultIndexAndConsumedFlag & CONSUMED_FLAG) != 0; + public bool IsConsumed + { + get + { + SkipLeadingEmptyResults(); + return (_resultIndexAndConsumedFlag & CONSUMED_FLAG) != 0; + } + } + + private bool _checkedLeadingResults; + + // A reader positioned on a result without columns (for example a procedure that + // selects in a loop which never iterated) has nothing to read. Skip such results; only + // when no further result exists is the grid consumed. A zero-column result followed by a + // real result set must not be reported as consumed. + private void SkipLeadingEmptyResults() + { + if (_checkedLeadingResults) return; + _checkedLeadingResults = true; + if (reader is null || reader.IsClosed || _resultIndexAndConsumedFlag != 0) return; + while (reader.FieldCount == 0) + { + if (!reader.NextResult()) + { + _resultIndexAndConsumedFlag |= CONSUMED_FLAG; + return; + } + } + } /// /// The command associated with the reader diff --git a/Directory.Packages.props b/Directory.Packages.props index 2f91a3cca..8f2772419 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -35,6 +35,7 @@ + diff --git a/tests/Dapper.Tests/Dapper.Tests.csproj b/tests/Dapper.Tests/Dapper.Tests.csproj index 93b498618..09947a89a 100644 --- a/tests/Dapper.Tests/Dapper.Tests.csproj +++ b/tests/Dapper.Tests/Dapper.Tests.csproj @@ -31,8 +31,9 @@ - - + + + diff --git a/tests/Dapper.Tests/FakeDbTests.Issue751.cs b/tests/Dapper.Tests/FakeDbTests.Issue751.cs new file mode 100644 index 000000000..898a09f72 --- /dev/null +++ b/tests/Dapper.Tests/FakeDbTests.Issue751.cs @@ -0,0 +1,61 @@ +#if !NET481 +using System.Collections.Generic; +using System.Linq; +using pengdows.crud.fakeDb; +using Xunit; + +namespace Dapper.Tests +{ + public sealed class FakeDbIssue751Tests + { + [Fact] + public void IsConsumedIsTrueWhenReaderHasNoResultSets() + { + using var conn = new fakeDbConnection(new FakeDataStore()); + conn.Open(); + + using var grid = conn.QueryMultiple("exec NoResultSets"); + + Assert.True(grid.IsConsumed); + } + + [Fact] + public void IsConsumedIsFalseWhenEmptyResultPrecedesRealResultSet() + { + using var conn = new fakeDbConnection(new FakeDataStore()); + conn.EnqueueMultiResultReader(new[] + { + Enumerable.Empty>(), + new[] { new Dictionary { ["Value"] = 7 } } + }); + conn.Open(); + + using var grid = conn.QueryMultiple("insert x; select 7"); + + Assert.False(grid.IsConsumed); + Assert.Equal(7, grid.Read().Single()); + Assert.True(grid.IsConsumed); + } + + [Fact] + public void IsConsumedIsFalseForNonEmptyFirstResultAndTrueAfterLast() + { + using var conn = new fakeDbConnection(new FakeDataStore()); + conn.EnqueueMultiResultReader(new[] + { + new[] { new Dictionary { ["Value"] = 1 } }, + new[] { new Dictionary { ["Value"] = 2 } } + }); + conn.Open(); + + using var grid = conn.QueryMultiple("select 1; select 2"); + + Assert.False(grid.IsConsumed); + Assert.Equal(1, grid.Read().Single()); + Assert.False(grid.IsConsumed); + Assert.Equal(2, grid.Read().Single()); + Assert.True(grid.IsConsumed); + } + } +} +#endif