-
-
Notifications
You must be signed in to change notification settings - Fork 3.7k
Expand file tree
/
Copy pathCommandDefinition.cs
More file actions
211 lines (187 loc) · 8.66 KB
/
CommandDefinition.cs
File metadata and controls
211 lines (187 loc) · 8.66 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
using System;
using System.Data;
using System.Reflection;
using System.Reflection.Emit;
using System.Threading;
namespace Dapper
{
/// <summary>
/// Represents the key aspects of a sql operation
/// </summary>
public readonly struct CommandDefinition
{
internal static CommandDefinition ForCallback(object? parameters, CommandFlags flags)
{
return new CommandDefinition(parameters is DynamicParameters ? parameters : null, flags);
}
internal void OnCompleted()
{
(Parameters as SqlMapper.IParameterCallbacks)?.OnCompleted();
}
/// <summary>
/// The command (sql or a stored-procedure name) to execute
/// </summary>
public string CommandText { get; }
/// <summary>
/// The parameters associated with the command
/// </summary>
public object? Parameters { get; }
/// <summary>
/// The active transaction for the command
/// </summary>
public IDbTransaction? Transaction { get; }
/// <summary>
/// The effective timeout for the command
/// </summary>
public int? CommandTimeout { get
DEC9
; }
internal readonly CommandType CommandTypeDirect;
/// <summary>
/// The type of command that the command-text represents
/// </summary>
#if DEBUG // prevent use in our own code
[Obsolete("Prefer " + nameof(CommandTypeDirect), true)]
#endif
public CommandType? CommandType => CommandTypeDirect;
/// <summary>
/// Should data be buffered before returning?
/// </summary>
public bool Buffered => (Flags & CommandFlags.Buffered) != 0;
/// <summary>
/// Should the plan for this query be cached?
/// </summary>
internal bool AddToCache => (Flags & CommandFlags.NoCache) == 0;
/// <summary>
/// Additional state flags against this command
/// </summary>
public CommandFlags Flags { get; }
/// <summary>
/// Can async queries be pipelined?
/// </summary>
public bool Pipelined => (Flags & CommandFlags.Pipelined) != 0;
/// <summary>
/// Initialize the command definition
/// </summary>
/// <param name="commandText">The text for this command.</param>
/// <param name="parameters">The parameters for this command.</param>
/// <param name="transaction">The transaction for this command to participate in.</param>
/// <param name="commandTimeout">The timeout (in seconds) for this command.</param>
/// <param name="commandType">The <see cref="CommandType"/> for this command.</param>
/// <param name="flags">The behavior flags for this command.</param>
/// <param name="cancellationToken">The cancellation token for this command.</param>
public CommandDefinition(string commandText, object? parameters = null, IDbTransaction? transaction = null, int? commandTimeout = null,
CommandType? commandType = null, CommandFlags flags = CommandFlags.Buffered
, CancellationToken cancellationToken = default
)
{
CommandText = commandText;
Parameters = parameters;
Transaction = transaction;
CommandTimeout = commandTimeout;
CommandTypeDirect = commandType ?? InferCommandType(commandText);
Flags = flags;
CancellationToken = cancellationToken;
}
internal static CommandType InferCommandType(string sql)
{
// if the sql contains any whitespace character (space/tab/cr/lf/etc - via unicode),
// has operators, comments, semi-colon, or a known exception: interpret as ad-hoc;
// otherwise, simple names like "SomeName" should be treated as a stored-proc
// (note TableDirect would need to be specified explicitly, but in reality providers don't usually support TableDirect anyway)
if (sql is null || CompiledRegex.WhitespaceOrReserved.IsMatch(sql)) return System.Data.CommandType.Text;
return System.Data.CommandType.StoredProcedure;
}
private CommandDefinition(object? parameters, CommandFlags flags) : this()
{
Parameters = parameters;
Flags = flags;
CommandText = "";
}
/// <summary>
/// For asynchronous operations, the cancellation-token
/// </summary>
public CancellationToken CancellationToken { get; }
internal IDbCommand SetupCommand(IDbConnection cnn, Action<IDbCommand, object?>? paramReader)
{
var cmd = cnn.CreateCommand();
var init = GetInit(cmd.GetType());
init?.Invoke(cmd);
if (Transaction is not null)
cmd.Transaction = Transaction;
cmd.CommandText = CommandText;
if (CommandTimeout.HasValue)
{
cmd.CommandTimeout = CommandTimeout.Value;
}
else if (SqlMapper.Settings.CommandTimeout.HasValue)
{
cmd.CommandTimeout = SqlMapper.Settings.CommandTimeout.Value;
}
cmd.CommandType = CommandTypeDirect;
paramReader?.Invoke(cmd, Parameters);
return cmd;
}
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 is null)
return null; // GIGO
if (SqlMapper.Link<Type, Action<IDbCommand>>.TryGet(commandInitCache, commandType, out Action<IDbCommand>? action))
{
return action;
}
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 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 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 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;
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>));
}
// cache it
SqlMapper.Link<Type, Action<IDbCommand>>.TryAdd(ref commandInitCache, commandType, ref action!);
return action;
}
private static MethodInfo? GetBasicPropertySetter(Type declaringType, string name, Type expectedType)
{
var prop = declaringType.GetProperty(name, BindingFlags.Public | BindingFlags.Instance);
if (prop?.CanWrite == true && prop.PropertyType == expectedType && prop.GetIndexParameters().Length == 0)
{
return prop.GetSetMethod();
}
return null;
}
}
}