Commit b2952360 authored by Marc Gravell's avatar Marc Gravell

Merge branch 'Polylytics-arbitrary-multi-maps'

parents f8a189a9 3cd31e42
......@@ -1711,17 +1711,39 @@ private static IEnumerable<T> QueryImpl<T>(this IDbConnection cnn, CommandDefini
{
return MultiMap<TFirst, TSecond, TThird, TFourth, TFifth, TSixth, TSeventh, TReturn>(cnn, sql, map, param as object, transaction, buffered, splitOn, commandTimeout, commandType);
}
/// <summary>
/// Perform a multi mapping query with arbitrary input parameters
/// </summary>
/// <typeparam name="TReturn">The return type</typeparam>
/// <param name="cnn"></param>
/// <param name="sql"></param>
/// <param name="types">array of types in the recordset</param>
/// <param name="map"></param>
/// <param name="param"></param>
/// <param name="transaction"></param>
/// <param name="buffered"></param>
/// <param name="splitOn">The Field we should split and read the second object from (default: id)</param>
/// <param name="commandTimeout">Number of seconds before command execution timeout</param>
/// <param name="commandType">Is it a stored proc or a batch?</param>
/// <returns></returns>
public static IEnumerable<TReturn> Query<TReturn>(this IDbConnection cnn, string sql, Type[] types, Func<object[], TReturn> map, dynamic param = null, IDbTransaction transaction = null, bool buffered = true, string splitOn = "Id", int? commandTimeout = null, CommandType? commandType = null)
{
var command = new CommandDefinition(sql, (object)param, transaction, commandTimeout, commandType, buffered ? CommandFlags.Buffered : CommandFlags.None);
var results = MultiMapImpl<TReturn>(cnn, command, types, map, splitOn, null, null, true);
return buffered ? results.ToList() : results;
}
#endif
partial class DontMap { }
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)
{
var command = new CommandDefinition(sql, (object)param, transaction, commandTimeout, commandType, buffered ? CommandFlags.Buffered : CommandFlags.None);
var results = MultiMapImpl<TFirst, TSecond, TThird, TFourth, TFifth, TSixth, TSeventh, TReturn>(cnn, command, map, splitOn, null, null);
var results = MultiMapImpl<TFirst, TSecond, TThird, TFourth, TFifth, TSixth, TSeventh, TReturn>(cnn, command, map, splitOn, null, null, true);
return buffered ? results.ToList() : results;
}
static IEnumerable<TReturn> MultiMapImpl<TFirst, TSecond, TThird, TFourth, TFifth, TSixth, TSeventh, TReturn>(this IDbConnection cnn, CommandDefinition command, Delegate map, string splitOn, IDataReader reader, Identity identity)
static IEnumerable<TReturn> MultiMapImpl<TFirst, TSecond, TThird, TFourth, TFifth, TSixth, TSeventh, TReturn>(this IDbConnection cnn, CommandDefinition command, Delegate map, string splitOn, IDataReader reader, Identity identity, bool finalize)
{
object param = command.Parameters;
identity = identity ?? new Identity(command.CommandText, command.CommandType, cnn, typeof(TFirst), param == null ? null : param.GetType(), new[] { typeof(TFirst), typeof(TSecond), typeof(TThird), typeof(TFourth), typeof(TFifth), typeof(TSixth), typeof(TSeventh) });
......@@ -1760,7 +1782,82 @@ partial class DontMap { }
{
yield return mapIt(reader);
}
command.FireOutputCallbacks();
if(finalize)
{
while (reader.NextResult()) { }
command.FireOutputCallbacks();
}
}
}
finally
{
try
{
if (ownedReader != null)
{
ownedReader.Dispose();
}
}
finally
{
if (ownedCommand != null)
{
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)
{
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 == null ? null : 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 = ownedCommand.ExecuteReader();
reader = ownedReader;
}
DeserializerState deserializer = default(DeserializerState);
Func<IDataReader, object>[] otherDeserializers = null;
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(types.Length, deserializer.Func, otherDeserializers, map);
if (mapIt != null)
{
while (reader.Read())
{
yield return mapIt(reader);
}
if (finalize)
{
while (reader.NextResult()) { }
command.FireOutputCallbacks();
}
}
}
finally
......@@ -1806,6 +1903,22 @@ partial class DontMap { }
}
}
private static Func<IDataReader, TReturn> GenerateMapper<TReturn>(int length, Func<IDataReader, object> deserializer, Func<IDataReader, object>[] otherDeserializers, Func<object[], TReturn> map)
{
return r =>
{
var objects = new object[length];
objects[0] = deserializer(r);
for (var i = 1; i < length; ++i)
{
objects[i] = otherDeserializers[i - 1](r);
}
return map(objects);
};
}
private static Func<IDataReader, object>[] GenerateDeserializers(Type[] types, string splitOn, IDataReader reader)
{
var deserializers = new List<Func<IDataReader, object>>();
......@@ -3888,7 +4001,7 @@ private IEnumerable<T> ReadImpl<T>(Type type, bool buffered)
}, gridIndex);
try
{
foreach (var r in SqlMapper.MultiMapImpl<TFirst, TSecond, TThird, TFourth, TFifth, TSixth, TSeventh, TReturn>(null, default(CommandDefinition), func, splitOn, reader, identity))
foreach (var r in SqlMapper.MultiMapImpl<TFirst, TSecond, TThird, TFourth, TFifth, TSixth, TSeventh, TReturn>(null, default(CommandDefinition), func, splitOn, reader, identity, false))
{
yield return r;
}
......
......@@ -451,7 +451,7 @@ private static async Task<int> ExecuteImplAsync(IDbConnection cnn, CommandDefini
using (var reader = await cmd.ExecuteReaderAsync(wasClosed ? CommandBehavior.CloseConnection | CommandBehavior.SequentialAccess : CommandBehavior.SequentialAccess, command.CancellationToken).ConfigureAwait(false))
{
if (!command.Buffered) wasClosed = false; // handing back open reader; rely on command-behavior
var results = MultiMapImpl<TFirst, TSecond, TThird, TFourth, TFifth, TSixth, TSeventh, TReturn>(null, CommandDefinition.ForCallback(command.Parameters), map, splitOn, reader, identity);
var results = MultiMapImpl<TFirst, TSecond, TThird, TFourth, TFifth, TSixth, TSeventh, TReturn>(null, CommandDefinition.ForCallback(command.Parameters), map, splitOn, reader, identity, true);
return command.Buffered ? results.ToList() : results;
}
} finally
......@@ -459,8 +459,53 @@ private static async Task<int> ExecuteImplAsync(IDbConnection cnn, CommandDefini
if (wasClosed) cnn.Close();
}
}
/// <summary>
/// Perform a multi mapping query with arbitrary input parameters
/// </summary>
/// <typeparam name="TReturn">The return type</typeparam>
/// <param name="cnn"></param>
/// <param name="sql"></param>
/// <param name="types">array of types in the recordset</param>
/// <param name="map"></param>
/// <param name="param"></param>
/// <param name="transaction"></param>
/// <param name="buffered"></param>
/// <param name="splitOn">The Field we should split and read the second object from (default: id)</param>
/// <param name="commandTimeout">Number of seconds before command execution timeout</param>
/// <param name="commandType">Is it a stored proc or a batch?</param>
/// <returns></returns>
public static Task<IEnumerable<TReturn>> QueryAsync<TReturn>(this IDbConnection cnn, string sql, Type[] types, Func<object[], TReturn> map, dynamic param = null, IDbTransaction transaction = null, bool buffered = true, string splitOn = "Id", int? commandTimeout = null, CommandType? commandType = null)
{
var command = new CommandDefinition(sql, (object)param, transaction, commandTimeout, commandType, buffered ? CommandFlags.Buffered : CommandFlags.None, default(CancellationToken));
return MultiMapAsync<TReturn>(cnn, command, types, map, splitOn);
}
private static async Task<IEnumerable<TReturn>> MultiMapAsync<TReturn>(this IDbConnection cnn, CommandDefinition command, Type[] types, Func<object[], TReturn> map, string splitOn)
{
if (types.Length < 1)
{
throw new ArgumentException("you must provide at least one type to deserialize");
}
object param = command.Parameters;
var identity = new Identity(command.CommandText, command.CommandType, cnn, types[0], param == null ? null : param.GetType(), types);
var info = GetCacheInfo(identity, param, command.AddToCache);
bool wasClosed = cnn.State == ConnectionState.Closed;
try {
if (wasClosed) await ((DbConnection)cnn).OpenAsync().ConfigureAwait(false);
using (var cmd = (DbCommand)command.SetupCommand(cnn, info.ParamReader))
using (var reader = await cmd.ExecuteReaderAsync(command.CancellationToken).ConfigureAwait(false)) {
var results = MultiMapImpl<TReturn>(null, default(CommandDefinition), types, map, splitOn, reader, identity, true);
return command.Buffered ? results.ToList() : results;
}
}
finally {
if (wasClosed) cnn.Close();
}
}
private static IEnumerable<T> ExecuteReaderSync<T>(IDataReader reader, Func<IDataReader,object> func, object parameters)
private static IEnumerable<T> ExecuteReaderSync<T>(IDataReader reader, Func<IDataReader, object> func, object parameters)
{
using (reader)
{
......
......@@ -46,6 +46,8 @@ private static void RunTests()
method.Invoke(tester, null);
Console.WriteLine(" - OK!");
}
Console.WriteLine("(end of tests; press any key)");
Console.ReadKey();
}
......
......@@ -120,6 +120,24 @@ public void TestMultiMapWithSplitAsync()
}
}
public void TestMultiMapArbitraryWithSplitAsync() {
var sql = @"select 1 as id, 'abc' as name, 2 as id, 'def' as name";
using (var connection = Program.GetOpenConnection()) {
var productQuery = connection.QueryAsync<Product>(sql, new[] { typeof(Product), typeof(Category) }, (objects) => {
var prod = (Product)objects[0];
prod.Category = (Category)objects[1];
return prod;
});
var product = productQuery.Result.First();
// assertions
product.Id.IsEqualTo(1);
product.Name.IsEqualTo("abc");
product.Category.Id.IsEqualTo(2);
product.Category.Name.IsEqualTo("def");
}
}
public void TestMultiMapWithSplitClosedConnAsync()
{
var sql = @"select 1 as id, 'abc' as name, 2 as id, 'def' as name";
......@@ -577,5 +595,113 @@ class Address
public string Name { get; set; }
public int PersonId { get; set; }
}
class ReviewBoard
{
public int Id { get; set; }
public string Name { get; set; }
public User User1 { get; set; }
public User User2 { get; set; }
public User User3 { get; set; }
public User User4 { get; set; }
public User User5 { get; set; }
public User User6 { get; set; }
public User User7 { get; set; }
public User User8 { get; set; }
public User User9 { get; set; }
}
class User
{
public int Id { get; set; }
public string Name { get; set; }
}
public void TestMultiMapArbitraryMaps()
{
// please excuse the trite example, but it is easier to follow than a more real-world one
var createSql = @"
create table #ReviewBoards (Id int, Name varchar(20), User1Id int, User2Id int, User3Id int, User4Id int, User5Id int, User6Id int, User7Id int, User8Id int, User9Id int)
create table #Users (Id int, Name varchar(20))
insert #Users values(1, 'User 1')
insert #Users values(2, 'User 2')
insert #Users values(3, 'User 3')
insert #Users values(4, 'User 4')
insert #Users values(5, 'User 5')
insert #Users values(6, 'User 6')
insert #Users values(7, 'User 7')
insert #Users values(8, 'User 8')
insert #Users values(9, 'User 9')
insert #ReviewBoards values(1, 'Review Board 1', 1, 2, 3, 4, 5, 6, 7, 8, 9)
";
using (var connection = Program.GetOpenConnection())
{
connection.ExecuteAsync(createSql).Wait();
try
{
var sql = @"
select
rb.Id, rb.Name,
u1.*, u2.*, u3.*, u4.*, u5.*, u6.*, u7.*, u8.*, u9.*
from #ReviewBoards rb
inner join #Users u1 on u1.Id = rb.User1Id
inner join #Users u2 on u2.Id = rb.User2Id
inner join #Users u3 on u3.Id = rb.User3Id
inner join #Users u4 on u4.Id = rb.User4Id
inner join #Users u5 on u5.Id = rb.User5Id
inner join #Users u6 on u6.Id = rb.User6Id
inner join #Users u7 on u7.Id = rb.User7Id
inner join #Users u8 on u8.Id = rb.User8Id
inner join #Users u9 on u9.Id = rb.User9Id
";
var types = new[] { typeof(ReviewBoard), typeof(User), typeof(User), typeof(User), typeof(User), typeof(User), typeof(User), typeof(User), typeof(User), typeof(User) };
Func<object[], ReviewBoard> mapper = (objects) =>
{
var board = (ReviewBoard)objects[0];
board.User1 = (User)objects[1];
board.User2 = (User)objects[2];
board.User3 = (User)objects[3];
board.User4 = (User)objects[4];
board.User5 = (User)objects[5];
board.User6 = (User)objects[6];
board.User7 = (User)objects[7];
board.User8 = (User)objects[8];
board.User9 = (User)objects[9];
return board;
};
var data = connection.QueryAsync<ReviewBoard>(sql, types, mapper).Result.ToList();
var p = data.First();
p.Id.IsEqualTo(1);
p.Name.IsEqualTo("Review Board 1");
p.User1.Id.IsEqualTo(1);
p.User2.Id.IsEqualTo(2);
p.User3.Id.IsEqualTo(3);
p.User4.Id.IsEqualTo(4);
p.User5.Id.IsEqualTo(5);
p.User6.Id.IsEqualTo(6);
p.User7.Id.IsEqualTo(7);
p.User8.Id.IsEqualTo(8);
p.User9.Id.IsEqualTo(9);
p.User1.Name.IsEqualTo("User 1");
p.User2.Name.IsEqualTo("User 2");
p.User3.Name.IsEqualTo("User 3");
p.User4.Name.IsEqualTo("User 4");
p.User5.Name.IsEqualTo("User 5");
p.User6.Name.IsEqualTo("User 6");
p.User7.Name.IsEqualTo("User 7");
p.User8.Name.IsEqualTo("User 8");
p.User9.Name.IsEqualTo("User 9");
}
finally
{
connection.Execute("drop table #Users drop table #ReviewBoards");
}
}
}
}
}
\ No newline at end of file
......@@ -47,6 +47,10 @@
<Reference Include="EntityFramework.SqlServer">
<HintPath>..\packages\EntityFramework.6.1.0\lib\net45\EntityFramework.SqlServer.dll</HintPath>
</Reference>
<Reference Include="FSharp.Core, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
<SpecificVersion>False</SpecificVersion>
<HintPath>..\packages\FSharp.Core.4.0.0\lib\FSharp.Core.dll</HintPath>
</Reference>
<Reference Include="FSharp.PowerPack, Version=2.0.0.0, Culture=neutral, PublicKeyToken=a19089b1c74d0809, processorArchitecture=MSIL">
<Private>True</Private>
<HintPath>..\packages\FSPowerPack.Community.2.0.0.0\Lib\Net40\FSharp.PowerPack.dll</HintPath>
......
......@@ -785,7 +785,105 @@ public void TestMultiMap()
}
}
class ReviewBoard
{
public int Id { get; set; }
public string Name { get; set; }
public User User1 { get; set; }
public User User2 { get; set; }
public User User3 { get; set; }
public User User4 { get; set; }
public User User5 { get; set; }
public User User6 { get; set; }
public User User7 { get; set; }
public User User8 { get; set; }
public User User9 { get; set; }
}
public void TestMultiMapArbitraryMaps()
{
// please excuse the trite example, but it is easier to follow than a more real-world one
var createSql = @"
create table #ReviewBoards (Id int, Name varchar(20), User1Id int, User2Id int, User3Id int, User4Id int, User5Id int, User6Id int, User7Id int, User8Id int, User9Id int)
create table #Users (Id int, Name varchar(20))
insert #Users values(1, 'User 1')
insert #Users values(2, 'User 2')
insert #Users values(3, 'User 3')
insert #Users values(4, 'User 4')
insert #Users values(5, 'User 5')
insert #Users values(6, 'User 6')
insert #Users values(7, 'User 7')
insert #Users values(8, 'User 8')
insert #Users values(9, 'User 9')
insert #ReviewBoards values(1, 'Review Board 1', 1, 2, 3, 4, 5, 6, 7, 8, 9)
";
connection.Execute(createSql);
try
{
var sql = @"
select
rb.Id, rb.Name,
u1.*, u2.*, u3.*, u4.*, u5.*, u6.*, u7.*, u8.*, u9.*
from #ReviewBoards rb
inner join #Users u1 on u1.Id = rb.User1Id
inner join #Users u2 on u2.Id = rb.User2Id
inner join #Users u3 on u3.Id = rb.User3Id
inner join #Users u4 on u4.Id = rb.User4Id
inner join #Users u5 on u5.Id = rb.User5Id
inner join #Users u6 on u6.Id = rb.User6Id
inner join #Users u7 on u7.Id = rb.User7Id
inner join #Users u8 on u8.Id = rb.User8Id
inner join #Users u9 on u9.Id = rb.User9Id
";
var types = new[] { typeof(ReviewBoard), typeof(User), typeof(User), typeof(User), typeof(User), typeof(User), typeof(User), typeof(User), typeof(User), typeof(User) };
Func<object[], ReviewBoard> mapper = (objects) =>
{
var board = (ReviewBoard)objects[0];
board.User1 = (User)objects[1];
board.User2 = (User)objects[2];
board.User3 = (User)objects[3];
board.User4 = (User)objects[4];
board.User5 = (User)objects[5];
board.User6 = (User)objects[6];
board.User7 = (User)objects[7];
board.User8 = (User)objects[8];
board.User9 = (User)objects[9];
return board;
};
var data = connection.Query<ReviewBoard>(sql, types, mapper).ToList();
var p = data.First();
p.Id.IsEqualTo(1);
p.Name.IsEqualTo("Review Board 1");
p.User1.Id.IsEqualTo(1);
p.User2.Id.IsEqualTo(2);
p.User3.Id.IsEqualTo(3);
p.User4.Id.IsEqualTo(4);
p.User5.Id.IsEqualTo(5);
p.User6.Id.IsEqualTo(6);
p.User7.Id.IsEqualTo(7);
p.User8.Id.IsEqualTo(8);
p.User9.Id.IsEqualTo(9);
p.User1.Name.IsEqualTo("User 1");
p.User2.Name.IsEqualTo("User 2");
p.User3.Name.IsEqualTo("User 3");
p.User4.Name.IsEqualTo("User 4");
p.User5.Name.IsEqualTo("User 5");
p.User6.Name.IsEqualTo("User 6");
p.User7.Name.IsEqualTo("User 7");
p.User8.Name.IsEqualTo("User 8");
p.User9.Name.IsEqualTo("User 9");
}
finally
{
connection.Execute("drop table #Users drop table #ReviewBoards");
}
}
public void TestMultiMapGridReader()
{
......
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="EntityFramework" version="6.1.0" targetFramework="net45" />
<package id="FSharp.Core" version="4.0.0" targetFramework="net45" />
<package id="FSPowerPack.Community" version="2.0.0.0" />
<package id="Microsoft.SqlServer.Types" version="11.0.1" targetFramework="net45" />
<package id="Npgsql" version="2.0.11" />
......
This source diff could not be displayed because it is too large. You can view the blob instead.
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