Commit 07976041 authored by mgravell's avatar mgravell

fix #1277 - make sure that the command is disposed at the correct time when...

fix #1277 - make sure that the command is disposed at the correct time when using ExecuteReaderAsync; this means pushing it into WrappedReader, which was in turn overdue some love; as part of that, add netcoreapp2.1 target so we can expose the best reader possible
parent 191a1cb8
......@@ -5,7 +5,7 @@
<Title>Dapper (Strong Named)</Title>
<Description>A high performance Micro-ORM supporting SQL Server, MySQL, Sqlite, SqlCE, Firebird etc..</Description>
<Authors>Sam Saffron;Marc Gravell;Nick Craver</Authors>
<TargetFrameworks>net451;netstandard1.3;netstandard2.0</TargetFrameworks>
<TargetFrameworks>net451;netstandard1.3;netstandard2.0;netcoreapp2.1</TargetFrameworks>
<SignAssembly>true</SignAssembly>
<PublicSign Condition=" '$(OS)' != 'Windows_NT' ">true</PublicSign>
</PropertyGroup>
......@@ -24,6 +24,10 @@
<PackageReference Include="System.Reflection.TypeExtensions" Version="4.4.0" />
<!-- it would be nice to use System.Data.Common here, but we need SqlClient for SqlDbType in 1.3, and legacy SqlDataRecord API-->
<!--<PackageReference Include="System.Data.Common" Version="4.3.0" />-->
<PackageReference Include="System.Data.SqlClient" Version="4.6.0" />
</ItemGroup>
<ItemGroup Condition="'$(TargetFramework)' == 'netcoreapp2.1'">
<PackageReference Include="System.Data.SqlClient" Version="4.6.0" />
</ItemGroup>
<ItemGroup Condition=" '$(TargetFramework)' == 'netstandard1.3' ">
......
......@@ -5,7 +5,7 @@
<Title>Dapper</Title>
<Description>A high performance Micro-ORM supporting SQL Server, MySQL, Sqlite, SqlCE, Firebird etc..</Description>
<Authors>Sam Saffron;Marc Gravell;Nick Craver</Authors>
<TargetFrameworks>net451;netstandard1.3;netstandard2.0</TargetFrameworks>
<TargetFrameworks>net451;netstandard1.3;netstandard2.0;netcoreapp2.1</TargetFrameworks>
</PropertyGroup>
<ItemGroup Condition="'$(TargetFramework)' == 'net451'">
<Reference Include="System" />
......@@ -19,6 +19,10 @@
<PackageReference Include="System.Reflection.TypeExtensions" Version="4.4.0" />
<!-- it would be nice to use System.Data.Common here, but we need SqlClient for SqlDbType in 1.3, and legacy SqlDataRecord API-->
<!--<PackageReference Include="System.Data.Common" Version="4.3.0" />-->
<PackageReference Include="System.Data.SqlClient" Version="4.6.0" />
</ItemGroup>
<ItemGroup Condition="'$(TargetFramework)' == 'netcoreapp2.1'">
<PackageReference Include="System.Data.SqlClient" Version="4.6.0" />
</ItemGroup>
<ItemGroup Condition=" '$(TargetFramework)' == 'netstandard1.3' ">
......
......@@ -84,7 +84,11 @@ static class StructuredHelper
{
il.Emit(OpCodes.Ldarg_0);
il.Emit(OpCodes.Castclass, type);
#if NETSTANDARD1_3
il.Emit(OpCodes.Ldc_I4, (int)30); // hard-code the known enum value
#else
il.Emit(OpCodes.Ldc_I4, (int)SqlDbType.Structured);
#endif
il.EmitCall(OpCodes.Callvirt, dbType.GetSetMethod(), null);
}
......
......@@ -1101,7 +1101,7 @@ public static async Task<GridReader> QueryMultipleAsync(this IDbConnection cnn,
/// </code>
/// </example>
public static Task<IDataReader> ExecuteReaderAsync(this IDbConnection cnn, string sql, object param = null, IDbTransaction transaction = null, int? commandTimeout = null, CommandType? commandType = null) =>
ExecuteReaderImplAsync(cnn, new CommandDefinition(sql, param, transaction, commandTimeout, commandType, CommandFlags.Buffered), CommandBehavior.Default);
ExecuteWrappedReaderImplAsync(cnn, new CommandDefinition(sql, param, transaction, commandTimeout, commandType, CommandFlags.Buffered), CommandBehavior.Default);
/// <summary>
/// Execute parameterized SQL and return an <see cref="IDataReader"/>.
......@@ -1114,7 +1114,7 @@ public static async Task<GridReader> QueryMultipleAsync(this IDbConnection cnn,
/// or <see cref="T:DataSet"/>.
/// </remarks>
public static Task<IDataReader> ExecuteReaderAsync(this IDbConnection cnn, CommandDefinition command) =>
ExecuteReaderImplAsync(cnn, command, CommandBehavior.Default);
ExecuteWrappedReaderImplAsync(cnn, command, CommandBehavior.Default);
/// <summary>
/// Execute parameterized SQL and return an <see cref="IDataReader"/>.
......@@ -1128,26 +1128,27 @@ public static async Task<GridReader> QueryMultipleAsync(this IDbConnection cnn,
/// or <see cref="T:DataSet"/>.
/// </remarks>
public static Task<IDataReader> ExecuteReaderAsync(this IDbConnection cnn, CommandDefinition command, CommandBehavior commandBehavior) =>
ExecuteReaderImplAsync(cnn, command, commandBehavior);
ExecuteWrappedReaderImplAsync(cnn, command, commandBehavior);
private static async Task<IDataReader> ExecuteReaderImplAsync(IDbConnection cnn, CommandDefinition command, CommandBehavior commandBehavior)
private static async Task<IDataReader> ExecuteWrappedReaderImplAsync(IDbConnection cnn, CommandDefinition command, CommandBehavior commandBehavior)
{
Action<IDbCommand, object> paramReader = GetParameterReader(cnn, ref command);
DbCommand cmd = null;
bool wasClosed = cnn.State == ConnectionState.Closed;
bool wasClosed = cnn.State == ConnectionState.Closed, disposeCommand = true;
try
{
cmd = command.TrySetupAsyncCommand(cnn, paramReader);
if (wasClosed) await cnn.TryOpenAsync(command.CancellationToken).ConfigureAwait(false);
var reader = await ExecuteReaderWithFlagsFallbackAsync(cmd, wasClosed, commandBehavior, command.CancellationToken).ConfigureAwait(false);
wasClosed = false;
return reader;
disposeCommand = false;
return WrappedReader.Create(cmd, reader);
}
finally
{
if (wasClosed) cnn.Close();
cmd?.Dispose();
if (cmd != null && disposeCommand) cmd.Dispose();
}
}
......
......@@ -599,7 +599,7 @@ public static IDataReader ExecuteReader(this IDbConnection cnn, string sql, obje
{
var command = new CommandDefinition(sql, param, transaction, commandTimeout, commandType, CommandFlags.Buffered);
var reader = ExecuteReaderImpl(cnn, ref command, CommandBehavior.Default, out IDbCommand dbcmd);
return new WrappedReader(dbcmd, reader);
return WrappedReader.Create(dbcmd, reader);
}
/// <summary>
......@@ -615,7 +615,7 @@ public static IDataReader ExecuteReader(this IDbConnection cnn, string sql, obje
public static IDataReader ExecuteReader(this IDbConnection cnn, CommandDefinition command)
{
var reader = ExecuteReaderImpl(cnn, ref command, CommandBehavior.Default, out IDbCommand dbcmd);
return new WrappedReader(dbcmd, reader);
return WrappedReader.Create(dbcmd, reader);
}
/// <summary>
......@@ -632,7 +632,7 @@ public static IDataReader ExecuteReader(this IDbConnection cnn, CommandDefinitio
public static IDataReader ExecuteReader(this IDbConnection cnn, CommandDefinition command, CommandBehavior commandBehavior)
{
var reader = ExecuteReaderImpl(cnn, ref command, commandBehavior, out IDbCommand dbcmd);
return new WrappedReader(dbcmd, reader);
return WrappedReader.Create(dbcmd, reader);
}
/// <summary>
......@@ -3696,6 +3696,7 @@ public static void SetTypeName(this DataTable table, string typeName)
public static ICustomQueryParameter AsTableValuedParameter<T>(this IEnumerable<T> list, string typeName = null) where T : IDataRecord =>
new SqlDataRecordListTVPParameter<T>(list, typeName);
/*
/// <summary>
/// Used to pass a IEnumerable&lt;SqlDataRecord&gt; as a TableValuedParameter.
/// </summary>
......@@ -3704,6 +3705,7 @@ public static void SetTypeName(this DataTable table, string typeName)
public static ICustomQueryParameter AsTableValuedParameter(this IEnumerable<Microsoft.SqlServer.Server.SqlDataRecord> list, string typeName = null) =>
new SqlDataRecordListTVPParameter<Microsoft.SqlServer.Server.SqlDataRecord>(list, typeName);
// ^^^ retained to avoid missing-method-exception; can presumably drop in a "major"
*/
// one per thread
[ThreadStatic]
......
using System;
using System.Collections;
using System.Data;
using System.Data.Common;
using System.IO;
using System.Runtime.CompilerServices;
using System.Threading;
using System.Threading.Tasks;
namespace Dapper
{
internal class WrappedReader : IWrappedDataReader
internal sealed class DisposedReader : DbDataReader
{
private IDataReader reader;
private IDbCommand cmd;
internal static readonly DisposedReader Instance = new DisposedReader();
private DisposedReader() { }
public override int Depth => 0;
public override int FieldCount => 0;
public override bool IsClosed => true;
public override bool HasRows => false;
public override int RecordsAffected => -1;
public override int VisibleFieldCount => 0;
[MethodImpl(MethodImplOptions.NoInlining)]
private static T ThrowDisposed<T>() => throw new ObjectDisposedException(nameof(DbDataReader));
[MethodImpl(MethodImplOptions.NoInlining)]
private async static Task<T> ThrowDisposedAsync<T>()
{
var result = ThrowDisposed<T>();
await Task.Yield(); // will never hit this - already thrown and handled
return result;
}
#if !NETSTANDARD1_3
public override void Close() { }
public override DataTable GetSchemaTable() => ThrowDisposed<DataTable>();
public override object InitializeLifetimeService() => ThrowDisposed<object>();
#endif
protected override void Dispose(bool disposing) { }
#if NET451
public override System.Runtime.Remoting.ObjRef CreateObjRef(Type requestedType) => ThrowDisposed<System.Runtime.Remoting.ObjRef>();
#endif
public override bool GetBoolean(int ordinal) => ThrowDisposed<bool>();
public override long GetBytes(int ordinal, long dataOffset, byte[] buffer, int bufferOffset, int length) => ThrowDisposed<long>();
public override float GetFloat(int ordinal) => ThrowDisposed<float>();
public override short GetInt16(int ordinal) => ThrowDisposed<short>();
public override byte GetByte(int ordinal) => ThrowDisposed<byte>();
public override char GetChar(int ordinal) => ThrowDisposed<char>();
public override long GetChars(int ordinal, long dataOffset, char[] buffer, int bufferOffset, int length) => ThrowDisposed<long>();
public override string GetDataTypeName(int ordinal) => ThrowDisposed<string>();
public override DateTime GetDateTime(int ordinal) => ThrowDisposed<DateTime>();
protected override DbDataReader GetDbDataReader(int ordinal) => ThrowDisposed<DbDataReader>();
public override decimal GetDecimal(int ordinal) => ThrowDisposed<decimal>();
public override double GetDouble(int ordinal) => ThrowDisposed<double>();
public override IEnumerator GetEnumerator() => ThrowDisposed<IEnumerator>();
public override Type GetFieldType(int ordinal) => ThrowDisposed<Type>();
public override T GetFieldValue<T>(int ordinal) => ThrowDisposed<T>();
public override Task<T> GetFieldValueAsync<T>(int ordinal, CancellationToken cancellationToken) => ThrowDisposedAsync<T>();
public override Guid GetGuid(int ordinal) => ThrowDisposed<Guid>();
public override int GetInt32(int ordinal) => ThrowDisposed<int>();
public override long GetInt64(int ordinal) => ThrowDisposed<long>();
public override string GetName(int ordinal) => ThrowDisposed<string>();
public override int GetOrdinal(string name) => ThrowDisposed<int>();
public override Type GetProviderSpecificFieldType(int ordinal) => ThrowDisposed<Type>();
public override object GetProviderSpecificValue(int ordinal) => ThrowDisposed<object>();
public override int GetProviderSpecificValues(object[] values) => ThrowDisposed<int>();
public override Stream GetStream(int ordinal) => ThrowDisposed<Stream>();
public override string GetString(int ordinal) => ThrowDisposed<string>();
public override TextReader GetTextReader(int ordinal) => ThrowDisposed<TextReader>();
public override object GetValue(int ordinal) => ThrowDisposed<object>();
public override int GetValues(object[] values) => ThrowDisposed<int>();
public override bool IsDBNull(int ordinal) => ThrowDisposed<bool>();
public override Task<bool> IsDBNullAsync(int ordinal, CancellationToken cancellationToken) => ThrowDisposedAsync<bool>();
public override bool NextResult() => ThrowDisposed<bool>();
public override bool Read() => ThrowDisposed<bool>();
public override Task<bool> NextResultAsync(CancellationToken cancellationToken) => ThrowDisposedAsync<bool>();
public override Task<bool> ReadAsync(CancellationToken cancellationToken) => ThrowDisposedAsync<bool>();
public override object this[int ordinal] => ThrowDisposed<object>();
public override object this[string name] => ThrowDisposed<object>();
}
public IDataReader Reader
internal static class WrappedReader
{
// the purpose of wrapping here is to allow closing a reader to *also* close
// the command, without having to explicitly hand the command back to the
// caller; what that actually looks like depends on what we get: if we are
// given a DbDataReader, we will surface a DbDataReader; if we are given
// a raw IDataReader, we will surface that; and if null: null
public static IDataReader Create(IDbCommand cmd, IDataReader reader)
{
get
{
var tmp = reader;
if (tmp == null) throw new ObjectDisposedException(GetType().Name);
return tmp;
}
if (cmd == null) return reader; // no need to wrap if no command
if (reader is DbDataReader dbr) return new DbWrappedReader(cmd, dbr);
if (reader != null) return new BasicWrappedReader(cmd, reader);
cmd.Dispose();
return null; // GIGO
}
}
internal sealed class DbWrappedReader : DbDataReader, IWrappedDataReader
{
private DbDataReader _reader;
private IDbCommand _cmd;
IDataReader IWrappedDataReader.Reader => _reader;
IDbCommand IWrappedDataReader.Command
IDbCommand IWrappedDataReader.Command => _cmd;
public DbWrappedReader(IDbCommand cmd, DbDataReader reader)
{
get
_cmd = cmd;
_reader = reader;
}
public override bool HasRows => _reader.HasRows;
#if !NETSTANDARD1_3
public override void Close() => _reader.Close();
public override DataTable GetSchemaTable() => _reader.GetSchemaTable();
public override object InitializeLifetimeService() => _reader.InitializeLifetimeService();
#endif
#if NET451
public override System.Runtime.Remoting.ObjRef CreateObjRef(Type requestedType) => _reader.CreateObjRef(requestedType);
#endif
public override int Depth => _reader.Depth;
public override bool IsClosed => _reader.IsClosed;
public override bool NextResult() => _reader.NextResult();
public override bool Read() => _reader.Read();
public override int RecordsAffected => _reader.RecordsAffected;
protected override void Dispose(bool disposing)
{
if (disposing)
{
var tmp = cmd;
if (tmp == null) throw new ObjectDisposedException(GetType().Name);
return tmp;
#if !NETSTANDARD1_3
_reader.Close();
#endif
_reader.Dispose();
_reader = DisposedReader.Instance; // all future ops are no-ops
_cmd?.Dispose();
_cmd = null;
}
}
public override int FieldCount => _reader.FieldCount;
public override bool GetBoolean(int i) => _reader.GetBoolean(i);
public override byte GetByte(int i) => _reader.GetByte(i);
public override long GetBytes(int i, long fieldOffset, byte[] buffer, int bufferoffset, int length) =>
_reader.GetBytes(i, fieldOffset, buffer, bufferoffset, length);
public override char GetChar(int i) => _reader.GetChar(i);
public override long GetChars(int i, long fieldoffset, char[] buffer, int bufferoffset, int length) =>
_reader.GetChars(i, fieldoffset, buffer, bufferoffset, length);
public override string GetDataTypeName(int i) => _reader.GetDataTypeName(i);
public override DateTime GetDateTime(int i) => _reader.GetDateTime(i);
public override decimal GetDecimal(int i) => _reader.GetDecimal(i);
public override double GetDouble(int i) => _reader.GetDouble(i);
public override Type GetFieldType(int i) => _reader.GetFieldType(i);
public override float GetFloat(int i) => _reader.GetFloat(i);
public override Guid GetGuid(int i) => _reader.GetGuid(i);
public override short GetInt16(int i) => _reader.GetInt16(i);
public override int GetInt32(int i) => _reader.GetInt32(i);
public override long GetInt64(int i) => _reader.GetInt64(i);
public override string GetName(int i) => _reader.GetName(i);
public override int GetOrdinal(string name) => _reader.GetOrdinal(name);
public override string GetString(int i) => _reader.GetString(i);
public override object GetValue(int i) => _reader.GetValue(i);
public override int GetValues(object[] values) => _reader.GetValues(values);
public override bool IsDBNull(int i) => _reader.IsDBNull(i);
public override object this[string name] => _reader[name];
public override object this[int i] => _reader[i];
public override T GetFieldValue<T>(int ordinal) => _reader.GetFieldValue<T>(ordinal);
public override Task<T> GetFieldValueAsync<T>(int ordinal, CancellationToken cancellationToken) => _reader.GetFieldValueAsync<T>(ordinal, cancellationToken);
public override IEnumerator GetEnumerator() => _reader.GetEnumerator();
public override Type GetProviderSpecificFieldType(int ordinal) => _reader.GetProviderSpecificFieldType(ordinal);
public override object GetProviderSpecificValue(int ordinal) => _reader.GetProviderSpecificValue(ordinal);
public override int GetProviderSpecificValues(object[] values) => _reader.GetProviderSpecificValues(values);
public override Stream GetStream(int ordinal) => _reader.GetStream(ordinal);
public override TextReader GetTextReader(int ordinal) => _reader.GetTextReader(ordinal);
public override Task<bool> IsDBNullAsync(int ordinal, CancellationToken cancellationToken) => _reader.IsDBNullAsync(ordinal, cancellationToken);
public override Task<bool> NextResultAsync(CancellationToken cancellationToken) => _reader.NextResultAsync(cancellationToken);
public override Task<bool> ReadAsync(CancellationToken cancellationToken) => _reader.ReadAsync(cancellationToken);
public override int VisibleFieldCount => _reader.VisibleFieldCount;
protected override DbDataReader GetDbDataReader(int ordinal) => (((IDataReader)_reader).GetData(ordinal) as DbDataReader) ?? throw new NotSupportedException();
}
internal class BasicWrappedReader : IWrappedDataReader
{
private IDataReader _reader;
private IDbCommand _cmd;
IDataReader IWrappedDataReader.Reader => _reader;
IDbCommand IWrappedDataReader.Command => _cmd;
public WrappedReader(IDbCommand cmd, IDataReader reader)
public BasicWrappedReader(IDbCommand cmd, IDataReader reader)
{
this.cmd = cmd;
this.reader = reader;
_cmd = cmd;
_reader = reader;
}
void IDataReader.Close() => reader?.Close();
void IDataReader.Close() => _reader.Close();
int IDataReader.Depth => Reader.Depth;
int IDataReader.Depth => _reader.Depth;
DataTable IDataReader.GetSchemaTable() => Reader.GetSchemaTable();
DataTable IDataReader.GetSchemaTable() => _reader.GetSchemaTable();
bool IDataReader.IsClosed => reader?.IsClosed ?? true;
bool IDataReader.IsClosed => _reader.IsClosed;
bool IDataReader.NextResult() => Reader.NextResult();
bool IDataReader.NextResult() => _reader.NextResult();
bool IDataReader.Read() => Reader.Read();
bool IDataReader.Read() => _reader.Read();
int IDataReader.RecordsAffected => Reader.RecordsAffected;
int IDataReader.RecordsAffected => _reader.RecordsAffected;
void IDisposable.Dispose()
{
reader?.Close();
reader?.Dispose();
reader = null;
cmd?.Dispose();
cmd = null;
_reader.Close();
_reader.Dispose();
_reader = DisposedReader.Instance;
_cmd?.Dispose();
_cmd = null;
}
int IDataRecord.FieldCount => Reader.FieldCount;
int IDataRecord.FieldCount => _reader.FieldCount;
bool IDataRecord.GetBoolean(int i) => Reader.GetBoolean(i);
bool IDataRecord.GetBoolean(int i) => _reader.GetBoolean(i);
byte IDataRecord.GetByte(int i) => Reader.GetByte(i);
byte IDataRecord.GetByte(int i) => _reader.GetByte(i);
long IDataRecord.GetBytes(int i, long fieldOffset, byte[] buffer, int bufferoffset, int length) =>
Reader.GetBytes(i, fieldOffset, buffer, bufferoffset, length);
_reader.GetBytes(i, fieldOffset, buffer, bufferoffset, length);
char IDataRecord.GetChar(int i) => Reader.GetChar(i);
char IDataRecord.GetChar(int i) => _reader.GetChar(i);
long IDataRecord.GetChars(int i, long fieldoffset, char[] buffer, int bufferoffset, int length) =>
Reader.GetChars(i, fieldoffset, buffer, bufferoffset, length);
_reader.GetChars(i, fieldoffset, buffer, bufferoffset, length);
IDataReader IDataRecord.GetData(int i) => Reader.GetData(i);
IDataReader IDataRecord.GetData(int i) => _reader.GetData(i);
string IDataRecord.GetDataTypeName(int i) => Reader.GetDataTypeName(i);
string IDataRecord.GetDataTypeName(int i) => _reader.GetDataTypeName(i);
DateTime IDataRecord.GetDateTime(int i) => Reader.GetDateTime(i);
DateTime IDataRecord.GetDateTime(int i) => _reader.GetDateTime(i);
decimal IDataRecord.GetDecimal(int i) => Reader.GetDecimal(i);
decimal IDataRecord.GetDecimal(int i) => _reader.GetDecimal(i);
double IDataRecord.GetDouble(int i) => Reader.GetDouble(i);
double IDataRecord.GetDouble(int i) => _reader.GetDouble(i);
Type IDataRecord.GetFieldType(int i) => Reader.GetFieldType(i);
Type IDataRecord.GetFieldType(int i) => _reader.GetFieldType(i);
float IDataRecord.GetFloat(int i) => Reader.GetFloat(i);
float IDataRecord.GetFloat(int i) => _reader.GetFloat(i);
Guid IDataRecord.GetGuid(int i) => Reader.GetGuid(i);
Guid IDataRecord.GetGuid(int i) => _reader.GetGuid(i);
short IDataRecord.GetInt16(int i) => Reader.GetInt16(i);
short IDataRecord.GetInt16(int i) => _reader.GetInt16(i);
int IDataRecord.GetInt32(int i) => Reader.GetInt32(i);
int IDataRecord.GetInt32(int i) => _reader.GetInt32(i);
long IDataRecord.GetInt64(int i) => Reader.GetInt64(i);
long IDataRecord.GetInt64(int i) => _reader.GetInt64(i);
string IDataRecord.GetName(int i) => Reader.GetName(i);
string IDataRecord.GetName(int i) => _reader.GetName(i);
int IDataRecord.GetOrdinal(string name) => Reader.GetOrdinal(name);
int IDataRecord.GetOrdinal(string name) => _reader.GetOrdinal(name);
string IDataRecord.GetString(int i) => Reader.GetString(i);
string IDataRecord.GetString(int i) => _reader.GetString(i);
object IDataRecord.GetValue(int i) => Reader.GetValue(i);
object IDataRecord.GetValue(int i) => _reader.GetValue(i);
int IDataRecord.GetValues(object[] values) => Reader.GetValues(values);
int IDataRecord.GetValues(object[] values) => _reader.GetValues(values);
bool IDataRecord.IsDBNull(int i) => Reader.IsDBNull(i);
bool IDataRecord.IsDBNull(int i) => _reader.IsDBNull(i);
object IDataRecord.this[string name] => Reader[name];
object IDataRecord.this[string name] => _reader[name];
object IDataRecord.this[int i] => Reader[i];
object IDataRecord.this[int i] => _reader[i];
}
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment