Commit 61e965ee authored by Marc Gravell's avatar Marc Gravell Committed by GitHub

Merge pull request #602 from StackExchange/#563

Make CommandBehavior optimizations more configurable
parents 6bca1952 5f3d178d
......@@ -771,6 +771,17 @@ public async Task Issue1281_DataReaderOutOfOrderAsync()
reader.Read().IsFalse();
}
}
[Fact]
public async Task Issue563_QueryAsyncShouldThrowException()
{
try
{
var data = (await connection.QueryAsync<int>("select 1 union all select 2; RAISERROR('after select', 16, 1);")).ToList();
Assert.Fail();
}
catch (SqlException ex) when (ex.Message == "after select") { }
}
}
}
#endif
\ No newline at end of file
......@@ -218,7 +218,7 @@ public static Task<T> QuerySingleOrDefaultAsync<T>(this IDbConnection cnn, Comma
private static Task<DbDataReader> ExecuteReaderWithFlagsFallbackAsync(DbCommand cmd, bool wasClosed, CommandBehavior behavior, CancellationToken cancellationToken)
{
var task = cmd.ExecuteReaderAsync(GetBehavior(wasClosed, behavior), cancellationToken);
if (task.Status == TaskStatus.Faulted && DisableCommandBehaviorOptimizations(behavior, task.Exception.InnerException))
if (task.Status == TaskStatus.Faulted && Settings.DisableCommandBehaviorOptimizations(behavior, task.Exception.InnerException))
{ // we can retry; this time it will have different flags
task = cmd.ExecuteReaderAsync(GetBehavior(wasClosed, behavior), cancellationToken);
}
......
namespace Dapper
using System;
using System.Data;
namespace Dapper
{
partial class SqlMapper
{
......@@ -7,6 +10,47 @@ partial class SqlMapper
/// </summary>
public static class Settings
{
// disable single result by default; prevents errors AFTER the select being detected properly
const CommandBehavior DefaultAllowedCommandBehaviors = ~CommandBehavior.SingleResult;
internal static CommandBehavior AllowedCommandBehaviors { get; private set; } = DefaultAllowedCommandBehaviors;
private static void SetAllowedCommandBehaviors(CommandBehavior behavior, bool enabled)
{
if (enabled) AllowedCommandBehaviors |= behavior;
else AllowedCommandBehaviors &= ~behavior;
}
/// <summary>
/// Gets or sets whether Dapper should use the CommandBehavior.SingleResult optimization
/// </summary>
/// <remarks>Note that a consequence of enabling this option is that errors that happen <b>after</b> the first select may not be reported</remarks>
public static bool UseSingleResultOptimization
{
get { return (AllowedCommandBehaviors & CommandBehavior.SingleResult) != 0; }
set { SetAllowedCommandBehaviors(CommandBehavior.SingleResult, value); }
}
/// <summary>
/// Gets or sets whether Dapper should use the CommandBehavior.SingleRow optimization
/// </summary>
/// <remarks>Note that on some DB providers this optimization can have adverse performance impact</remarks>
public static bool UseSingleRowOptimization
{
get { return (AllowedCommandBehaviors & CommandBehavior.SingleRow) != 0; }
set { SetAllowedCommandBehaviors(CommandBehavior.SingleRow, value); }
}
internal static bool DisableCommandBehaviorOptimizations(CommandBehavior behavior, Exception ex)
{
if (AllowedCommandBehaviors == DefaultAllowedCommandBehaviors
&& (behavior & (CommandBehavior.SingleResult | CommandBehavior.SingleRow)) != 0)
{
if (ex.Message.Contains(nameof(CommandBehavior.SingleResult))
|| ex.Message.Contains(nameof(CommandBehavior.SingleRow)))
{ // some providers just just allow these, so: try again without them and stop issuing them
SetAllowedCommandBehaviors(CommandBehavior.SingleResult | CommandBehavior.SingleRow, false);
return true;
}
}
return false;
}
static Settings()
{
SetDefaults();
......
......@@ -122,7 +122,7 @@ private static void PurgeQueryCacheByType(Type type)
}
/// <summary>
/// Return a count of all the cached queries by dapper
/// Return a count of all the cached queries by Dapper
/// </summary>
/// <returns></returns>
public static int GetCachedSQLCount()
......@@ -131,7 +131,7 @@ public static int GetCachedSQLCount()
}
/// <summary>
/// Return a list of all the queries cached by dapper
/// Return a list of all the queries cached by Dapper
/// </summary>
/// <param name="ignoreHitCountAbove"></param>
/// <returns></returns>
......@@ -235,9 +235,7 @@ private static void ResetTypeHandlers(bool clone)
#endif
AddTypeHandlerImpl(typeof(XmlDocument), new XmlDocumentHandler(), clone);
AddTypeHandlerImpl(typeof(XDocument), new XDocumentHandler(), clone);
AddTypeHandlerImpl(typeof(XElement), new XElementHandler(), clone);
allowedCommandBehaviors = DefaultAllowedCommandBehaviors;
AddTypeHandlerImpl(typeof(XElement), new XElementHandler(), clone);
}
#if !COREFX
[MethodImpl(MethodImplOptions.NoInlining)]
......@@ -912,7 +910,7 @@ private static IDataReader ExecuteReaderWithFlagsFallback(IDbCommand cmd, bool w
}
catch (ArgumentException ex)
{ // thanks, Sqlite!
if (DisableCommandBehaviorOptimizations(behavior, ex))
if (Settings.DisableCommandBehaviorOptimizations(behavior, ex))
{
// we can retry; this time it will have different flags
return cmd.ExecuteReader(GetBehavior(wasClosed, behavior));
......@@ -1324,25 +1322,10 @@ public static IEnumerable<TReturn> Query<TReturn>(this IDbConnection cnn, string
}
}
}
const CommandBehavior DefaultAllowedCommandBehaviors = ~((CommandBehavior)0);
static CommandBehavior allowedCommandBehaviors = DefaultAllowedCommandBehaviors;
private static bool DisableCommandBehaviorOptimizations(CommandBehavior behavior, Exception ex)
{
if(allowedCommandBehaviors == DefaultAllowedCommandBehaviors
&& (behavior & (CommandBehavior.SingleResult | CommandBehavior.SingleRow)) != 0)
{
if (ex.Message.Contains(nameof(CommandBehavior.SingleResult))
|| ex.Message.Contains(nameof(CommandBehavior.SingleRow)))
{ // some providers just just allow these, so: try again without them and stop issuing them
allowedCommandBehaviors = ~(CommandBehavior.SingleResult | CommandBehavior.SingleRow);
return true;
}
}
return false;
}
private static CommandBehavior GetBehavior(bool close, CommandBehavior @default)
{
return (close ? (@default | CommandBehavior.CloseConnection) : @default) & allowedCommandBehaviors;
return (close ? (@default | CommandBehavior.CloseConnection) : @default) & Settings.AllowedCommandBehaviors;
}
static IEnumerable<TReturn> MultiMapImpl<TReturn>(this IDbConnection cnn, CommandDefinition command, Type[] types, Func<object[], TReturn> map, string splitOn, IDataReader reader, Identity identity, bool finalize)
{
......
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