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
2df6d074
Commit
2df6d074
authored
Apr 21, 2017
by
Marc Gravell
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Experimental multi-map code for tuples (not optimized)
parent
841b03f2
Changes
4
Hide whitespace changes
Inline
Side-by-side
Showing
4 changed files
with
263 additions
and
3 deletions
+263
-3
MultiMapTests.cs
Dapper.Tests/MultiMapTests.cs
+2
-2
MultiMapTupleTests.cs
Dapper.Tests/MultiMapTupleTests.cs
+128
-0
SqlMapper.Identity.cs
Dapper/SqlMapper.Identity.cs
+5
-0
SqlMapper.cs
Dapper/SqlMapper.cs
+128
-1
No files found.
Dapper.Tests/MultiMapTests.cs
View file @
2df6d074
...
@@ -29,13 +29,13 @@ public void ParentChildIdentityAssociations()
...
@@ -29,13 +29,13 @@ public void ParentChildIdentityAssociations()
parents
[
3
].
Children
.
Select
(
c
=>
c
.
Id
).
SequenceEqual
(
new
[]
{
5
}).
IsTrue
();
parents
[
3
].
Children
.
Select
(
c
=>
c
.
Id
).
SequenceEqual
(
new
[]
{
5
}).
IsTrue
();
}
}
private
class
Parent
internal
class
Parent
{
{
public
int
Id
{
get
;
set
;
}
public
int
Id
{
get
;
set
;
}
public
readonly
List
<
Child
>
Children
=
new
List
<
Child
>();
public
readonly
List
<
Child
>
Children
=
new
List
<
Child
>();
}
}
private
class
Child
internal
class
Child
{
{
public
int
Id
{
get
;
set
;
}
public
int
Id
{
get
;
set
;
}
}
}
...
...
Dapper.Tests/MultiMapTupleTests.cs
0 → 100644
View file @
2df6d074
using
System.Collections.Generic
;
using
System.Linq
;
using
Xunit
;
using
Child
=
Dapper
.
Tests
.
MultiMapTests
.
Child
;
using
Parent
=
Dapper
.
Tests
.
MultiMapTests
.
Parent
;
namespace
Dapper.Tests
{
public
class
MultiMapTupleTests
:
TestBase
{
// note: implementation is not optimized yet - just basic reflection, no IL crazy
// intent: to explore the API, allowing horizontally partitioned data to be fetched
// more convenienty by expressing them as tuples; this is similar to the (hard to use)
// pre-existing multi-generic Query<...> API that does the same, i.e.
// similar to connection.Query<Parent,Child,Parent>(...) - which folks find hard to grok
// here are 3 possible ideas for expressing that
[
Fact
]
public
void
GetRawTuples_Manual
()
// here we use an explicit map function that returns the entire row, and
// in doing so provides the contextual name metadata
{
var
tuples
=
connection
.
Query
(
@"select 1 as [Id], 1 as [Id] union all select 1,2 union all select 2,3 union all select 1,4 union all select 3,5"
,
((
Parent
parent
,
Child
child
)
row
)
=>
row
).
AsList
();
tuples
.
Count
.
IsEqualTo
(
5
);
string
.
Join
(
","
,
tuples
.
Select
(
x
=>
$"(
{
x
.
parent
.
Id
}
,
{
x
.
child
.
Id
}
)"
)).
IsEqualTo
(
"(1,1),(1,2),(2,3),(1,4),(3,5)"
);
}
[
Fact
]
public
void
GetRawTuples_Passthru
()
// here we use a declarative map function, so all we provide is the T to Map<T>
{
var
tuples
=
connection
.
Query
(
@"select 1 as [Id], 1 as [Id] union all select 1,2 union all select 2,3 union all select 1,4 union all select 3,5"
,
SqlMapper
.
Map
<(
Parent
parent
,
Child
child
)>()
).
AsList
();
tuples
.
Count
.
IsEqualTo
(
5
);
string
.
Join
(
","
,
tuples
.
Select
(
x
=>
$"(
{
x
.
parent
.
Id
}
,
{
x
.
child
.
Id
}
)"
)).
IsEqualTo
(
"(1,1),(1,2),(2,3),(1,4),(3,5)"
);
}
[
Fact
]
public
void
GetRawTuples_Split
()
// here we provide the tuple metadata via the T in QuerySplit<T> - note the name
// is different to avoid ambiguity with the primary Query<T> which does something else
{
var
tuples
=
connection
.
QuerySplit
<(
Parent
parent
,
Child
child
)>(
@"select 1 as [Id], 1 as [Id] union all select 1,2 union all select 2,3 union all select 1,4 union all select 3,5"
).
AsList
();
tuples
.
Count
.
IsEqualTo
(
5
);
string
.
Join
(
","
,
tuples
.
Select
(
x
=>
$"(
{
x
.
parent
.
Id
}
,
{
x
.
child
.
Id
}
)"
)).
IsEqualTo
(
"(1,1),(1,2),(2,3),(1,4),(3,5)"
);
}
// these are more complex examples that make use of a non-trivial mapping function to play with the horizontal partitions *before*
// yielding them - for example, to take parent/child data and stitch it together such that the children are attached to the parents
// compare and contrast: MultiMapTests.ParentChildIdentityAssociations
[
Fact
]
public
void
ParentChildIdentityAssociations
()
{
var
lookup
=
new
Dictionary
<
int
,
Parent
>();
var
parents
=
connection
.
Query
(
@"select 1 as [Id], 1 as [Id] union all select 1,2 union all select 2,3 union all select 1,4 union all select 3,5"
,
((
Parent
parent
,
Child
child
)
row
)
=>
{
if
(!
lookup
.
TryGetValue
(
row
.
parent
.
Id
,
out
Parent
found
))
{
lookup
.
Add
(
row
.
parent
.
Id
,
found
=
row
.
parent
);
}
found
.
Children
.
Add
(
row
.
child
);
return
found
;
}).
Distinct
().
ToDictionary
(
p
=>
p
.
Id
);
parents
.
Count
.
IsEqualTo
(
3
);
parents
[
1
].
Children
.
Select
(
c
=>
c
.
Id
).
SequenceEqual
(
new
[]
{
1
,
2
,
4
}).
IsTrue
();
parents
[
2
].
Children
.
Select
(
c
=>
c
.
Id
).
SequenceEqual
(
new
[]
{
3
}).
IsTrue
();
parents
[
3
].
Children
.
Select
(
c
=>
c
.
Id
).
SequenceEqual
(
new
[]
{
5
}).
IsTrue
();
}
// compare and contrast: MultiMapTests.TestMultiMap
[
Fact
]
public
void
TestMultiMap
()
{
const
string
createSql
=
@"
create table #Users (Id int, Name varchar(20))
create table #Posts (Id int, OwnerId int, Content varchar(20))
insert #Users values(99, 'Sam')
insert #Users values(2, 'I am')
insert #Posts values(1, 99, 'Sams Post1')
insert #Posts values(2, 99, 'Sams Post2')
insert #Posts values(3, null, 'no ones post')
"
;
connection
.
Execute
(
createSql
);
try
{
const
string
sql
=
@"select * from #Posts p
left join #Users u on u.Id = p.OwnerId
Order by p.Id"
;
var
data
=
connection
.
Query
(
sql
,
((
Post
post
,
User
user
)
row
)
=>
{
row
.
post
.
Owner
=
row
.
user
;
return
row
.
post
;
}).
ToList
();
var
p
=
data
.
First
();
p
.
Content
.
IsEqualTo
(
"Sams Post1"
);
p
.
Id
.
IsEqualTo
(
1
);
p
.
Owner
.
Name
.
IsEqualTo
(
"Sam"
);
p
.
Owner
.
Id
.
IsEqualTo
(
99
);
data
[
2
].
Owner
.
IsNull
();
}
finally
{
connection
.
Execute
(
"drop table #Users drop table #Posts"
);
}
}
}
}
Dapper/SqlMapper.Identity.cs
View file @
2df6d074
...
@@ -125,5 +125,10 @@ public bool Equals(Identity other)
...
@@ -125,5 +125,10 @@ public bool Equals(Identity other)
&&
parametersType
==
other
.
parametersType
;
&&
parametersType
==
other
.
parametersType
;
}
}
}
}
/// <summary>
/// Exposes a pass-thru identity map (useful for using with multi-map and tuples)
/// </summary>
public
static
Func
<
T
,
T
>
Map
<
T
>()
where
T
:
struct
=>
x
=>
x
;
}
}
}
}
Dapper/SqlMapper.cs
View file @
2df6d074
...
@@ -1271,6 +1271,23 @@ public static IEnumerable<TReturn> Query<TReturn>(this IDbConnection cnn, string
...
@@ -1271,6 +1271,23 @@ public static IEnumerable<TReturn> Query<TReturn>(this IDbConnection cnn, string
return
buffered
?
results
.
ToList
()
:
results
;
return
buffered
?
results
.
ToList
()
:
results
;
}
}
/// <summary>
/// Perform a multi mapping query with arbitrary input parameters expressed as tuples
/// </summary>
public
static
IEnumerable
<
TReturn
>
Query
<
TTuple
,
TReturn
>(
this
IDbConnection
cnn
,
string
sql
,
Func
<
TTuple
,
TReturn
>
map
,
object
param
=
null
,
IDbTransaction
transaction
=
null
,
bool
buffered
=
true
,
string
splitOn
=
"Id"
,
int
?
commandTimeout
=
null
,
CommandType
?
commandType
=
null
)
where
TTuple
:
struct
{
var
command
=
new
CommandDefinition
(
sql
,
param
,
transaction
,
commandTimeout
,
commandType
,
buffered
?
CommandFlags
.
Buffered
:
CommandFlags
.
None
);
var
results
=
MultiMapImpl
<
TTuple
,
TReturn
>(
cnn
,
command
,
map
,
splitOn
,
null
,
null
,
true
);
return
buffered
?
results
.
ToList
()
:
results
;
}
/// <summary>
/// Perform a multi mapping query with arbitrary input parameters expressed as tuples
/// </summary>
public
static
IEnumerable
<
TTuple
>
QuerySplit
<
TTuple
>(
this
IDbConnection
cnn
,
string
sql
,
object
param
=
null
,
IDbTransaction
transaction
=
null
,
bool
buffered
=
true
,
string
splitOn
=
"Id"
,
int
?
commandTimeout
=
null
,
CommandType
?
commandType
=
null
)
where
TTuple
:
struct
=>
Query
<
TTuple
,
TTuple
>(
cnn
,
sql
,
t
=>
t
,
param
,
transaction
,
buffered
,
splitOn
,
commandTimeout
,
commandType
);
static
IEnumerable
<
TReturn
>
MultiMap
<
TFirst
,
TSecond
,
TThird
,
TFourth
,
TFifth
,
TSixth
,
TSeventh
,
TReturn
>(
static
IEnumerable
<
TReturn
>
MultiMap
<
TFirst
,
TSecond
,
TThird
,
TFourth
,
TFifth
,
TSixth
,
TSeventh
,
TReturn
>(
this
IDbConnection
cnn
,
string
sql
,
Delegate
map
,
object
param
,
IDbTransaction
transaction
,
bool
buffered
,
string
splitOn
,
int
?
commandTimeout
,
CommandType
?
commandType
)
this
IDbConnection
cnn
,
string
sql
,
Delegate
map
,
object
param
,
IDbTransaction
transaction
,
bool
buffered
,
string
splitOn
,
int
?
commandTimeout
,
CommandType
?
commandType
)
{
{
...
@@ -1343,6 +1360,103 @@ private static CommandBehavior GetBehavior(bool close, CommandBehavior @default)
...
@@ -1343,6 +1360,103 @@ private static CommandBehavior GetBehavior(bool close, CommandBehavior @default)
{
{
return
(
close
?
(
@default
|
CommandBehavior
.
CloseConnection
)
:
@default
)
&
Settings
.
AllowedCommandBehaviors
;
return
(
close
?
(
@default
|
CommandBehavior
.
CloseConnection
)
:
@default
)
&
Settings
.
AllowedCommandBehaviors
;
}
}
static
class
TupleCache
<
T
>
{
private
static
readonly
Type
[]
_types
;
private
static
readonly
ConstructorInfo
_ctor
;
public
static
Type
[]
GetTypes
()
=>
_types
??
throw
new
InvalidOperationException
(
$"Type
{
typeof
(
T
)}
is not a tuple"
);
public
static
ConstructorInfo
Constructor
=>
_ctor
??
throw
new
InvalidOperationException
(
$"Type
{
typeof
(
T
)}
is not a tuple"
);
static
TupleCache
()
{
var
type
=
typeof
(
T
);
if
(
IsValueTuple
(
type
))
{
foreach
(
var
ctor
in
type
.
GetConstructors
())
{
var
parameters
=
ctor
.
GetParameters
();
if
(
parameters
.
Length
!=
0
)
{
Type
[]
types
=
new
Type
[
parameters
.
Length
];
for
(
int
i
=
0
;
i
<
types
.
Length
;
i
++)
types
[
i
]
=
parameters
[
i
].
ParameterType
;
_types
=
types
;
_ctor
=
ctor
;
break
;
}
}
}
}
}
static
IEnumerable
<
TReturn
>
MultiMapImpl
<
TTuple
,
TReturn
>(
this
IDbConnection
cnn
,
CommandDefinition
command
,
Func
<
TTuple
,
TReturn
>
map
,
string
splitOn
,
IDataReader
reader
,
Identity
identity
,
bool
finalize
)
where
TTuple
:
struct
{
Type
[]
types
=
TupleCache
<
TTuple
>.
GetTypes
();
if
(
types
.
Length
<
1
)
{
throw
new
ArgumentException
(
"you must provide at least one type to deserialize"
);
}
object
param
=
command
.
Parameters
;
identity
=
identity
??
new
Identity
(
command
.
CommandText
,
command
.
CommandType
,
cnn
,
types
[
0
],
param
?.
GetType
(),
types
);
CacheInfo
cinfo
=
GetCacheInfo
(
identity
,
param
,
command
.
AddToCache
);
IDbCommand
ownedCommand
=
null
;
IDataReader
ownedReader
=
null
;
bool
wasClosed
=
cnn
!=
null
&&
cnn
.
State
==
ConnectionState
.
Closed
;
try
{
if
(
reader
==
null
)
{
ownedCommand
=
command
.
SetupCommand
(
cnn
,
cinfo
.
ParamReader
);
if
(
wasClosed
)
cnn
.
Open
();
ownedReader
=
ExecuteReaderWithFlagsFallback
(
ownedCommand
,
wasClosed
,
CommandBehavior
.
SequentialAccess
|
CommandBehavior
.
SingleResult
);
reader
=
ownedReader
;
}
DeserializerState
deserializer
;
Func
<
IDataReader
,
object
>[]
otherDeserializers
;
int
hash
=
GetColumnHash
(
reader
);
if
((
deserializer
=
cinfo
.
Deserializer
).
Func
==
null
||
(
otherDeserializers
=
cinfo
.
OtherDeserializers
)
==
null
||
hash
!=
deserializer
.
Hash
)
{
var
deserializers
=
GenerateDeserializers
(
types
,
splitOn
,
reader
);
deserializer
=
cinfo
.
Deserializer
=
new
DeserializerState
(
hash
,
deserializers
[
0
]);
otherDeserializers
=
cinfo
.
OtherDeserializers
=
deserializers
.
Skip
(
1
).
ToArray
();
SetQueryCache
(
identity
,
cinfo
);
}
Func
<
IDataReader
,
TReturn
>
mapIt
=
GenerateMapper
<
TTuple
,
TReturn
>(
deserializer
.
Func
,
otherDeserializers
,
TupleCache
<
TTuple
>.
Constructor
,
map
);
if
(
mapIt
!=
null
)
{
while
(
reader
.
Read
())
{
yield
return
mapIt
(
reader
);
}
if
(
finalize
)
{
while
(
reader
.
NextResult
())
{
}
command
.
OnCompleted
();
}
}
}
finally
{
try
{
ownedReader
?.
Dispose
();
}
finally
{
ownedCommand
?.
Dispose
();
if
(
wasClosed
)
cnn
.
Close
();
}
}
}
static
IEnumerable
<
TReturn
>
MultiMapImpl
<
TReturn
>(
this
IDbConnection
cnn
,
CommandDefinition
command
,
Type
[]
types
,
Func
<
object
[],
TReturn
>
map
,
string
splitOn
,
IDataReader
reader
,
Identity
identity
,
bool
finalize
)
static
IEnumerable
<
TReturn
>
MultiMapImpl
<
TReturn
>(
this
IDbConnection
cnn
,
CommandDefinition
command
,
Type
[]
types
,
Func
<
object
[],
TReturn
>
map
,
string
splitOn
,
IDataReader
reader
,
Identity
identity
,
bool
finalize
)
{
{
if
(
types
.
Length
<
1
)
if
(
types
.
Length
<
1
)
...
@@ -1407,7 +1521,20 @@ static IEnumerable<TReturn> MultiMapImpl<TReturn>(this IDbConnection cnn, Comman
...
@@ -1407,7 +1521,20 @@ static IEnumerable<TReturn> MultiMapImpl<TReturn>(this IDbConnection cnn, Comman
}
}
}
}
}
}
private
static
Func
<
IDataReader
,
TReturn
>
GenerateMapper
<
TTuple
,
TReturn
>(
Func
<
IDataReader
,
object
>
deserializer
,
Func
<
IDataReader
,
object
>[]
otherDeserializers
,
ConstructorInfo
ctor
,
Func
<
TTuple
,
TReturn
>
map
)
where
TTuple
:
struct
{
return
r
=>
{
// unoptimized, obvs!
object
[]
args
=
new
object
[
1
+
otherDeserializers
.
Length
];
args
[
0
]
=
deserializer
(
r
);
for
(
int
i
=
1
;
i
<
args
.
Length
;
i
++)
args
[
i
]
=
otherDeserializers
[
i
-
1
](
r
);
var
tuple
=
(
TTuple
)
ctor
.
Invoke
(
args
);
return
map
(
tuple
);
};
}
private
static
Func
<
IDataReader
,
TReturn
>
GenerateMapper
<
TFirst
,
TSecond
,
TThird
,
TFourth
,
TFifth
,
TSixth
,
TSeventh
,
TReturn
>(
Func
<
IDataReader
,
object
>
deserializer
,
Func
<
IDataReader
,
object
>[]
otherDeserializers
,
object
map
)
private
static
Func
<
IDataReader
,
TReturn
>
GenerateMapper
<
TFirst
,
TSecond
,
TThird
,
TFourth
,
TFifth
,
TSixth
,
TSeventh
,
TReturn
>(
Func
<
IDataReader
,
object
>
deserializer
,
Func
<
IDataReader
,
object
>[]
otherDeserializers
,
object
map
)
{
{
switch
(
otherDeserializers
.
Length
)
switch
(
otherDeserializers
.
Length
)
...
...
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