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
5b0517ca
Commit
5b0517ca
authored
Aug 13, 2012
by
Anatoly Zhmur
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Added support for custom property/field mapping in TypeDeserializer
parent
4a7e7fb5
Changes
2
Show whitespace changes
Inline
Side-by-side
Showing
2 changed files
with
479 additions
and
87 deletions
+479
-87
SqlMapper.cs
Dapper/SqlMapper.cs
+446
-87
Tests.cs
Tests/Tests.cs
+33
-0
No files found.
Dapper/SqlMapper.cs
View file @
5b0517ca
...
...
@@ -37,6 +37,67 @@ public interface IDynamicParameters
/// <param name="identity">Information about the query</param>
void
AddParameters
(
IDbCommand
command
,
Identity
identity
);
}
/// <summary>
/// Implement this interface to change default mapping of reader columns to type memebers
/// </summary>
public
interface
ITypeMap
{
/// <summary>
/// Finds best constructor
/// </summary>
/// <param name="names">DataReader column names</param>
/// <param name="types">DataReader column types</param>
/// <returns>Matching constructor or default one</returns>
ConstructorInfo
FindConstructor
(
string
[]
names
,
Type
[]
types
);
/// <summary>
/// Gets mapping for constructor parameter
/// </summary>
/// <param name="constructor">Constructor to resolve</param>
/// <param name="columnName">DataReader column name</param>
/// <returns>Mapping implementation</returns>
IMemberMap
GetConstructorParameter
(
ConstructorInfo
constructor
,
string
columnName
);
/// <summary>
/// Gets member mapping for column
/// </summary>
/// <param name="columnName">DataReader column name</param>
/// <returns>Mapping implementation</returns>
IMemberMap
GetMember
(
string
columnName
);
}
/// <summary>
/// Implements this interface to provide custom member mapping
/// </summary>
public
interface
IMemberMap
{
/// <summary>
/// Source DataReader column name
/// </summary>
string
ColumnName
{
get
;
}
/// <summary>
/// Target member type
/// </summary>
Type
MemberType
{
get
;
}
/// <summary>
/// Target property
/// </summary>
PropertyInfo
Property
{
get
;
}
/// <summary>
/// Target field
/// </summary>
FieldInfo
Field
{
get
;
}
/// <summary>
/// Target constructor parameter
/// </summary>
ParameterInfo
Parameter
{
get
;
}
}
static
Link
<
Type
,
Action
<
IDbCommand
,
bool
>>
bindByNameCache
;
static
Action
<
IDbCommand
,
bool
>
GetBindByName
(
Type
commandType
)
{
...
...
@@ -172,6 +233,15 @@ private static bool TryGetQueryCache(Identity key, out CacheInfo value)
{
lock
(
_queryCache
)
{
return
_queryCache
.
TryGetValue
(
key
,
out
value
);
}
}
private
static
void
PurgeQueryCacheByType
(
Type
type
)
{
lock
(
_queryCache
)
{
var
toRemove
=
_queryCache
.
Keys
.
Where
(
id
=>
id
.
type
==
type
).
ToArray
();
foreach
(
var
key
in
toRemove
)
_queryCache
.
Remove
(
key
);
}
}
public
static
void
PurgeQueryCache
()
{
lock
(
_queryCache
)
...
...
@@ -233,6 +303,16 @@ public static void PurgeQueryCache()
OnQueryCachePurged
();
}
private
static
void
PurgeQueryCacheByType
(
Type
type
)
{
foreach
(
var
entry
in
_queryCache
)
{
CacheInfo
cache
;
if
(
entry
.
Key
.
type
==
type
)
_queryCache
.
TryRemove
(
entry
.
Key
,
out
cache
);
}
}
/// <summary>
/// Return a count of all the cached queries by dapper
/// </summary>
...
...
@@ -324,7 +404,7 @@ static SqlMapper()
typeMap
[
typeof
(
Object
)]
=
DbType
.
Object
;
}
private
const
string
LinqBinary
=
"System.Data.Linq.Binary"
;
internal
const
string
LinqBinary
=
"System.Data.Linq.Binary"
;
private
static
DbType
LookupDbType
(
Type
type
,
string
name
)
{
DbType
dbType
;
...
...
@@ -428,7 +508,10 @@ public override bool Equals(object obj)
///
/// </summary>
public
readonly
int
hashCode
,
gridIndex
;
private
readonly
Type
type
;
/// <summary>
///
/// </summary>
public
readonly
Type
type
;
/// <summary>
///
/// </summary>
...
...
@@ -957,8 +1040,8 @@ class DontMap { }
// if our current type has the split, skip the first time you see it.
if
(
type
!=
typeof
(
Object
))
{
var
props
=
GetSettableProps
(
type
);
var
fields
=
GetSettableFields
(
type
);
var
props
=
DefaultTypeMap
.
GetSettableProps
(
type
);
var
fields
=
DefaultTypeMap
.
GetSettableFields
(
type
);
foreach
(
var
name
in
props
.
Select
(
p
=>
p
.
Name
).
Concat
(
fields
.
Select
(
f
=>
f
.
Name
)))
{
...
...
@@ -1578,32 +1661,43 @@ static readonly MethodInfo
.
Where
(
p
=>
p
.
GetIndexParameters
().
Any
()
&&
p
.
GetIndexParameters
()[
0
].
ParameterType
==
typeof
(
int
))
.
Select
(
p
=>
p
.
GetGetMethod
()).
First
();
class
PropInfo
/// <summary>
/// Gets type map
/// </summary>
/// <param name="type"></param>
/// <returns>Type map implementation, DefaultTypeMap instance in no override present</returns>
public
static
ITypeMap
GetTypeMap
(
Type
type
)
{
public
string
Name
{
get
;
set
;
}
public
MethodInfo
Setter
{
get
;
set
;
}
public
Type
Type
{
get
;
set
;
}
ITypeMap
typeMap
;
lock
(
_typeMaps
)
{
_typeMaps
.
TryGetValue
(
type
,
out
typeMap
);
}
static
List
<
PropInfo
>
GetSettableProps
(
Type
t
)
{
return
t
.
GetProperties
(
BindingFlags
.
Public
|
BindingFlags
.
NonPublic
|
BindingFlags
.
Instance
)
.
Select
(
p
=>
new
PropInfo
{
Name
=
p
.
Name
,
Setter
=
p
.
DeclaringType
==
t
?
p
.
GetSetMethod
(
true
)
:
p
.
DeclaringType
.
GetProperty
(
p
.
Name
,
BindingFlags
.
Public
|
BindingFlags
.
NonPublic
|
BindingFlags
.
Instance
).
GetSetMethod
(
true
),
Type
=
p
.
PropertyType
})
.
Where
(
info
=>
info
.
Setter
!=
null
)
.
ToList
();
return
typeMap
??
new
DefaultTypeMap
(
type
);
}
static
List
<
FieldInfo
>
GetSettableFields
(
Type
t
)
private
static
readonly
Dictionary
<
Type
,
ITypeMap
>
_typeMaps
=
new
Dictionary
<
Type
,
ITypeMap
>();
/// <summary>
/// Set custom mapping for type deserializers
/// </summary>
/// <param name="type">Entity type to override</param>
/// <param name="map">Mapping rules impementation, null to remove custom map</param>
public
static
void
SetTypeMap
(
Type
type
,
ITypeMap
map
)
{
return
t
.
GetFields
(
BindingFlags
.
Public
|
BindingFlags
.
NonPublic
|
BindingFlags
.
Instance
).
ToList
();
if
(
type
==
null
)
throw
new
ArgumentNullException
(
"type"
);
if
(
map
==
null
||
map
is
DefaultTypeMap
)
lock
(
_typeMaps
)
_typeMaps
.
Remove
(
type
);
else
lock
(
_typeMaps
)
_typeMaps
[
type
]
=
map
;
PurgeQueryCacheByType
(
type
);
}
/// <summary>
...
...
@@ -1630,8 +1724,7 @@ static List<FieldInfo> GetSettableFields(Type t)
il
.
DeclareLocal
(
type
);
il
.
Emit
(
OpCodes
.
Ldc_I4_0
);
il
.
Emit
(
OpCodes
.
Stloc_0
);
var
properties
=
GetSettableProps
(
type
);
var
fields
=
GetSettableFields
(
type
);
if
(
length
==
-
1
)
{
length
=
reader
.
FieldCount
-
startBound
;
...
...
@@ -1642,17 +1735,14 @@ static List<FieldInfo> GetSettableFields(Type t)
throw
new
ArgumentException
(
"When using the multi-mapping APIs ensure you set the splitOn param if you have keys other than Id"
,
"splitOn"
);
}
var
names
=
new
List
<
string
>
();
var
names
=
Enumerable
.
Range
(
startBound
,
length
).
Select
(
i
=>
reader
.
GetName
(
i
)).
ToArray
();
for
(
int
i
=
startBound
;
i
<
startBound
+
length
;
i
++)
{
names
.
Add
(
reader
.
GetName
(
i
));
}
ITypeMap
typeMap
=
GetTypeMap
(
type
);
int
index
=
startBound
;
ConstructorInfo
specializedConstructor
=
null
;
ParameterInfo
[]
specializedParameters
=
null
;
if
(
type
.
IsValueType
)
{
il
.
Emit
(
OpCodes
.
Ldloca_S
,
(
byte
)
1
);
...
...
@@ -1665,47 +1755,31 @@ static List<FieldInfo> GetSettableFields(Type t)
{
types
[
i
-
startBound
]
=
reader
.
GetFieldType
(
i
);
}
var
constructors
=
type
.
GetConstructors
(
BindingFlags
.
Instance
|
BindingFlags
.
Public
|
BindingFlags
.
NonPublic
);
bool
hasDefaultConstructor
=
false
;
foreach
(
ConstructorInfo
ctor
in
constructors
.
OrderBy
(
c
=>
c
.
IsPublic
?
0
:
(
c
.
IsPrivate
?
2
:
1
)).
ThenBy
(
c
=>
c
.
GetParameters
().
Length
))
{
ParameterInfo
[]
ctorParameters
=
ctor
.
GetParameters
();
if
(
ctorParameters
.
Length
==
0
)
{
il
.
Emit
(
OpCodes
.
Newobj
,
ctor
);
il
.
Emit
(
OpCodes
.
Stloc_1
);
hasDefaultConstructor
=
true
;
break
;
}
if
(
ctorParameters
.
Length
!=
types
.
Length
)
continue
;
int
i
=
0
;
for
(;
i
<
ctorParameters
.
Length
;
i
++)
if
(
type
.
IsValueType
)
{
if
(!
String
.
Equals
(
ctorParameters
[
i
].
Name
,
names
[
i
],
StringComparison
.
OrdinalIgnoreCase
))
break
;
if
(
types
[
i
]
==
typeof
(
byte
[])
&&
ctorParameters
[
i
].
ParameterType
.
FullName
==
LinqBinary
)
continue
;
var
unboxedType
=
Nullable
.
GetUnderlyingType
(
ctorParameters
[
i
].
ParameterType
)
??
ctorParameters
[
i
].
ParameterType
;
if
(
unboxedType
!=
types
[
i
]
&&
!(
unboxedType
.
IsEnum
&&
Enum
.
GetUnderlyingType
(
unboxedType
)
==
types
[
i
])
&&
!(
unboxedType
==
typeof
(
char
)
&&
types
[
i
]
==
typeof
(
string
)))
break
;
il
.
Emit
(
OpCodes
.
Ldloca_S
,
(
byte
)
1
);
il
.
Emit
(
OpCodes
.
Initobj
,
type
);
}
if
(
i
==
ctorParameters
.
Length
)
else
{
specializedConstructor
=
ctor
;
specializedParameters
=
ctorParameters
;
break
;
}
}
if
(!
hasDefaultConstructor
&&
specializedConstructor
==
null
)
var
ctor
=
typeMap
.
FindConstructor
(
names
,
types
);
if
(
ctor
==
null
)
{
string
proposedTypes
=
"("
+
String
.
Join
(
", "
,
types
.
Select
((
t
,
i
)
=>
t
.
FullName
+
" "
+
names
[
i
]).
ToArray
())
+
")"
;
throw
new
InvalidOperationException
(
String
.
Format
(
"A parameterless default constructor or one matching signature {0} is required for {1} materialization"
,
proposedTypes
,
type
.
FullName
));
}
if
(
ctor
.
GetParameters
().
Length
==
0
)
{
il
.
Emit
(
OpCodes
.
Newobj
,
ctor
);
il
.
Emit
(
OpCodes
.
Stloc_1
);
}
else
specializedConstructor
=
ctor
;
}
}
il
.
BeginExceptionBlock
();
if
(
type
.
IsValueType
)
{
...
...
@@ -1716,27 +1790,20 @@ static List<FieldInfo> GetSettableFields(Type t)
il
.
Emit
(
OpCodes
.
Ldloc_1
);
// [target]
}
var
setters
=
specializedConstructor
!=
null
?
names
.
Select
((
n
,
i
)
=>
new
{
Name
=
n
,
Property
=
new
PropInfo
()
{
Type
=
specializedParameters
[
i
].
ParameterType
},
Field
=
(
FieldInfo
)
null
}).
ToList
()
:
(
from
n
in
names
let
prop
=
properties
.
FirstOrDefault
(
p
=>
string
.
Equals
(
p
.
Name
,
n
,
StringComparison
.
Ordinal
))
// property case sensitive first
??
properties
.
FirstOrDefault
(
p
=>
string
.
Equals
(
p
.
Name
,
n
,
StringComparison
.
OrdinalIgnoreCase
))
// property case insensitive second
let
field
=
prop
!=
null
?
null
:
(
fields
.
FirstOrDefault
(
p
=>
string
.
Equals
(
p
.
Name
,
n
,
StringComparison
.
Ordinal
))
// field case sensitive third
??
fields
.
FirstOrDefault
(
p
=>
string
.
Equals
(
p
.
Name
,
n
,
StringComparison
.
OrdinalIgnoreCase
)))
// field case insensitive fourth
select
new
{
Name
=
n
,
Property
=
prop
,
Field
=
field
}
).
ToList
();
var
members
=
(
specializedConstructor
!=
null
?
names
.
Select
(
n
=>
typeMap
.
GetConstructorParameter
(
specializedConstructor
,
n
))
:
names
.
Select
(
n
=>
typeMap
.
GetMember
(
n
))).
ToList
();
// stack is now [target]
bool
first
=
true
;
var
allDone
=
il
.
DefineLabel
();
int
enumDeclareLocal
=
-
1
;
foreach
(
var
item
in
sett
ers
)
foreach
(
var
item
in
memb
ers
)
{
if
(
item
.
Property
!=
null
||
item
.
Field
!=
null
)
if
(
item
!=
null
)
{
if
(
specializedConstructor
==
null
)
if
(
specializedConstructor
==
null
)
il
.
Emit
(
OpCodes
.
Dup
);
// stack is now [target][target]
Label
isDbNullLabel
=
il
.
DefineLabel
();
Label
finishLabel
=
il
.
DefineLabel
();
...
...
@@ -1747,7 +1814,7 @@ static List<FieldInfo> GetSettableFields(Type t)
il
.
Emit
(
OpCodes
.
Stloc_0
);
// stack is now [target][target][reader][index]
il
.
Emit
(
OpCodes
.
Callvirt
,
getItem
);
// stack is now [target][target][value-as-object]
Type
memberType
=
item
.
Property
!=
null
?
item
.
Property
.
Type
:
item
.
Field
.
Field
Type
;
Type
memberType
=
item
.
Member
Type
;
if
(
memberType
==
typeof
(
char
)
||
memberType
==
typeof
(
char
?))
{
...
...
@@ -1813,11 +1880,11 @@ static List<FieldInfo> GetSettableFields(Type t)
{
if
(
type
.
IsValueType
)
{
il
.
Emit
(
OpCodes
.
Call
,
item
.
Property
.
Setter
);
// stack is now [target]
il
.
Emit
(
OpCodes
.
Call
,
DefaultTypeMap
.
GetPropertySetter
(
item
.
Property
,
type
)
);
// stack is now [target]
}
else
{
il
.
Emit
(
OpCodes
.
Callvirt
,
item
.
Property
.
Setter
);
// stack is now [target]
il
.
Emit
(
OpCodes
.
Callvirt
,
DefaultTypeMap
.
GetPropertySetter
(
item
.
Property
,
type
)
);
// stack is now [target]
}
}
else
...
...
@@ -1832,11 +1899,11 @@ static List<FieldInfo> GetSettableFields(Type t)
if
(
specializedConstructor
!=
null
)
{
il
.
Emit
(
OpCodes
.
Pop
);
if
(
item
.
Property
.
Type
.
IsValueType
)
if
(
item
.
Member
Type
.
IsValueType
)
{
int
localIndex
=
il
.
DeclareLocal
(
item
.
Property
.
Type
).
LocalIndex
;
int
localIndex
=
il
.
DeclareLocal
(
item
.
Member
Type
).
LocalIndex
;
LoadLocalAddress
(
il
,
localIndex
);
il
.
Emit
(
OpCodes
.
Initobj
,
item
.
Property
.
Type
);
il
.
Emit
(
OpCodes
.
Initobj
,
item
.
Member
Type
);
LoadLocal
(
il
,
localIndex
);
}
else
...
...
@@ -2519,4 +2586,296 @@ public static FeatureSupport Get(IDbConnection connection)
public
bool
Arrays
{
get
;
set
;
}
}
/// <summary>
/// Represents simple memeber map for one of target parameter or property or field to source DataReader column
/// </summary>
public
sealed
class
SimpleMemberMap
:
SqlMapper
.
IMemberMap
{
private
readonly
string
_columnName
;
private
readonly
PropertyInfo
_property
;
private
readonly
FieldInfo
_field
;
private
readonly
ParameterInfo
_parameter
;
/// <summary>
/// Creates instance for simple property mapping
/// </summary>
/// <param name="columnName">DataReader column name</param>
/// <param name="property">Target property</param>
public
SimpleMemberMap
(
string
columnName
,
PropertyInfo
property
)
{
if
(
columnName
==
null
)
throw
new
ArgumentNullException
(
"columnName"
);
if
(
property
==
null
)
throw
new
ArgumentNullException
(
"property"
);
_columnName
=
columnName
;
_property
=
property
;
}
/// <summary>
/// Creates instance for simple field mapping
/// </summary>
/// <param name="columnName">DataReader column name</param>
/// <param name="field">Target property</param>
public
SimpleMemberMap
(
string
columnName
,
FieldInfo
field
)
{
if
(
columnName
==
null
)
throw
new
ArgumentNullException
(
"columnName"
);
if
(
field
==
null
)
throw
new
ArgumentNullException
(
"field"
);
_columnName
=
columnName
;
_field
=
field
;
}
/// <summary>
/// Creates instance for simple constructor parameter mapping
/// </summary>
/// <param name="columnName">DataReader column name</param>
/// <param name="parameter">Target constructor parameter</param>
public
SimpleMemberMap
(
string
columnName
,
ParameterInfo
parameter
)
{
if
(
columnName
==
null
)
throw
new
ArgumentNullException
(
"columnName"
);
if
(
parameter
==
null
)
throw
new
ArgumentNullException
(
"parameter"
);
_columnName
=
columnName
;
_parameter
=
parameter
;
}
/// <summary>
/// DataReader column name
/// </summary>
public
string
ColumnName
{
get
{
return
_columnName
;
}
}
/// <summary>
/// Target member type
/// </summary>
public
Type
MemberType
{
get
{
if
(
_field
!=
null
)
return
_field
.
FieldType
;
if
(
_property
!=
null
)
return
_property
.
PropertyType
;
if
(
_parameter
!=
null
)
return
_parameter
.
ParameterType
;
return
null
;
}
}
/// <summary>
/// Target property
/// </summary>
public
PropertyInfo
Property
{
get
{
return
_property
;
}
}
/// <summary>
/// Target field
/// </summary>
public
FieldInfo
Field
{
get
{
return
_field
;
}
}
/// <summary>
/// Target constructor parameter
/// </summary>
public
ParameterInfo
Parameter
{
get
{
return
_parameter
;
}
}
}
/// <summary>
/// Represents default type mapping strategy used by Dapper
/// </summary>
public
sealed
class
DefaultTypeMap
:
SqlMapper
.
ITypeMap
{
private
readonly
List
<
FieldInfo
>
_fields
;
private
readonly
List
<
PropertyInfo
>
_properties
;
private
readonly
Type
_type
;
/// <summary>
/// Creates default type map
/// </summary>
/// <param name="type">Entity type</param>
public
DefaultTypeMap
(
Type
type
)
{
if
(
type
==
null
)
throw
new
ArgumentNullException
(
"type"
);
_fields
=
GetSettableFields
(
type
);
_properties
=
GetSettableProps
(
type
);
_type
=
type
;
}
internal
static
MethodInfo
GetPropertySetter
(
PropertyInfo
propertyInfo
,
Type
type
)
{
return
propertyInfo
.
DeclaringType
==
type
?
propertyInfo
.
GetSetMethod
(
true
)
:
propertyInfo
.
DeclaringType
.
GetProperty
(
propertyInfo
.
Name
,
BindingFlags
.
Public
|
BindingFlags
.
NonPublic
|
BindingFlags
.
Instance
).
GetSetMethod
(
true
);
}
internal
static
List
<
PropertyInfo
>
GetSettableProps
(
Type
t
)
{
return
t
.
GetProperties
(
BindingFlags
.
Public
|
BindingFlags
.
NonPublic
|
BindingFlags
.
Instance
)
.
Where
(
p
=>
GetPropertySetter
(
p
,
t
)
!=
null
)
.
ToList
();
}
internal
static
List
<
FieldInfo
>
GetSettableFields
(
Type
t
)
{
return
t
.
GetFields
(
BindingFlags
.
Public
|
BindingFlags
.
NonPublic
|
BindingFlags
.
Instance
).
ToList
();
}
/// <summary>
/// Finds best constructor
/// </summary>
/// <param name="names">DataReader column names</param>
/// <param name="types">DataReader column types</param>
/// <returns>Matching constructor or default one</returns>
public
ConstructorInfo
FindConstructor
(
string
[]
names
,
Type
[]
types
)
{
var
constructors
=
_type
.
GetConstructors
(
BindingFlags
.
Instance
|
BindingFlags
.
Public
|
BindingFlags
.
NonPublic
);
foreach
(
ConstructorInfo
ctor
in
constructors
.
OrderBy
(
c
=>
c
.
IsPublic
?
0
:
(
c
.
IsPrivate
?
2
:
1
)).
ThenBy
(
c
=>
c
.
GetParameters
().
Length
))
{
ParameterInfo
[]
ctorParameters
=
ctor
.
GetParameters
();
if
(
ctorParameters
.
Length
==
0
)
return
ctor
;
if
(
ctorParameters
.
Length
!=
types
.
Length
)
continue
;
int
i
=
0
;
for
(;
i
<
ctorParameters
.
Length
;
i
++)
{
if
(!
String
.
Equals
(
ctorParameters
[
i
].
Name
,
names
[
i
],
StringComparison
.
OrdinalIgnoreCase
))
break
;
if
(
types
[
i
]
==
typeof
(
byte
[])
&&
ctorParameters
[
i
].
ParameterType
.
FullName
==
SqlMapper
.
LinqBinary
)
continue
;
var
unboxedType
=
Nullable
.
GetUnderlyingType
(
ctorParameters
[
i
].
ParameterType
)
??
ctorParameters
[
i
].
ParameterType
;
if
(
unboxedType
!=
types
[
i
]
&&
!(
unboxedType
.
IsEnum
&&
Enum
.
GetUnderlyingType
(
unboxedType
)
==
types
[
i
])
&&
!(
unboxedType
==
typeof
(
char
)
&&
types
[
i
]
==
typeof
(
string
)))
break
;
}
if
(
i
==
ctorParameters
.
Length
)
return
ctor
;
}
return
null
;
}
/// <summary>
/// Gets mapping for constructor parameter
/// </summary>
/// <param name="constructor">Constructor to resolve</param>
/// <param name="columnName">DataReader column name</param>
/// <returns>Mapping implementation</returns>
public
SqlMapper
.
IMemberMap
GetConstructorParameter
(
ConstructorInfo
constructor
,
string
columnName
)
{
var
parameters
=
constructor
.
GetParameters
();
return
new
SimpleMemberMap
(
columnName
,
parameters
.
FirstOrDefault
(
p
=>
string
.
Equals
(
p
.
Name
,
columnName
,
StringComparison
.
OrdinalIgnoreCase
)));
}
/// <summary>
/// Gets member mapping for column
/// </summary>
/// <param name="columnName">DataReader column name</param>
/// <returns>Mapping implementation</returns>
public
SqlMapper
.
IMemberMap
GetMember
(
string
columnName
)
{
var
property
=
_properties
.
FirstOrDefault
(
p
=>
string
.
Equals
(
p
.
Name
,
columnName
,
StringComparison
.
Ordinal
))
??
_properties
.
FirstOrDefault
(
p
=>
string
.
Equals
(
p
.
Name
,
columnName
,
StringComparison
.
OrdinalIgnoreCase
));
if
(
property
!=
null
)
return
new
SimpleMemberMap
(
columnName
,
property
);
var
field
=
_fields
.
FirstOrDefault
(
p
=>
string
.
Equals
(
p
.
Name
,
columnName
,
StringComparison
.
Ordinal
))
??
_fields
.
FirstOrDefault
(
p
=>
string
.
Equals
(
p
.
Name
,
columnName
,
StringComparison
.
OrdinalIgnoreCase
));
if
(
field
!=
null
)
return
new
SimpleMemberMap
(
columnName
,
field
);
return
null
;
}
}
/// <summary>
/// Implements custom property mapping by user provided criteria (usually presence of some custom attribute with column to member mapping)
/// </summary>
public
sealed
class
CustomPropertyTypeMap
:
SqlMapper
.
ITypeMap
{
private
readonly
Type
_type
;
private
readonly
Func
<
Type
,
string
,
PropertyInfo
>
_propertySelector
;
/// <summary>
/// Creates custom property mapping
/// </summary>
/// <param name="type">Target entity type</param>
/// <param name="propertySelector">Property selector based on target type and DataReader column name</param>
public
CustomPropertyTypeMap
(
Type
type
,
Func
<
Type
,
string
,
PropertyInfo
>
propertySelector
)
{
if
(
type
==
null
)
throw
new
ArgumentNullException
(
"type"
);
if
(
propertySelector
==
null
)
throw
new
ArgumentNullException
(
"propertySelector"
);
_type
=
type
;
_propertySelector
=
propertySelector
;
}
/// <summary>
/// Always returns default constructor
/// </summary>
/// <param name="names">DataReader column names</param>
/// <param name="types">DataReader column types</param>
/// <returns>Default constructor</returns>
public
ConstructorInfo
FindConstructor
(
string
[]
names
,
Type
[]
types
)
{
return
_type
.
GetConstructor
(
new
Type
[
0
]);
}
/// <summary>
/// Not impelmeneted as far as default constructor used for all cases
/// </summary>
/// <param name="constructor"></param>
/// <param name="columnName"></param>
/// <returns></returns>
public
SqlMapper
.
IMemberMap
GetConstructorParameter
(
ConstructorInfo
constructor
,
string
columnName
)
{
throw
new
NotImplementedException
();
}
/// <summary>
/// Returns property based on selector strategy
/// </summary>
/// <param name="columnName">DataReader column name</param>
/// <returns>Poperty member map</returns>
public
SqlMapper
.
IMemberMap
GetMember
(
string
columnName
)
{
var
prop
=
_propertySelector
(
_type
,
columnName
);
return
prop
!=
null
?
new
SimpleMemberMap
(
columnName
,
prop
)
:
null
;
}
}
}
\ No newline at end of file
Tests/Tests.cs
View file @
5b0517ca
...
...
@@ -10,6 +10,7 @@
using
System.Collections
;
using
System.Reflection
;
using
System.Dynamic
;
using
System.ComponentModel
;
#if POSTGRESQL
using
Npgsql
;
#endif
...
...
@@ -1863,6 +1864,38 @@ class ResultsChangeType
public
int
Z
{
get
;
set
;
}
}
public
void
TestCustomTypeMap
()
{
// default mapping
var
item
=
connection
.
Query
<
TypeWithMapping
>(
"Select 'AVal' as A, 'BVal' as B"
).
Single
();
item
.
A
.
IsEqualTo
(
"AVal"
);
item
.
B
.
IsEqualTo
(
"BVal"
);
// custom mapping
var
map
=
new
CustomPropertyTypeMap
(
typeof
(
TypeWithMapping
),
(
type
,
columnName
)
=>
type
.
GetProperties
().
Where
(
prop
=>
prop
.
GetCustomAttributes
(
false
).
OfType
<
DescriptionAttribute
>().
Any
(
attr
=>
attr
.
Description
==
columnName
)).
FirstOrDefault
());
Dapper
.
SqlMapper
.
SetTypeMap
(
typeof
(
TypeWithMapping
),
map
);
item
=
connection
.
Query
<
TypeWithMapping
>(
"Select 'AVal' as A, 'BVal' as B"
).
Single
();
item
.
A
.
IsEqualTo
(
"BVal"
);
item
.
B
.
IsEqualTo
(
"AVal"
);
// reset to default
Dapper
.
SqlMapper
.
SetTypeMap
(
typeof
(
TypeWithMapping
),
null
);
item
=
connection
.
Query
<
TypeWithMapping
>(
"Select 'AVal' as A, 'BVal' as B"
).
Single
();
item
.
A
.
IsEqualTo
(
"AVal"
);
item
.
B
.
IsEqualTo
(
"BVal"
);
}
public
class
TypeWithMapping
{
[
Description
(
"B"
)]
public
string
A
{
get
;
set
;
}
[
Description
(
"A"
)]
public
string
B
{
get
;
set
;
}
}
class
TransactedConnection
:
IDbConnection
{
IDbConnection
_conn
;
...
...
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