19

Playing around with Dapper, I'm quite pleased with the results so far - intriguing!

But now, my next scenario would be to read data from two tables - a Student and an Address table.

Student table has a primary key of StudentID (INT IDENTITY), Address has an AddressID (INT IDENTITY). Student also has an FK called AddressID linking into the Address table.

My idea was to create two classes, one for each table, with the properties I'm interested in. Additionally, I put an PrimaryAddress property of type Address onto my Student class in C#.

I then tried to retrieve both student and address data in a single query - I mimick the sample that's given on the Github page:

var data = connection.Query<Post, User>(sql, (post, user) => { post.Owner = user; });
var post = data.First();

Here, a Post and a User are retrieved, and the owner of the post is set to the user - the type returned is a Post - correct?

So in my code, I define two parameters to the generic Query extension method - a Student as the first which should be returned, and an Address as the second, which will be stored onto the student instance:

var student = _conn.Query<Student, Address>
                  ("SELECT s.*, a.* FROM dbo.Student s 
                        INNER JOIN dbo.Address a ON s.AddressID = a.AddressID 
                        WHERE s.StudentenID = @Id", 
                    (stu, adr) => { stu.PrimaryAddress = adr; },  
                    new { Id = 4711 });

Trouble is - I get an error in Visual Studio:

Using the generic method 'Dapper.SqlMapper.Query(System.Data.IDbConnection, string, System.Func, dynamic, System.Data.IDbTransaction, bool, string, int?, System.Data.CommandType?)' requires 6 type arguments

I don't really understand why Dapper insists on using this overload with 6 type arguments...

Crypth
  • 1,576
  • 18
  • 32
marc_s
  • 732,580
  • 175
  • 1,330
  • 1,459

1 Answers1

23

That would be cause I changed APIs and forgot to update the documentation, I corrected the error.

Be sure to have a look at Tests.cs for a full up-to-date spec.

In particular, the old API used to take in an Action<T,U> to perform the mapping, the trouble was that it felt both arbitrary and inflexible. You could not fully control the return type. The new APIs take in a Func<T,U,V>. So you can control the type you get back from the mapper and it does not need to be a mapped type.

I just tied up some additional flexibility around multi mapping, this test should make it clear:

class Person
{
    public int PersonId { get; set; }
    public string Name { get; set; }
}

class Address
{
    public int AddressId { get; set; }
    public string Name { get; set; }
    public int PersonId { get; set; }
}

class Extra
{
    public int Id { get; set; }
    public string Name { get; set; }
}

public void TestFlexibleMultiMapping()
{
    var sql = 
@"select 
1 as PersonId, 'bob' as Name, 
2 as AddressId, 'abc street' as Name, 1 as PersonId,
3 as Id, 'fred' as Name
";
    var personWithAddress = connection.Query<Person, Address, Extra, Tuple<Person, Address,Extra>>
        (sql, (p,a,e) => Tuple.Create(p, a, e), splitOn: "AddressId,Id").First();

    personWithAddress.Item1.PersonId.IsEqualTo(1);
    personWithAddress.Item1.Name.IsEqualTo("bob");
    personWithAddress.Item2.AddressId.IsEqualTo(2);
    personWithAddress.Item2.Name.IsEqualTo("abc street");
    personWithAddress.Item2.PersonId.IsEqualTo(1);
    personWithAddress.Item3.Id.IsEqualTo(3);
    personWithAddress.Item3.Name.IsEqualTo("fred");

}

Dapper pipes all the multi mapping APIs through a single method, so if something fails it will end up in the 6 param one. The other piece of the puzzle was that I did not allow for some super flexible splits, which I just added.

Note, the splitOn param will default to Id, meaning it will take a column called id or Id as the first object boundary. However if you need boundaries on multiple primary keys that have different names for say a "3 way" multi mapping, you can now pass in a comma separated list.

So if we were to fix the above, probably the following would work:

 var student = _conn.Query<Student,Address,Student>
              ("SELECT s.*, a.* FROM dbo.Student s 
                    INNER JOIN dbo.Address a ON s.AddressID = a.AddressID 
                    WHERE s.StudentenID = @Id", 
                (stu, adr) => { stu.PrimaryAddress = adr; return stu;},  
                new { Id = 4711 }, splitOn: "AddressID").FirstOrDefault();
Sam Saffron
  • 128,308
  • 78
  • 326
  • 506
  • OK, thanks - but how can I add some criteria like `StudentId = @Id` (and then set `@ID = 4711`) to this multi-mapping query?? – marc_s May 14 '11 at 13:21
  • @marc_s keep in mind there are a ton of optional params to control stuff like command type, command timeout and so on. one really important one to learn is "buffered" which allows you to buffer the results so you don't have SqlReaders stepping on each other – Sam Saffron May 14 '11 at 13:40
  • Great stuff - thanks so much for your response, works like a charm now!! One last gripe: it works on .NET 4 only - any hope you'll backport to .NET 3.5 ?? I'm still very much stuck on .NET 3.5 for a lot of projects, unfortunately.... – marc_s May 14 '11 at 14:09
  • @marc_s cheers, it should be pretty straight forward to backport it would involve removing all traces of `dynamic` and `optional params`, I totally support having a backport of dapper in a contrib project in the main repo if you feel up to it. nobody has picked up this project yet – Sam Saffron May 14 '11 at 14:14
  • 1
    @Sam - it would be pretty easy (I think) to do a backport (probably to 2.0 if needed) via compiler symbols. We can also (IIRC) configure nuget to deliver the most appropriate dll. (says the fool who has parallel support for c# 1.2 upwards, on multiple different BCLs/CLIs for pb-net) – Marc Gravell May 14 '11 at 17:38
  • 1
    @Marc Gravell, @Sam Saffron: YAY ! A NuGet-powered, .NET 2 through .NET 4 compatible Dapper - is it Christmas already? :-) – marc_s May 14 '11 at 18:03
  • @marc_s I honestly don't think it is much work. The only trick is writing a build packager so you don't have to do multiple builds manually – Marc Gravell May 14 '11 at 18:05
  • @Sam in case it was unclear, I'm happy to take that piece of work - I'm not volunteering on your behalf :) – Marc Gravell May 14 '11 at 18:09
  • @marc_s - I just pushed support for this; just add CSHARP30 as a build symbol – Marc Gravell May 16 '11 at 10:13
  • @SamSaffron Unless I'm mistaken, this example is a one-to-one scenario: a person has one address. Is it possible to use multi-mapping to get an Order and that order's OrderItem objects? Say the object representation of that was order.items[x], etc. Or should I just use Dapper's multiple-results functionality instead? – Diego Barros Aug 13 '14 at 01:57