Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Submit feedback
Contribute to GitLab
Sign in / Register
Toggle navigation
D
Dapper
Project
Project
Details
Activity
Releases
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Boards
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
tsai
Dapper
Commits
f52d67f1
Commit
f52d67f1
authored
Sep 10, 2014
by
Derek
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Added output member expressions to DynamicParameters; included tests as well
parent
5472d2ab
Changes
2
Hide whitespace changes
Inline
Side-by-side
Showing
2 changed files
with
273 additions
and
9 deletions
+273
-9
SqlMapper.cs
Dapper NET40/SqlMapper.cs
+243
-9
Tests.cs
Tests/Tests.cs
+30
-0
No files found.
Dapper NET40/SqlMapper.cs
View file @
f52d67f1
...
...
@@ -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
View file @
f52d67f1
...
...
@@ -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
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment