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(...) - 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 { 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 - note the name // is different to avoid ambiguity with the primary Query 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(); 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"); } } } }