Question

I'm investigating using Dapper and tried this in LinqPad (you'll need to supply your own connection string and setup a 'Ticket' table to actually run this):

using ( var conn = new SqlConnection( builder.ToString() ) )
{
    conn.Open();
    var loadSql = 
        @"Insert into Ticket(StatusKey, Status, ContactFirstName, ContactPhoneNumber, WorkflowKey )
            Values ( @statusKey, @status, @first, @phoneNumber, @key )";
    var values = new[] 
    {
        new { statusKey = "O", status = "Open", first = "Bob", phoneNumber = "6855551425", key = "std" },
        new { statusKey = "R", status = "Researching", first = "Sue", phoneNumber = "77785552136", key = "exp" },
        new { statusKey = "OD", status = "Overdue", first = "Ted", phoneNumber = "6795551496", key = "std" },
        new { statusKey = "C", status = "Closed", first = "Mark", phoneNumber = "9945552678", key = "std" }
    };
    "Rows Added".Dump();
    conn.Execute( loadSql, values ).Dump();
    "".Dump();

    // Using <dynamic> returns same results
    var tickets = conn.Query("Select Status, ContactFirstName, ContactPhoneNumber From Ticket").ToList();
    "Tickets Found".Dump();
    tickets.Count().Dump();
    "".Dump();

    "Attempt to get first ticket".Dump();
    var firstTicket = tickets[0];
    firstTicket.Dump();
    (firstTicket ?? "first ticket is null").Dump();
    "--- End First Ticket Attempt ---".Dump();
    "".Dump();

    "Access items returned by query".Dump();
    tickets.ForEach( ticketObj => 
    {
            // ticketObj isn't null, but it not there either??
            "  Try to dump enumerated ticket".Dump();
            if(ticketObj == null) "    is null".Dump();
            ticketObj.Dump();
            //ticketObj.GetType().Dump(); // Null ref exception?
            "  --- End Enumeration Dump ---".Dump();

            // Have to cast to dictionary
            var ticket = (IDictionary<string,object>)ticketObj;
            string.Format("    {0}: {1} at {2}", ticket["Status"], ticket["ContactFirstName"], ticket["ContactPhoneNumber"]).Dump();

            "".Dump();
    });
    "--- End Access Test ---".Dump();
    "".Dump();


    conn.Execute("Truncate table ticket");
}

I got the following results:

Rows Added   
4

Tickets Found  
4

Attempt to get first ticket
--- End First Ticket Attempt ---

Access items returned by query
  Try to dump enumerated ticket
  --- End Enumeration Dump ---
    Open: Bob at 6855551425

  Try to dump enumerated ticket
  --- End Enumeration Dump ---
    Researching: Sue at 77785552136

  Try to dump enumerated ticket
  --- End Enumeration Dump ---
    Overdue: Ted at 6795551496

  Try to dump enumerated ticket
  --- End Enumeration Dump ---
    Closed: Mark at 9945552678

--- End Access Test ---

As you can see, following are weird:

  • It lets me access the first item, but it won't dump and isn't null(??) and there are four items being returned.
  • When I enumerate, if I cast to an IDictionary<string, object>, it works. But the actual item enumerated (ticketObj) is again not null, but I can't dump it. And if I try to get its type, I get a null reference exception.

The results are the same if I use the generic Query<dynamic> version as well.

I thought the dynamic results where supposed to be Expandos, and I could do something like

ticketObj.Status.Dump();

I.e. the fields from the query are turned into properties on the each object returned by Query. What am I missing? How does this the dynamic feature of Dapper work? Or maybe LinqPad does'nt work with dynamic objects?

Was it helpful?

Solution

There are two issues here. The first is that LINQPad dumps dynamic objects that implement IDynamicMetaObjectProvider plainly as IDictionary<string,object> without first pivoting for a nicer view. This has been fixed for the next build.

The second issue is that calling Dump() directly on a DapperRow fails silently, instead of throwing the RuntimeBinderException one would expect ('DapperRow does not contain a definition for "Dump"'). This, I think, is a fault in the implementation of DapperRow.

There are a number of workarounds. The first, as you've discovered, is to cast first to object. Another workaround is to call the Dump extension method directly:

LINQPad.Extensions.Dump (ticketObj);

or:

LINQPad.Extensions.Dump (ticketObj, "First ticket");

Yet another workaround is to call Console.WriteLine, which LINQPad redirects to Dump:

Console.WriteLine (ticketObj);

OTHER TIPS

I've determined that this is a LinqPad issue. Since it uses extension methods, it can't really handle dynamic types. However, if you cast the dynamic to an object, it works. So if you replace

firstTicket.Dump();

with

((object)firstTicket).Dump();

it works...sort of. It shows an IDictionary with the columns from the query. LinqPad has no way of knowing what the dynamically added columns are. See SO this answer for more information.

This same technique also worked in the enumeration. If I change

ticketObj.Dump();

to

((object)ticketObj).Dump();

it works. I can also access dynamic properties, I just have to cast them first.

((string)ticketObj.Status).Dump();

The .GetType() doesn't appear to work with dynamic types...I don't think they have one. Note sure if this is correct, but this didn't work in a normal VS console app either.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top