-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathSqlDataRecordListTVPParameter.cs
106 lines (94 loc) · 4.42 KB
/
SqlDataRecordListTVPParameter.cs
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
using System;
using System.Collections;
using System.Collections.Generic;
using System.Data;
using System.Linq;
using System.Reflection;
using System.Reflection.Emit;
namespace Dapper
{
/// <summary>
/// Used to pass a IEnumerable<SqlDataRecord> as a SqlDataRecordListTVPParameter
/// </summary>
internal sealed class SqlDataRecordListTVPParameter<T> : SqlMapper.ICustomQueryParameter
where T : IDataRecord
{
private readonly IEnumerable<T> data;
private readonly string typeName;
/// <summary>
/// Create a new instance of <see cref="SqlDataRecordListTVPParameter<T>"/>.
/// </summary>
/// <param name="data">The data records to convert into TVPs.</param>
/// <param name="typeName">The parameter type name.</param>
public SqlDataRecordListTVPParameter(IEnumerable<T> data, string typeName)
{
this.data = data;
this.typeName = typeName;
}
void SqlMapper.ICustomQueryParameter.AddParameter(IDbCommand command, string name)
{
var param = command.CreateParameter();
param.ParameterName = name;
Set(param, data, typeName);
command.Parameters.Add(param);
}
internal static void Set(IDbDataParameter parameter, IEnumerable<T> data, string typeName)
{
parameter.Value = data != null && data.Any() ? data : null;
StructuredHelper.ConfigureTVP(parameter, typeName);
}
}
static class StructuredHelper
{
private static readonly Hashtable s_udt = new Hashtable(), s_tvp = new Hashtable();
private static Action<IDbDataParameter, string> GetUDT(Type type)
=> (Action<IDbDataParameter, string>)s_udt[type] ?? SlowGetHelper(type, s_udt, "UdtTypeName", 29); // 29 = SqlDbType.Udt (avoiding ref)
private static Action<IDbDataParameter, string> GetTVP(Type type)
=> (Action<IDbDataParameter, string>)s_tvp[type] ?? SlowGetHelper(type, s_tvp, "TypeName", 30); // 30 = SqlDbType.Structured (avoiding ref)
static Action<IDbDataParameter, string> SlowGetHelper(Type type, Hashtable hashtable, string nameProperty, int sqlDbType)
{
lock (hashtable)
{
var helper = (Action<IDbDataParameter, string>)hashtable[type];
if (helper == null)
{
helper = CreateFor(type, nameProperty, sqlDbType);
hashtable.Add(type, helper);
}
return helper;
}
}
static Action<IDbDataParameter, string> CreateFor(Type type, string nameProperty, int sqlDbType)
{
var name = type.GetProperty(nameProperty, BindingFlags.Public | BindingFlags.Instance);
if (name == null || !name.CanWrite)
{
return (p, n) => { };
}
var dbType = type.GetProperty("SqlDbType", BindingFlags.Public | BindingFlags.Instance);
if (dbType != null && !dbType.CanWrite) dbType = null;
var dm = new DynamicMethod(nameof(CreateFor) + "_" + type.Name, null,
new[] { typeof(IDbDataParameter), typeof(string) }, true);
var il = dm.GetILGenerator();
il.Emit(OpCodes.Ldarg_0);
il.Emit(OpCodes.Castclass, type);
il.Emit(OpCodes.Ldarg_1);
il.EmitCall(OpCodes.Callvirt, name.GetSetMethod(), null);
if (dbType != null)
{
il.Emit(OpCodes.Ldarg_0);
il.Emit(OpCodes.Castclass, type);
il.Emit(OpCodes.Ldc_I4, sqlDbType);
il.EmitCall(OpCodes.Callvirt, dbType.GetSetMethod(), null);
}
il.Emit(OpCodes.Ret);
return (Action<IDbDataParameter, string>)dm.CreateDelegate(typeof(Action<IDbDataParameter, string>));
}
// this needs to be done per-provider; "dynamic" doesn't work well on all runtimes, although that
// would be a fair option otherwise
internal static void ConfigureUDT(IDbDataParameter parameter, string typeName)
=> GetUDT(parameter.GetType())(parameter, typeName);
internal static void ConfigureTVP(IDbDataParameter parameter, string typeName)
=> GetTVP(parameter.GetType())(parameter, typeName);
}
}