Skip to content

Commit

Permalink
Add FetchSize global setting (#1946)
Browse files Browse the repository at this point in the history
* fix #1945

* PR number

* docs
mgravell authored Aug 17, 2023
1 parent 33090c0 commit 8c9b4bd
Showing 4 changed files with 47 additions and 4 deletions.
22 changes: 19 additions & 3 deletions Dapper/CommandDefinition.cs
Original file line number Diff line number Diff line change
@@ -131,6 +131,9 @@ internal IDbCommand SetupCommand(IDbConnection cnn, Action<IDbCommand, object> p

private static SqlMapper.Link<Type, Action<IDbCommand>> commandInitCache;

internal static void ResetCommandInitCache()
=> SqlMapper.Link<Type, Action<IDbCommand>>.Clear(ref commandInitCache);

private static Action<IDbCommand> GetInit(Type commandType)
{
if (commandType == null)
@@ -141,29 +144,42 @@ private static Action<IDbCommand> GetInit(Type commandType)
}
var bindByName = GetBasicPropertySetter(commandType, "BindByName", typeof(bool));
var initialLongFetchSize = GetBasicPropertySetter(commandType, "InitialLONGFetchSize", typeof(int));
var fetchSize = GetBasicPropertySetter(commandType, "FetchSize", typeof(long));

action = null;
if (bindByName != null || initialLongFetchSize != null)
if (bindByName is not null || initialLongFetchSize is not null || fetchSize is not null)
{
var method = new DynamicMethod(commandType.Name + "_init", null, new Type[] { typeof(IDbCommand) });
var il = method.GetILGenerator();

if (bindByName != null)
if (bindByName is not null)
{
// .BindByName = true
il.Emit(OpCodes.Ldarg_0);
il.Emit(OpCodes.Castclass, commandType);
il.Emit(OpCodes.Ldc_I4_1);
il.EmitCall(OpCodes.Callvirt, bindByName, null);
}
if (initialLongFetchSize != null)
if (initialLongFetchSize is not null)
{
// .InitialLONGFetchSize = -1
il.Emit(OpCodes.Ldarg_0);
il.Emit(OpCodes.Castclass, commandType);
il.Emit(OpCodes.Ldc_I4_M1);
il.EmitCall(OpCodes.Callvirt, initialLongFetchSize, null);
}
if (fetchSize is not null)
{
var snapshot = SqlMapper.Settings.FetchSize;

This comment has been minimized.

Copy link
@jtylerc

jtylerc Jul 11, 2024

Thank you for making this improvement. We found this change to be useful when executing Oracle queries where we were returning large columns. In a few cases we've seen 100x improvement in query speed. However, I wonder about the implementation here where FetchSize can only be set globally. Technically it could be set on each command and it seems this would be more appropriate. Why not just add another constructor argument to the CommandDefinition class so that the fetch size could be more carefully applied as the situation called for it?

This comment has been minimized.

Copy link
@mgravell

mgravell Jul 11, 2024

Author Member

Ultimately we can't squeeze every option onto the granular API - it makes it too unwieldy. DapperAOT addresses this on a per-method (caller) basis: https://github.com/DapperLib/DapperAOT/blob/42778fa9a426a477128dd37dba3272ad382b4304/test/Dapper.AOT.Test/Interceptors/CommandProperties.input.cs#L10

if (snapshot >= 0)
{
// .FetchSize = {withValue}
il.Emit(OpCodes.Ldarg_0);
il.Emit(OpCodes.Castclass, commandType);
il.Emit(OpCodes.Ldc_I8, snapshot); // bake it as a constant
il.EmitCall(OpCodes.Callvirt, fetchSize, null);
}
}
il.Emit(OpCodes.Ret);
action = (Action<IDbCommand>)method.CreateDelegate(typeof(Action<IDbCommand>));
}
2 changes: 2 additions & 0 deletions Dapper/SqlMapper.Link.cs
Original file line number Diff line number Diff line change
@@ -13,6 +13,8 @@ public static partial class SqlMapper
/// <typeparam name="TValue">The value type of the cache.</typeparam>
internal class Link<TKey, TValue> where TKey : class
{
public static void Clear(ref Link<TKey, TValue> head) => Interlocked.Exchange(ref head, null);

public static bool TryGet(Link<TKey, TValue> link, TKey key, out TValue value)
{
while (link != null)
25 changes: 24 additions & 1 deletion Dapper/SqlMapper.Settings.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using System;
using System.Data;
using System.Threading;

namespace Dapper
{
@@ -63,7 +64,9 @@ static Settings()
public static void SetDefaults()
{
CommandTimeout = null;
ApplyNullValues = false;
ApplyNullValues = PadListExpansions = UseIncrementalPseudoPositionalParameterNames = false;
AllowedCommandBehaviors = DefaultAllowedCommandBehaviors;
FetchSize = InListStringSplitCount = -1;
}

/// <summary>
@@ -99,6 +102,26 @@ public static void SetDefaults()
/// instead of the original name; for most scenarios, this is ignored since the name is redundant, but "snowflake" requires this.
/// </summary>
public static bool UseIncrementalPseudoPositionalParameterNames { get; set; }

/// <summary>
/// If assigned a non-negative value, then that value is applied to any commands <c>FetchSize</c> property, if it exists;
/// see https://docs.oracle.com/en/database/oracle/oracle-database/18/odpnt/CommandFetchSize.html; note that this value
/// can only be set globally - it is not intended for frequent/contextual changing.
/// </summary>
public static long FetchSize
{
get => Volatile.Read(ref s_FetchSize);
set
{
if (Volatile.Read(ref s_FetchSize) != value)
{
Volatile.Write(ref s_FetchSize, value);
CommandDefinition.ResetCommandInitCache(); // if this setting is useful: we've invalidated things
}
}
}

private static long s_FetchSize = -1;
}
}
}
2 changes: 2 additions & 0 deletions docs/index.md
Original file line number Diff line number Diff line change
@@ -22,6 +22,8 @@ Note: to get the latest pre-release build, add ` -Pre` to the end of the command

### unreleased


- add global `FetchSize` setting for use with Oracle (#1946 via mgravell, fixes #1945) (also add some missing logic in `Settings.Reset()`)
- add underscore handling with constructors (#1786 via @jo-goro, fixes #818; also #1947 via mgravell)

(note: new PRs will not be merged until they add release note wording here)

0 comments on commit 8c9b4bd

Please sign in to comment.