Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Can't get multi-mapping to work in Dapper

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...

like image 211
marc_s Avatar asked May 14 '11 11:05

marc_s


People also ask

How Dapper mapping works?

Dapper maps data to the first type in the same way as it does if only one generic parameter has been supplied to the QueryAsync<T> method. If is then told to map data to the Category type, and to assign the resulting object to the product's Category property.

What is splitOn in dapper?

splitOn: CustomerId will result in a null customer name. If you specify CustomerId,CustomerName as split points, dapper assumes you are trying to split up the result set into 3 objects. First starts at the beginning, second starts at CustomerId , third at CustomerName .

Can I use Linq with dapper?

Dapper Plus LINQ DynamicYou can execute query dynamically through the Eval-Expression.NET library. The Eval-Expression.NET library can be activated with the Dapper Plus license.


1 Answers

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();
like image 113
Sam Saffron Avatar answered Sep 20 '22 06:09

Sam Saffron