0

Per my boss' recommendations, we are using Dapper to access our database. As this is just a quick, throwaway project, he would like to forego creating POCO objects for the results of these calls to the database, so we're just having Dapper return dynamic objects.

So I have several Web API controllers, all deriving from the same BaseController. In the base class, we have a method like this:

protected dynamic ExecuteSingle(string sql, object parameters = null, bool useStoredProcedure = true) {
    using (var db = ObjectFactory.GetInstance<IDbManager>())
    {
        var cmd = useStoredProcedure ? db.SetSpCommand(sql) : db.SetCommand(sql);

        if (parameters != null)
        {
            cmd = cmd.SetParameters(parameters);
        }

        return cmd.ExecuteObject<dynamic>();
    }
}

We are able to use that successfully when taking that result and passing it as the parameter to a Ok() return value. All of the properties of the object get successfully parsed into the JSON object.

I'm now working on another section of code where instead of immediately spitting it back out, I need to use the data coming back to hit another set of functionality.

var sql = " SELECT Id FROM Table WHERE ReviewId = :ReviewId ";
dynamic dbSurvey = ExecuteSingle(sql, new {ReviewId = reviewId}, false);

var survey = sg.GetSurvey(new GetSurveyRequest(dbSurvey.Id));

It then fails on that last line where calling dbSurvey.Id with an exception saying

'object' does not contain a definition for 'Id'

Checking the object, there is a property on there named "Id".

I have checked several questions already, and even though they are all dealing with Anonymous objects (which I could understand dynamic being under that heading), I am staying within the same assembly, so those points about Anonymous objects being declared as "internal" wouldn't apply in this case.

I also tried changing the return type of ExecuteSingle to an ExpandoObject, but am getting the same results.

EDIT Here is a screenshot of how it is being called, versus the object and properties that it contains. Perhaps the format of the properties could help determine why it's not being found.

run time evaluation of the dynamic object

I also used the Watch menu to try various ways to access the property, and none of the following worked:

dbSurvey["Id"]
(dbSurvey as IDictionary<string, long>)["Id"]
((IDictionary<string, int>)dbSurvey)["Id"]

Here is the result of dbSurvey.GetType() from the immediate window while running:

{<>f__AnonymousType2`1[System.Int64]}
base: {Name = "<>f__AnonymousType2`1" FullName = "<>f__AnonymousType2`1[[System.Int64, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089]]"}
Assembly: {***.Web.Test, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null}
AssemblyQualifiedName: "<>f__AnonymousType2`1[[System.Int64, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089]], ***.Web.Test, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null"
BaseType: {Name = "Object" FullName = "System.Object"}
ContainsGenericParameters: false
DeclaringMethod: 'dbSurvey.GetType().DeclaringMethod' threw an exception of type 'System.InvalidOperationException'
DeclaringType: null
FullName: "<>f__AnonymousType2`1[[System.Int64, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089]]"
GenericParameterAttributes: 'dbSurvey.GetType().GenericParameterAttributes' threw an exception of type 'System.InvalidOperationException'
GenericParameterPosition: 'dbSurvey.GetType().GenericParameterPosition' threw an exception of type 'System.InvalidOperationException'
GUID: {382c0269-d631-3c89-a105-38a1be8a3db7}
IsConstructedGenericType: true
IsEnum: false
IsGenericParameter: false
IsGenericType: true
IsGenericTypeDefinition: false
IsSecurityCritical: true
IsSecuritySafeCritical: false
IsSecurityTransparent: false
MemberType: TypeInfo
MetadataToken: 33554480
Module: {***.Web.Test.dll}
Name: "<>f__AnonymousType2`1"
Namespace: null
ReflectedType: null
StructLayoutAttribute: {System.Runtime.InteropServices.StructLayoutAttribute}
TypeHandle: {System.RuntimeTypeHandle}
UnderlyingSystemType: {Name = "<>f__AnonymousType2`1" FullName = "<>f__AnonymousType2`1[[System.Int64, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089]]"}
Community
  • 1
  • 1
krillgar
  • 12,596
  • 6
  • 50
  • 86
  • There are a number of issues with this approach. 1, all of the logic will now take place in your controllers instead of a properly segregated DAL. 2, any transformation of the objects before they return will also occur in the controllers, not to mention that it really isn't manageable now, 3, you are suffering from a small performance hit every call thanks to dynamic. 4, there is no such thing as a throwaway project. Some poor soul will end up supporting it eventually. So, in the end, what's the harm with POCOs again?? – David L Apr 20 '15 at 18:31
  • The Project Manager is well aware of all of those. He refuted every single one of those questions before any of us writing the code were able to ask them at the initial design meeting. – krillgar Apr 20 '15 at 18:34
  • Does `dbSurvey.GetType().GetProperty("Id")` returns something or null? – xanatos Apr 20 '15 at 18:53
  • In the Watch menu, that has a value of `{Int64 Id}`, @xanatos – krillgar Apr 20 '15 at 18:56
  • @krillgar Mmmh... try `long id = dbSurvey.Id;` and then pass the id variable. – xanatos Apr 20 '15 at 18:57
  • @xanatos Trying that in the Immediate Window throws the same exception. – krillgar Apr 20 '15 at 18:59
  • 1
    @krillgar give use the `dbSurvey.GetType()`. I think that dynamic object is fishy... And you told us you are using Dapper, but `IDbManager`, `SetSpCommand` and `ExecuteObject` aren't "base" Dapper (I'm looking at Dapper.dll now) – xanatos Apr 20 '15 at 19:05
  • Also to your comment, @xanatos this is using `SetCommand`, not `SetSpCommand`. Just clarifying as they can do different things. It's the ternary in there that I didn't remove from my copy/paste and trying to not miss anything. – krillgar Apr 20 '15 at 19:11
  • 1
    For as much time as you are spending trying to hack this together, you could have already created and used the required objects. Also, try just using dbSurvey itself, instead of dbSurvey.Id. Or, dbSurvey[0]. – Der Kommissar Apr 20 '15 at 19:12
  • @krillgar Show your project manager how much time you've already wasted with this question. – Yuval Itzchakov Apr 20 '15 at 19:14
  • @EBrown Just using `dbSurvey` doesn't work as it can't convert an object to a long. – krillgar Apr 20 '15 at 19:14
  • @YuvalItzchakov I've moved onto working on other things while working with xanos. I've spent far more time ignoring unconstructive comments. – krillgar Apr 20 '15 at 19:15
  • @krillgar So convert it. Convert.ToInt64(dbSurvey) or Convert.ToInt64(dbSurvey[0]). I suspect that somehow it did not become a right-proper dictionary/array. It's probably some linked-list or something. It should, ideally, just be the object itself. Edit: better yet, what does expanding the dropdown by dbSurvey get you? – Der Kommissar Apr 20 '15 at 19:18
  • "Unable to cast anonymous object to IConvertible" – krillgar Apr 20 '15 at 19:24
  • @krillgar *I am staying within the same assembly, so those points about Anonymous objects being declared as "internal" wouldn't apply in this case.*, but I see the anonymous type is declared in `***.Web.Test.dll`. Are you doing the `dbSurvey.Id` that assembly? – xanatos Apr 20 '15 at 19:33
  • @xanatos Yeah, I see that too... I'm calling the Api method from my Web.Test project, however, the `dynamic` object is created in the BaseApiController that the controller my Web.Test project is calling. I can give it a try to call it through the actual API tomorrow. – krillgar Apr 20 '15 at 23:59
  • @xanatos I got it figured out, see my answer below. Like you noticed, it was from another assembly, though it wasn't how I was expecting it to. It took 36 hours away for me to see it clearly. – krillgar Apr 22 '15 at 13:02

1 Answers1

0

OK, so I resolved this issue. I just figured out what the problem was. Thanks to xanatos' help in the comments, we noticed that the Module property of the type was coming from the Test harness. That didn't make any sense to me at the time, because the dynamic object was being created in the same assembly.

However, what I didn't think about until I came back to the issue this morning was that the source of the object being "created" in the base API Controller was an anonymous object that I was creating in the Test harness. So that Module was correct.

If I went and created a separate POCO object for the Mock object in the Test DLL, then it was no longer creating a dynamic off of an anonymous object created in another assembly.

krillgar
  • 12,596
  • 6
  • 50
  • 86