Commit db1c00a8 authored by Sam Saffron's avatar Sam Saffron

Added support for a multi mapping grid reader

parent d9ef0084
...@@ -214,6 +214,12 @@ internal Identity ForGrid(Type primaryType, int gridIndex) ...@@ -214,6 +214,12 @@ internal Identity ForGrid(Type primaryType, int gridIndex)
{ {
return new Identity(sql, connectionString, primaryType, parametersType, null, gridIndex); return new Identity(sql, connectionString, primaryType, parametersType, null, gridIndex);
} }
internal Identity ForGrid(Type primaryType, Type[] otherTypes, int gridIndex)
{
return new Identity(sql, connectionString, primaryType, parametersType, otherTypes, gridIndex);
}
internal Identity(string sql, IDbConnection connection, Type type, Type parametersType, Type[] otherTypes) internal Identity(string sql, IDbConnection connection, Type type, Type parametersType, Type[] otherTypes)
: this(sql, connection.ConnectionString, type, parametersType, otherTypes, 0) : this(sql, connection.ConnectionString, type, parametersType, otherTypes, 0)
{ } { }
...@@ -473,137 +479,161 @@ class DontMap { } ...@@ -473,137 +479,161 @@ class DontMap { }
static IEnumerable<TReturn> MultiMap<TFirst, TSecond, TThird, TFourth, TFifth, TReturn>( static IEnumerable<TReturn> MultiMap<TFirst, TSecond, TThird, TFourth, TFifth, TReturn>(
this IDbConnection cnn, string sql, object map, object param, IDbTransaction transaction, bool buffered, string splitOn, int? commandTimeout, CommandType? commandType) this IDbConnection cnn, string sql, object map, object param, IDbTransaction transaction, bool buffered, string splitOn, int? commandTimeout, CommandType? commandType)
{ {
var results = MultiMapImpl<TFirst, TSecond, TThird, TFourth, TFifth, TReturn>(cnn, sql, map, param, transaction, splitOn, commandTimeout, commandType); var results = MultiMapImpl<TFirst, TSecond, TThird, TFourth, TFifth, TReturn>(cnn, sql, map, param, transaction, splitOn, commandTimeout, commandType, null, null);
return buffered ? results.ToList() : results; return buffered ? results.ToList() : results;
} }
static IEnumerable<TReturn> MultiMapImpl<TFirst, TSecond, TThird, TFourth, TFifth, TReturn>(this IDbConnection cnn, string sql, object map, object param, IDbTransaction transaction, string splitOn, int? commandTimeout, CommandType? commandType) static IEnumerable<TReturn> MultiMapImpl<TFirst, TSecond, TThird, TFourth, TFifth, TReturn>(this IDbConnection cnn, string sql, object map, object param, IDbTransaction transaction, string splitOn, int? commandTimeout, CommandType? commandType, IDataReader reader, Identity identity)
{ {
Identity identity = new Identity(sql, cnn, typeof(TFirst), (object)param == null ? null : ((object)param).GetType(), new[] { typeof(TFirst), typeof(TSecond), typeof(TThird), typeof(TFourth), typeof(TFifth) }); identity = identity ?? new Identity(sql, cnn, typeof(TFirst), (object)param == null ? null : ((object)param).GetType(), new[] { typeof(TFirst), typeof(TSecond), typeof(TThird), typeof(TFourth), typeof(TFifth) });
CacheInfo info = GetCacheInfo(identity); CacheInfo info = GetCacheInfo(identity);
using (var cmd = SetupCommand(cnn, transaction, sql, info.ParamReader, (object)param, commandTimeout, commandType)) IDbCommand ownedCommand = null;
IDataReader ownedReader = null;
try
{ {
using (var reader = cmd.ExecuteReader()) if (reader == null)
{ {
if (info.Deserializer == null) ownedCommand = SetupCommand(cnn, transaction, sql, info.ParamReader, (object)param, commandTimeout, commandType);
{ ownedReader = ownedCommand.ExecuteReader();
int current = 0; reader = ownedReader;
}
var splits = splitOn.Split(',').ToArray();
var splitIndex = 0;
Func<int> nextSplit = () =>
{
var currentSplit = splits[splitIndex];
if (splits.Length > splitIndex + 1)
{
splitIndex++;
}
int pos;
for (pos = current + 1; pos < reader.FieldCount; pos++)
{
// some people like ID some id ... assuming case insensitive splits for now
if (splitOn == "*" || string.Equals(reader.GetName(pos), currentSplit, StringComparison.InvariantCultureIgnoreCase))
{
break;
}
}
current = pos;
return pos;
};
var otherDeserializer = new List<object>(); if (info.Deserializer == null)
{
int current = 0;
int split = nextSplit(); var splits = splitOn.Split(',').ToArray();
info.Deserializer = GetDeserializer<TFirst>(reader, 0, split, false); var splitIndex = 0;
if (typeof(TSecond) != typeof(DontMap)) Func<int> nextSplit = () =>
{ {
var next = nextSplit(); var currentSplit = splits[splitIndex];
otherDeserializer.Add(GetDeserializer<TSecond>(reader, split, next - split, true)); if (splits.Length > splitIndex + 1)
split = next;
}
if (typeof(TThird) != typeof(DontMap))
{
var next = nextSplit();
otherDeserializer.Add(GetDeserializer<TThird>(reader, split, next - split, true));
split = next;
}
if (typeof(TFourth) != typeof(DontMap))
{ {
var next = nextSplit(); splitIndex++;
otherDeserializer.Add(GetDeserializer<TFourth>(reader, split, next - split, true));
split = next;
} }
if (typeof(TFifth) != typeof(DontMap)) int pos;
for (pos = current + 1; pos < reader.FieldCount; pos++)
{ {
var next = nextSplit(); // some people like ID some id ... assuming case insensitive splits for now
otherDeserializer.Add(GetDeserializer<TFifth>(reader, split, next - split, true)); if (splitOn == "*" || string.Equals(reader.GetName(pos), currentSplit, StringComparison.InvariantCultureIgnoreCase))
{
break;
}
} }
current = pos;
return pos;
};
var otherDeserializer = new List<object>();
info.OtherDeserializers = otherDeserializer.ToArray(); int split = nextSplit();
info.Deserializer = GetDeserializer<TFirst>(reader, 0, split, false);
SetQueryCache(identity, info); if (typeof(TSecond) != typeof(DontMap))
{
var next = nextSplit();
otherDeserializer.Add(GetDeserializer<TSecond>(reader, split, next - split, true));
split = next;
}
if (typeof(TThird) != typeof(DontMap))
{
var next = nextSplit();
otherDeserializer.Add(GetDeserializer<TThird>(reader, split, next - split, true));
split = next;
}
if (typeof(TFourth) != typeof(DontMap))
{
var next = nextSplit();
otherDeserializer.Add(GetDeserializer<TFourth>(reader, split, next - split, true));
split = next;
}
if (typeof(TFifth) != typeof(DontMap))
{
var next = nextSplit();
otherDeserializer.Add(GetDeserializer<TFifth>(reader, split, next - split, true));
} }
var deserializer = (Func<IDataReader, TFirst>)info.Deserializer; info.OtherDeserializers = otherDeserializer.ToArray();
var deserializer2 = (Func<IDataReader, TSecond>)info.OtherDeserializers[0];
SetQueryCache(identity, info);
}
var deserializer = (Func<IDataReader, TFirst>)info.Deserializer;
var deserializer2 = (Func<IDataReader, TSecond>)info.OtherDeserializers[0];
Func<IDataReader, TReturn> mapIt = null;
if (info.OtherDeserializers.Length == 1)
{
mapIt = r => ((Func<TFirst, TSecond, TReturn>)map)(deserializer(r), deserializer2(r));
}
Func<IDataReader, TReturn> mapIt = null; if (info.OtherDeserializers.Length > 1)
{
var deserializer3 = (Func<IDataReader, TThird>)info.OtherDeserializers[1];
if (info.OtherDeserializers.Length == 1) if (info.OtherDeserializers.Length == 2)
{ {
mapIt = r => ((Func<TFirst, TSecond, TReturn>)map)(deserializer(r), deserializer2(r)); mapIt = r => ((Func<TFirst, TSecond, TThird, TReturn>)map)(deserializer(r), deserializer2(r), deserializer3(r));
} }
if (info.OtherDeserializers.Length > 2)
if (info.OtherDeserializers.Length > 1)
{ {
var deserializer3 = (Func<IDataReader, TThird>)info.OtherDeserializers[1]; var deserializer4 = (Func<IDataReader, TFourth>)info.OtherDeserializers[2];
if (info.OtherDeserializers.Length == 3)
if (info.OtherDeserializers.Length == 2)
{ {
mapIt = r => ((Func<TFirst, TSecond, TThird, TReturn>)map)(deserializer(r), deserializer2(r), deserializer3(r)); mapIt = r => ((Func<TFirst, TSecond, TThird, TFourth, TReturn>)map)(deserializer(r), deserializer2(r), deserializer3(r), deserializer4(r));
} }
if (info.OtherDeserializers.Length > 2)
{
var deserializer4 = (Func<IDataReader, TFourth>)info.OtherDeserializers[2];
if (info.OtherDeserializers.Length == 3)
{
mapIt = r => ((Func<TFirst, TSecond, TThird, TFourth, TReturn>)map)(deserializer(r), deserializer2(r), deserializer3(r), deserializer4(r));
}
if (info.OtherDeserializers.Length > 3) if (info.OtherDeserializers.Length > 3)
{ {
#if CSHARP30 #if CSHARP30
throw new NotSupportedException(); throw new NotSupportedException();
#else #else
var deserializer5 = (Func<IDataReader, TFifth>)info.OtherDeserializers[3]; var deserializer5 = (Func<IDataReader, TFifth>)info.OtherDeserializers[3];
mapIt = r => ((Func<TFirst, TSecond, TThird, TFourth, TFifth, TReturn>)map)(deserializer(r), deserializer2(r), deserializer3(r), deserializer4(r), deserializer5(r)); mapIt = r => ((Func<TFirst, TSecond, TThird, TFourth, TFifth, TReturn>)map)(deserializer(r), deserializer2(r), deserializer3(r), deserializer4(r), deserializer5(r));
#endif #endif
}
} }
} }
}
if (mapIt != null) if (mapIt != null)
{
bool clean = true;
try
{ {
bool clean = true; while (reader.Read())
try
{
while (reader.Read())
{
clean = false;
TReturn next = mapIt(reader);
clean = true;
yield return next;
}
}
finally
{ {
if (!clean) PurgeQueryCache(identity); clean = false;
TReturn next = mapIt(reader);
clean = true;
yield return next;
} }
} }
finally
{
if (!clean) PurgeQueryCache(identity);
}
}
}
finally
{
try
{
if (ownedReader != null)
{
ownedReader.Dispose();
}
}
finally
{
if (ownedCommand != null)
{
ownedCommand.Dispose();
}
} }
} }
} }
...@@ -1254,6 +1284,7 @@ public class GridReader : IDisposable ...@@ -1254,6 +1284,7 @@ public class GridReader : IDisposable
private IDataReader reader; private IDataReader reader;
private IDbCommand command; private IDbCommand command;
private Identity identity; private Identity identity;
internal GridReader(IDbCommand command, IDataReader reader, Identity identity) internal GridReader(IDbCommand command, IDataReader reader, Identity identity)
{ {
this.command = command; this.command = command;
...@@ -1279,7 +1310,63 @@ public IEnumerable<T> Read<T>() ...@@ -1279,7 +1310,63 @@ public IEnumerable<T> Read<T>()
return ReadDeferred(gridIndex, deserializer, typedIdentity); return ReadDeferred(gridIndex, deserializer, typedIdentity);
} }
// todo multimapping. private IEnumerable<TReturn> MultiReadInternal<TFirst, TSecond, TThird, TFourth, TFifth, TReturn>(object func, string splitOn)
{
var identity = this.identity.ForGrid(typeof(TReturn), new Type[] {
typeof(TFirst),
typeof(TSecond),
typeof(TThird),
typeof(TFourth),
typeof(TFifth)
}, gridIndex);
try
{
foreach (var r in SqlMapper.MultiMapImpl<TFirst, TSecond, DontMap, DontMap, DontMap, TReturn>(null, null, func, null, null, splitOn, null, null, reader, identity))
{
yield return r;
}
}
finally
{
NextResult();
}
}
#if CSHARP30
public IEnumerable<TReturn> Read<TFirst, TSecond, TReturn>(Func<TFirst, TSecond, TReturn> func, string splitOn)
#else
public IEnumerable<TReturn> Read<TFirst, TSecond, TReturn>(Func<TFirst, TSecond, TReturn> func, string splitOn = "id")
#endif
{
return MultiReadInternal<TFirst, TSecond, DontMap, DontMap, DontMap, TReturn>(func, splitOn);
}
#if CSHARP30
public IEnumerable<TReturn> Read<TFirst, TSecond, TThird, TReturn>(Func<TFirst, TSecond, TThird, TReturn> func, string splitOn)
#else
public IEnumerable<TReturn> Read<TFirst, TSecond, TThird, TReturn>(Func<TFirst, TSecond, TThird, TReturn> func, string splitOn = "id")
#endif
{
return MultiReadInternal<TFirst, TSecond, TThird, DontMap, DontMap, TReturn>(func, splitOn);
}
#if CSHARP30
public IEnumerable<TReturn> Read<TFirst, TSecond, TThird, TFourth, TReturn>(Func<TFirst, TSecond, TThird, TFourth, TReturn> func, string splitOn)
#else
public IEnumerable<TReturn> Read<TFirst, TSecond, TThird, TFourth, TReturn>(Func<TFirst, TSecond, TThird, TFourth, TReturn> func, string splitOn = "id")
#endif
{
return MultiReadInternal<TFirst, TSecond, TThird, TFourth, DontMap, TReturn>(func, splitOn);
}
#if !CSHARP30
public IEnumerable<TReturn> Read<TFirst, TSecond, TThird, TFourth, TFifth, TReturn>(Func<TFirst, TSecond, TThird, TFourth, TFifth, TReturn> func, string splitOn = "id")
{
return MultiReadInternal<TFirst, TSecond, TThird, TFourth, TFifth, TReturn>(func, splitOn);
}
#endif
private IEnumerable<T> ReadDeferred<T>(int index, Func<IDataReader, T> deserializer, Identity typedIdentity) private IEnumerable<T> ReadDeferred<T>(int index, Func<IDataReader, T> deserializer, Identity typedIdentity)
{ {
......
...@@ -348,6 +348,51 @@ public void TestMultiMap() ...@@ -348,6 +348,51 @@ public void TestMultiMap()
} }
public void TestMultiMapGridReader()
{
var 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);
var sql =
@"select p.*, u.Id, u.Name + '0' Name from #Posts p
left join #Users u on u.Id = p.OwnerId
Order by p.Id
select p.*, u.Id, u.Name + '1' Name from #Posts p
left join #Users u on u.Id = p.OwnerId
Order by p.Id
";
var grid = connection.QueryMultiple(sql);
for (int i = 0; i < 2; i++)
{
var data = grid.Read<Post, User, Post>((post, user) => { post.Owner = user; return post; }).ToList();
var p = data.First();
p.Content.IsEqualTo("Sams Post1");
p.Id.IsEqualTo(1);
p.Owner.Name.IsEqualTo("Sam" + i);
p.Owner.Id.IsEqualTo(99);
data[2].Owner.IsNull();
}
connection.Execute("drop table #Users drop table #Posts");
}
public void TestMultiMapDynamic() public void TestMultiMapDynamic()
{ {
var createSql = @" var createSql = @"
...@@ -810,6 +855,7 @@ public void ParentChildIdentityAssociations() ...@@ -810,6 +855,7 @@ 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();
} }
/* TODO: /* TODO:
* *
public void TestMagicParam() public void TestMagicParam()
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment