Skip to content
体验新版
项目
组织
正在加载...
登录
切换导航
打开侧边栏
int
Dapper
提交
d1e6d6f5
D
Dapper
项目概览
int
/
Dapper
11 个月 前同步成功
通知
2
Star
0
Fork
0
代码
文件
提交
分支
Tags
贡献者
分支图
Diff
Issue
0
列表
看板
标记
里程碑
合并请求
0
DevOps
流水线
流水线任务
计划
Wiki
0
Wiki
分析
仓库
DevOps
项目成员
Pages
D
Dapper
项目概览
项目概览
详情
发布
仓库
仓库
文件
提交
分支
标签
贡献者
分支图
比较
Issue
0
Issue
0
列表
看板
标记
里程碑
合并请求
0
合并请求
0
Pages
DevOps
DevOps
流水线
流水线任务
计划
分析
分析
仓库分析
DevOps
Wiki
0
Wiki
成员
成员
收起侧边栏
关闭侧边栏
动态
分支图
创建新Issue
流水线任务
提交
Issue看板
体验新版 GitCode,发现更多精彩内容 >>
提交
d1e6d6f5
编写于
9月 15, 2014
作者:
M
Marc Gravell
浏览文件
操作
浏览文件
下载
差异文件
Merge pull request #165 from tuespetre/master
Added support for expression-based output parameter setting
上级
5472d2ab
f52d67f1
变更
2
隐藏空白更改
内联
并排
Showing
2 changed file
with
273 addition
and
9 deletion
+273
-9
Dapper NET40/SqlMapper.cs
Dapper NET40/SqlMapper.cs
+243
-9
Tests/Tests.cs
Tests/Tests.cs
+30
-0
未找到文件。
Dapper NET40/SqlMapper.cs
浏览文件 @
d1e6d6f5
...
...
@@ -19,6 +19,7 @@
using
System.Text.RegularExpressions
;
using
System.Diagnostics
;
using
System.Globalization
;
using
System.Linq.Expressions
;
namespace
Dapper
{
...
...
@@ -2456,8 +2457,8 @@ public static void PackListParameters(IDbCommand command, string namePrefix, obj
listParam
.
Value
=
item
??
DBNull
.
Value
;
if
(
isString
)
{
listParam
.
Size
=
4000
;
if
(
item
!=
null
&&
((
string
)
item
).
Length
>
4000
)
listParam
.
Size
=
DbString
.
DefaultLength
;
if
(
item
!=
null
&&
((
string
)
item
).
Length
>
DbString
.
DefaultLength
)
{
listParam
.
Size
=
-
1
;
}
...
...
@@ -2851,11 +2852,11 @@ internal static IList<LiteralToken> GetLiteralTokens(string sql)
{
il
.
Emit
(
OpCodes
.
Dup
);
// [string] [string]
il
.
EmitCall
(
OpCodes
.
Callvirt
,
typeof
(
string
).
GetProperty
(
"Length"
).
GetGetMethod
(),
null
);
// [string] [length]
EmitInt32
(
il
,
4000
);
// [string] [length] [4000]
EmitInt32
(
il
,
DbString
.
DefaultLength
);
// [string] [length] [4000]
il
.
Emit
(
OpCodes
.
Cgt
);
// [string] [0 or 1]
Label
isLong
=
il
.
DefineLabel
(),
lenDone
=
il
.
DefineLabel
();
il
.
Emit
(
OpCodes
.
Brtrue_S
,
isLong
);
EmitInt32
(
il
,
4000
);
// [string] [4000]
EmitInt32
(
il
,
DbString
.
DefaultLength
);
// [string] [4000]
il
.
Emit
(
OpCodes
.
Br_S
,
lenDone
);
il
.
MarkLabel
(
isLong
);
EmitInt32
(
il
,
-
1
);
// [string] [-1]
...
...
@@ -3027,6 +3028,11 @@ private static int ExecuteCommand(IDbConnection cnn, ref CommandDefinition comma
{
if
(
wasClosed
)
cnn
.
Close
();
if
(
cmd
!=
null
)
cmd
.
Dispose
();
if
(
command
.
Parameters
is
DynamicParameters
)
{
((
DynamicParameters
)
command
.
Parameters
).
FireOutputCallbacks
();
}
}
}
...
...
@@ -3053,6 +3059,11 @@ private static T ExecuteScalarImpl<T>(IDbConnection cnn, ref CommandDefinition c
{
if
(
wasClosed
)
cnn
.
Close
();
if
(
cmd
!=
null
)
cmd
.
Dispose
();
if
(
command
.
Parameters
is
DynamicParameters
)
{
((
DynamicParameters
)
command
.
Parameters
).
FireOutputCallbacks
();
}
}
return
Parse
<
T
>(
result
);
}
...
...
@@ -3076,6 +3087,11 @@ private static IDataReader ExecuteReaderImpl(IDbConnection cnn, ref CommandDefin
{
if
(
wasClosed
)
cnn
.
Close
();
if
(
cmd
!=
null
)
cmd
.
Dispose
();
if
(
command
.
Parameters
is
DynamicParameters
)
{
((
DynamicParameters
)
command
.
Parameters
).
FireOutputCallbacks
();
}
}
}
...
...
@@ -4090,6 +4106,9 @@ partial class ParamInfo
public
DbType
?
DbType
{
get
;
set
;
}
public
int
?
Size
{
get
;
set
;
}
public
IDbDataParameter
AttachedParam
{
get
;
set
;
}
internal
Action
<
object
,
DynamicParameters
>
OutputCallback
{
get
;
set
;
}
internal
object
OutputTarget
{
get
;
set
;
}
internal
bool
CameFromTemplate
{
get
;
set
;
}
}
/// <summary>
...
...
@@ -4221,6 +4240,7 @@ void SqlMapper.IDynamicParameters.AddParameters(IDbCommand command, SqlMapper.Id
protected
void
AddParameters
(
IDbCommand
command
,
SqlMapper
.
Identity
identity
)
{
var
literals
=
SqlMapper
.
GetLiteralTokens
(
identity
.
sql
);
if
(
templates
!=
null
)
{
foreach
(
var
template
in
templates
)
...
...
@@ -4239,9 +4259,31 @@ protected void AddParameters(IDbCommand command, SqlMapper.Identity identity)
appender
(
command
,
template
);
}
// The parameters were added to the command, but not the
// DynamicParameters until now.
foreach
(
IDbDataParameter
param
in
command
.
Parameters
)
{
parameters
.
Add
(
param
.
ParameterName
,
new
ParamInfo
{
AttachedParam
=
param
,
CameFromTemplate
=
true
,
DbType
=
param
.
DbType
,
Name
=
param
.
ParameterName
,
ParameterDirection
=
param
.
Direction
,
Size
=
param
.
Size
,
Value
=
param
.
Value
});
}
// Now that the parameters are added to the command, let's place our output callbacks
foreach
(
var
generator
in
this
.
outputCallbacks
)
{
generator
();
}
}
foreach
(
var
param
in
parameters
.
Values
)
foreach
(
var
param
in
parameters
.
Values
.
Where
(
p
=>
!
p
.
CameFromTemplate
)
)
{
var
dbType
=
param
.
DbType
;
var
val
=
param
.
Value
;
...
...
@@ -4286,9 +4328,9 @@ protected void AddParameters(IDbCommand command, SqlMapper.Identity identity)
var
s
=
val
as
string
;
if
(
s
!=
null
)
{
if
(
s
.
Length
<=
4000
)
if
(
s
.
Length
<=
DbString
.
DefaultLength
)
{
p
.
Size
=
4000
;
p
.
Size
=
DbString
.
DefaultLength
;
}
}
if
(
param
.
Size
!=
null
)
...
...
@@ -4310,6 +4352,7 @@ protected void AddParameters(IDbCommand command, SqlMapper.Identity identity)
param
.
AttachedParam
=
p
;
}
}
// note: most non-priveleged implementations would use: this.ReplaceLiterals(command);
if
(
literals
.
Count
!=
0
)
SqlMapper
.
ReplaceLiterals
(
this
,
command
,
literals
);
}
...
...
@@ -4345,6 +4388,190 @@ public T Get<T>(string name)
}
return
(
T
)
val
;
}
/// <summary>
/// Allows you to automatically populate a target property/field from output parameters. It actually
/// creates an InputOutput parameter, so you can still pass data in.
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="target">The object whose property/field you wish to populate.</param>
/// <param name="expression">A MemberExpression targeting a property/field of the target (or descendant thereof.)</param>
/// <param name="dbType"></param>
/// <param name="size">The size to set on the parameter. Defaults to 0, or DbString.DefaultLength in case of strings.</param>
/// <returns>The DynamicParameters instance</returns>
#if CSHARP30
public
DynamicParameters
Output
<
T
>(
T
target
,
Expression
<
Func
<
T
,
object
>>
expression
,
DbType
?
dbType
,
int
?
size
)
#else
public
DynamicParameters
Output
<
T
>(
T
target
,
Expression
<
Func
<
T
,
object
>>
expression
,
DbType
?
dbType
=
null
,
int
?
size
=
null
)
#endif
{
var
failMessage
=
"Expression must be a property/field chain off of a(n) {0} instance"
;
failMessage
=
string
.
Format
(
failMessage
,
typeof
(
T
).
Name
);
Action
@throw
=
()
=>
{
throw
new
InvalidOperationException
(
failMessage
);
};
// Is it even a MemberExpression?
var
lastMemberAccess
=
expression
.
Body
as
MemberExpression
;
if
(
lastMemberAccess
==
null
||
(
lastMemberAccess
.
Member
.
MemberType
!=
MemberTypes
.
Property
&&
lastMemberAccess
.
Member
.
MemberType
!=
MemberTypes
.
Field
))
{
if
(
expression
.
Body
.
NodeType
==
ExpressionType
.
Convert
&&
expression
.
Body
.
Type
==
typeof
(
object
)
&&
((
UnaryExpression
)
expression
.
Body
).
Operand
is
MemberExpression
)
{
// It's got to be unboxed
lastMemberAccess
=
(
MemberExpression
)((
UnaryExpression
)
expression
.
Body
).
Operand
;
}
else
@throw
();
}
// Does the chain consist of MemberExpressions leading to a ParameterExpression of type T?
MemberExpression
diving
=
lastMemberAccess
;
ParameterExpression
constant
=
null
;
// Retain a list of member names and the member expressions so we can rebuild the chain.
List
<
string
>
names
=
new
List
<
string
>();
List
<
MemberExpression
>
chain
=
new
List
<
MemberExpression
>();
do
{
// Insert the names in the right order so expression
// "Post.Author.Name" becomes parameter "PostAuthorName"
names
.
Insert
(
0
,
diving
.
Member
.
Name
);
chain
.
Insert
(
0
,
diving
);
constant
=
diving
.
Expression
as
ParameterExpression
;
diving
=
diving
.
Expression
as
MemberExpression
;
if
(
constant
!=
null
&&
constant
.
Type
==
typeof
(
T
))
{
break
;
}
else
if
(
diving
==
null
||
(
diving
.
Member
.
MemberType
!=
MemberTypes
.
Property
&&
diving
.
Member
.
MemberType
!=
MemberTypes
.
Field
))
{
@throw
();
}
}
while
(
diving
!=
null
);
var
dynamicParamName
=
string
.
Join
(
string
.
Empty
,
names
.
ToArray
());
// Before we get all emitty...
var
lookup
=
typeof
(
T
).
Name
+
"_"
+
string
.
Join
(
"|"
,
names
.
ToArray
());
Action
<
object
,
DynamicParameters
>
setter
=
null
;
lock
(
cachedOutputSettersLock
)
{
if
(
cachedOutputSetters
.
TryGetValue
(
lookup
,
out
setter
))
{
goto
MAKECALLBACK
;
}
}
// Come on let's build a method, let's build it, let's build it now!
var
dm
=
new
DynamicMethod
(
string
.
Format
(
"ExpressionParam{0}"
,
Guid
.
NewGuid
()),
null
,
new
[]
{
typeof
(
object
),
this
.
GetType
()
},
true
);
var
il
=
dm
.
GetILGenerator
();
il
.
Emit
(
OpCodes
.
Ldarg_0
);
// [object]
il
.
Emit
(
OpCodes
.
Castclass
,
typeof
(
T
));
// [T]
// Count - 1 to skip the last member access
var
i
=
0
;
for
(;
i
<
(
chain
.
Count
-
1
);
i
++)
{
var
member
=
chain
[
0
].
Member
;
if
(
member
.
MemberType
==
MemberTypes
.
Property
)
{
var
get
=
((
PropertyInfo
)
member
).
GetGetMethod
(
true
);
il
.
Emit
(
OpCodes
.
Callvirt
,
get
);
// [Member{i}]
}
else
// Else it must be a field!
{
il
.
Emit
(
OpCodes
.
Ldfld
,
((
FieldInfo
)
member
));
// [Member{i}]
}
}
var
paramGetter
=
this
.
GetType
().
GetMethod
(
"Get"
,
new
Type
[]
{
typeof
(
string
)
}).
MakeGenericMethod
(
lastMemberAccess
.
Type
);
il
.
Emit
(
OpCodes
.
Ldarg_1
);
// [target] [DynamicParameters]
il
.
Emit
(
OpCodes
.
Ldstr
,
dynamicParamName
);
// [target] [DynamicParameters] [ParamName]
il
.
Emit
(
OpCodes
.
Callvirt
,
paramGetter
);
// [target] [value], it's already typed thanks to generic method
// GET READY
var
lastMember
=
lastMemberAccess
.
Member
;
if
(
lastMember
.
MemberType
==
MemberTypes
.
Property
)
{
var
set
=
((
PropertyInfo
)
lastMember
).
GetSetMethod
(
true
);
il
.
Emit
(
OpCodes
.
Callvirt
,
set
);
// SET
}
else
{
il
.
Emit
(
OpCodes
.
Stfld
,
((
FieldInfo
)
lastMember
));
// SET
}
il
.
Emit
(
OpCodes
.
Ret
);
// GO
setter
=
(
Action
<
object
,
DynamicParameters
>)
dm
.
CreateDelegate
(
typeof
(
Action
<
object
,
DynamicParameters
>));
lock
(
cachedOutputSettersLock
)
{
if
(!
cachedOutputSetters
.
ContainsKey
(
lookup
))
{
cachedOutputSetters
.
Add
(
lookup
,
setter
);
}
}
// Queue the preparation to be fired off when adding parameters to the DbCommand
MAKECALLBACK
:
this
.
outputCallbacks
.
Add
(()
=>
{
// Finally, prep the parameter and attach the callback to it
ParamInfo
parameter
;
var
targetMemberType
=
lastMemberAccess
.
Type
;
int
sizeToSet
=
(!
size
.
HasValue
&&
targetMemberType
==
typeof
(
string
))
?
DbString
.
DefaultLength
:
size
??
0
;
if
(
this
.
parameters
.
TryGetValue
(
dynamicParamName
,
out
parameter
))
{
parameter
.
ParameterDirection
=
parameter
.
AttachedParam
.
Direction
=
ParameterDirection
.
InputOutput
;
if
(
parameter
.
AttachedParam
.
Size
==
0
)
{
parameter
.
Size
=
parameter
.
AttachedParam
.
Size
=
sizeToSet
;
}
}
else
{
SqlMapper
.
ITypeHandler
handler
;
dbType
=
(!
dbType
.
HasValue
)
?
SqlMapper
.
LookupDbType
(
targetMemberType
,
targetMemberType
.
Name
,
out
handler
)
:
dbType
;
// CameFromTemplate property would not apply here because this new param
// Still needs to be added to the command
this
.
Add
(
dynamicParamName
,
expression
.
Compile
().
Invoke
(
target
),
null
,
ParameterDirection
.
InputOutput
,
sizeToSet
);
}
parameter
=
this
.
parameters
[
dynamicParamName
];
parameter
.
OutputCallback
=
setter
;
parameter
.
OutputTarget
=
target
;
});
return
this
;
}
private
readonly
List
<
Action
>
outputCallbacks
=
new
List
<
Action
>();
private
readonly
Dictionary
<
string
,
Action
<
object
,
DynamicParameters
>>
cachedOutputSetters
=
new
Dictionary
<
string
,
Action
<
object
,
DynamicParameters
>>();
private
readonly
object
cachedOutputSettersLock
=
new
object
();
internal
void
FireOutputCallbacks
()
{
foreach
(
var
param
in
(
from
p
in
parameters
select
p
.
Value
))
{
if
(
param
.
OutputCallback
!=
null
)
param
.
OutputCallback
(
param
.
OutputTarget
,
this
);
}
}
}
sealed
class
DataTableHandler
:
Dapper
.
SqlMapper
.
ITypeHandler
...
...
@@ -4420,6 +4647,13 @@ internal static void Set(IDbDataParameter parameter, DataTable table, string typ
/// </summary>
sealed
partial
class
DbString
:
Dapper
.
SqlMapper
.
ICustomQueryParameter
{
/// <summary>
/// A value to set the default value of strings
/// going through Dapper. Default is 4000, any value larger than this
/// field will not have the default value applied.
/// </summary>
public
const
int
DefaultLength
=
4000
;
/// <summary>
/// Create a new DbString
/// </summary>
...
...
@@ -4454,9 +4688,9 @@ public void AddParameter(IDbCommand command, string name)
var
param
=
command
.
CreateParameter
();
param
.
ParameterName
=
name
;
param
.
Value
=
(
object
)
Value
??
DBNull
.
Value
;
if
(
Length
==
-
1
&&
Value
!=
null
&&
Value
.
Length
<=
4000
)
if
(
Length
==
-
1
&&
Value
!=
null
&&
Value
.
Length
<=
DefaultLength
)
{
param
.
Size
=
4000
;
param
.
Size
=
DefaultLength
;
}
else
{
...
...
Tests/Tests.cs
浏览文件 @
d1e6d6f5
...
...
@@ -1175,6 +1175,33 @@ public void TestSupportForDynamicParameters()
p
.
Get
<
int
>(
"age"
).
IsEqualTo
(
11
);
}
[
ActiveTest
]
public
void
TestSupportForDynamicParametersOutputExpressions
()
{
var
bob
=
new
Person
{
Name
=
"bob"
,
PersonId
=
1
,
Address
=
new
Address
{
PersonId
=
2
}
};
var
p
=
new
DynamicParameters
(
bob
);
p
.
Output
(
bob
,
b
=>
b
.
PersonId
);
p
.
Output
(
bob
,
b
=>
b
.
Occupation
);
p
.
Output
(
bob
,
b
=>
b
.
NumberOfLegs
);
p
.
Output
(
bob
,
b
=>
b
.
Address
.
Name
);
p
.
Output
(
bob
,
b
=>
b
.
Address
.
PersonId
);
connection
.
Execute
(
@"
SET @Occupation = 'grillmaster'
SET @PersonId = @PersonId + 1
SET @NumberOfLegs = @NumberOfLegs - 1
SET @AddressName = 'bobs burgers'
SET @AddressPersonId = @PersonId"
,
p
);
bob
.
Occupation
.
IsEqualTo
(
"grillmaster"
);
bob
.
PersonId
.
IsEqualTo
(
2
);
bob
.
NumberOfLegs
.
IsEqualTo
(
1
);
bob
.
Address
.
Name
.
IsEqualTo
(
"bobs burgers"
);
bob
.
Address
.
PersonId
.
IsEqualTo
(
2
);
}
public
void
TestSupportForExpandoObjectParameters
()
{
dynamic
p
=
new
ExpandoObject
();
...
...
@@ -1231,6 +1258,9 @@ class Person
{
public
int
PersonId
{
get
;
set
;
}
public
string
Name
{
get
;
set
;
}
public
string
Occupation
{
get
;
private
set
;
}
public
int
NumberOfLegs
=
2
;
public
Address
Address
{
get
;
set
;
}
}
class
Address
...
...
编辑
预览
Markdown
is supported
0%
请重试
或
添加新附件
.
添加附件
取消
You are about to add
0
people
to the discussion. Proceed with caution.
先完成此消息的编辑!
取消
想要评论请
注册
或
登录