39

Is it possible to use anonymous types with Dapper?

I can see how you can use dynamic i.e.

connection.Query<dynamic>(blah, blah, blah) 

is it then possible to do a

.Select(p=> new { A, B ,C }) 

or some variation of that afterwards?

Edit

I thought I'd show you how I am using Dapper at the moment. I tend to cache (using an InMemoryCache) data so I just do one big query at the beginning (which is super quick using Dapper) then I use Linq to sort it all out in my Repository.

using System;
using System.Collections.Generic;
using System.Configuration;
using System.Data.Common;
using System.Linq;
using Dapper;

namespace SomeNamespace.Data
{
public class DapperDataContext : IDisposable
{
    private readonly string _connectionString;
    private readonly DbProviderFactory _provider;
    private readonly string _providerName;

    public DapperDataContext()
    {
        const string connectionStringName = " DataContextConnectionString";
        _connectionString = ConfigurationManager.ConnectionStrings[connectionStringName].ConnectionString;
        _providerName = ConfigurationManager.ConnectionStrings[connectionStringName].ProviderName;
        _provider = DbProviderFactories.GetFactory(_providerName);
    }

    public IEnumerable<MyDataView> MyData1 { get; private set; }
    public IEnumerable<MyDataView> MyData2 { get; private set; }

    protected string SqlSelectMyTable1Query
    {
        get
        {
            return @"SELECT Id, A, B, C from table1Name";
        }
    }   


protected string SqlSelectMyTable2Query
{
    get
    {
    return @"SELECT Id, A, B, C from table2Name";
    }
    }

    public void Dispose()
    {
    }

    public void Refresh()
    {
        using (var connection = _provider.CreateConnection())
        {
            // blow up if null
            connection.ConnectionString = _connectionString;
            connection.Open();

            var sql = String.Join(" ",
                            new[]
                                {
                                    SqlSelectMyTable1Query,
                                    SqlSelectMyTable2Query
                                });

            using (var multi = connection.QueryMultiple(sql))
            {
                MyData1 = multi.Read<MyDataView>().ToList();
                MyData2 = multi.Read<MyDataView>().ToList();
            }
        }
    }

    public class MyDataView
    {
        public long Id { get; set; }
        public string A { get; set; }
        public string B { get; set; }
        public string C { get; set; }
    }      
}
}

The InMemoryCache looks like this

namespace Libs.Web
{
public class InMemoryCache : ICacheService
{
    #region ICacheService Members

    public T Get<T>(string cacheId, Func<T> getItemCallback) where T : class
    {
        var item = HttpRuntime.Cache.Get(cacheId) as T;
        if (item == null)
        {
            item = getItemCallback();
            HttpContext.Current.Cache.Insert(cacheId, item);
        }
        return item;
    }

    public void Clear(string cacheId)
    {
        HttpContext.Current.Cache.Remove(cacheId);
    }

    #endregion
}

public interface ICacheService
{
    T Get<T>(string cacheId, Func<T> getItemCallback) where T : class;
    void Clear(string cacheId);
}
}
Peter
  • 7,792
  • 9
  • 63
  • 94
  • Yeah I'm kind of expecting this kind of thing but am currently getting no joy var result = multi.Read().Select((p)=> new {Id = p["Id"]}).ToList(); – Peter May 27 '11 at 02:56
  • const string testsql = @"SELECT Id FROM table ;"; var result = connection.Query(testsql).Select((p) => new { Id = p.Id }); – Peter May 27 '11 at 03:06
  • If you genuinely mean via Select, then it should pretty-much work as written especially if you cast the properties so it knows the types. I will test it when I get a sec – Marc Gravell May 27 '11 at 16:23
  • I would upvote this question if the answer was revelant to me, but it's not imho. I'd like anonymous types without the dynamic intermediate. On a little mapper i made, i use connection.Query("SELECT * FROM Person").MapTo(() => new { Id = default(int), Name = default(string), Age = default(int?)}) to declare anonymous types, I'd love something like that inside Dapper :) – Guillaume86 Jun 13 '11 at 13:52
  • well the answer given is correct so I don't understand your reluctance to upvote. On your other point, I agree totally. – Peter Jun 19 '11 at 23:16

5 Answers5

48

Is it possible to use anonymous types with Dapper?

Sure see the non-generic Query override, it return a dynamic IDictionary<string, object> this object is an expando that can either be cast or accessed with dot notation.

Eg:

var v = connection.Query("select 1 as a, 2 as b").First(); 
Console.Write("{0} {1}",v.a, v.b) // prints: 1 2

is it then possible to do a .Select

Sure, you get an IEnumerable<dynamic> ... you can run anything you want on that.

Matt
  • 548
  • 9
  • 24
Sam Saffron
  • 128,308
  • 78
  • 326
  • 506
  • so what you are saying is that this `const string testsql = @"SELECT Id FROM table ;"; var result = connection.Query(testsql).ToList().Select((p) => new { Id = p.Id });` should work? – Peter May 30 '11 at 00:11
  • 1
    @Peter yes though you could be a bit more concise :) `connection.Query(testsql).Select((p) => new { (int)p.Id });` also if you are only selecting Ids you could go with `connection.Query(testsql).Select((Id) => new { Id });` – Sam Saffron May 30 '11 at 00:24
  • so when I do this `const string testsql = @"SELECT form_id as Id FROM form ;"; connection.Query(testsql).Select((p) => new { (int)p.Id });` I get the compile time error 'Invalid anonymous type member declarator. Anonymous type members must be declared with a member assignment, simple name or member access.' so I try this `var x = connection.Query(testsql).Select((p) => new { Id = (int)p.Id });` and I get the runtime error 'System.Dynamic.DynamicObject' does not contain a definition for 'Id' – Peter May 30 '11 at 00:40
  • @Peter you are selecting `form_id` so you are going to have to change it to: `new { Id = (int)p.form_id })` – Sam Saffron May 30 '11 at 01:49
41

Here's another solution to use anonymous types with dapper:

public static class DapperExtensions
{
    public static IEnumerable<T> Query<T>(this IDbConnection connection, Func<T> typeBuilder, string sql)
    {
        return connection.Query<T>(sql);
    }
}

and use it like this:

var data = connection.Query(() => new 
{
    ContactId = default(int),
    Name = default(string),
}, "SELECT ContactId, Name FROM Contact");
Guillaume86
  • 14,341
  • 4
  • 53
  • 53
  • 1
    So what's the purpose of `typeBuilder` in the extension method if it is even not used in it? – Deilan Jul 06 '16 at 14:13
  • It's using it thru – Guillaume86 Jul 07 '16 at 18:12
  • Shouldn't it be ", T typebuilder, " ? – Bruno Santos Jan 27 '17 at 16:42
  • 1
    This is very elegant. I'm not sure it's a "better" answer than the currently accepted or if it's just a matter of personal taste, but I'll be using this one. – Marc L. May 18 '17 at 12:41
  • @BrunoSantos, no, with Func, T is interfered from the return type, with T, you need to use a concrete type, i.e. not-anonymous type, so T won't work here (this what would replace it). – Ofiris Jan 09 '18 at 20:39
  • If you're encountering exceptions like `A parameterless default constructor or one matching signature`, try switch to the ValueTuple based approach: https://stackoverflow.com/a/71400985/ – n0099 Dec 03 '22 at 15:12
8

Just a small improvement of Guillaume86's great solution:

public static IEnumerable<T> Query<T>(this IDbConnection connection, Func<T> typeBuilder,
    string sql, dynamic param = null, IDbTransaction transaction = null,
    bool buffered = true, int? commandTimeout = null, CommandType? commandType = null)
{
    return SqlMapper.Query<T>(connection, sql, param, transaction, buffered,
        commandTimeout, commandType);
}
Marc Gravell
  • 1,026,079
  • 266
  • 2,566
  • 2,900
pistipanko
  • 745
  • 5
  • 9
4

An alternative approach is to use Tuple types that were added as part of C# 7.0

var users = dbConnection
    .Query<(Guid UserId, string Username)>("SELECT UserId, Username From MyUsers")
    .AsList();
Thomas
  • 2,368
  • 20
  • 22
  • It's a struct based ValueTuple so should be distinguish with the original Tuple class that used in https://stackoverflow.com/questions/26904081/how-to-use-generic-and-nullablet-types-in-dapper-for-materialization – n0099 Dec 03 '22 at 15:07
0

Tried Guillaume86 approach, but I was getting 'object must implement IConvertible'.

So I went to another approach:

public static class DapperExtensions
{
    public static IEnumerable<T> QueryTyped<T>(
        this IDbConnection connection,
        string sql,
        Func<dynamic, T> typeBuilder)
    {
        var items = connection.Query<dynamic>(sql);
        var result = items.Select(typeBuilder);
        return result;
    }
}

Usage:

var rows = connection.QueryTyped(sql,
    typeBuilder: (d) => new
    {
        Code = d.Code,
        Name = d.Name,
        FullName = d.Name + " " + d.LastName,
    });

This allows you map even a C# function if you need to.

Cesar
  • 420
  • 6
  • 12