0

We're using Dapper to fetch data from our SQL database, which returns our data as a collection of 'dynamic'. I understand the power of dynamic types, and the flexibility of "duck typing", basically "if it quacks like a duck, then it is a duck - I don't need to declare that it's a duck".

However, I don't understand why if I try to get a property from a dynamic object that it doesn't have, why doesn't it complain? For example, if I had something that wasn't a duck and I called "Quack" on it, I would have thought it reasonable to expect it to complain. EDIT: see comments, this seems to be something about the dynamic which Dapper is giving me because a standard dynamic object gives a runtime error if the property doesn't exist.

Is there a way I can make it complain?

The code that I have is a sequence of lines taking properties from the 'dynamic' and assigning them to the corresponding property in the strongly-typed object. The property names don't always tie-up (due to legacy database naming standards). At the moment, if a field name is misspelled on the dynamic, then it will just fail silently. I want it to complain. I don't want to have to rewrite each single line of code into 5 lines of "does [hard-coded name] property exist on the dynamic"/"if not complain"/"get the value and put it in the right place".

EDIT: Here is the specific code, in case that helps... the field name that is incorrect is "result.DecisionLevel", and I don't get a runtime error, it just assigns null into the target property

        var results = _connection.Query("usp_sel_Solution", new { IdCase = caseId }, commandType: CommandType.StoredProcedure);
        return results.Select(result => new Solution
        {
            IsDeleted = result.IsDeleted,
            FriendlyName = result.FriendlyName,
            DecisionLevel = (DecisionLevel?)result.DecisionLevel,
        }).ToList();

SOLUTION: The accepted answer to this, combined with Sergey's answer got me to this solution:

internal class SafeDynamic : DynamicObject
{
    private readonly IDictionary<string, object> _source;

    public SafeDynamic(dynamic source)
    {
        _source = source as IDictionary<string, object>;
    }

    public override bool TryGetMember(GetMemberBinder binder, out object result)
    {
        if (_source.TryGetValue(binder.Name, out result) == false)
        {
            throw new NotSupportedException(binder.Name);
        }

        return true;
    }

    // I'll refactor this later, probably to an extension method...
    public static IEnumerable<dynamic> Create(IEnumerable<dynamic> rows)
    {
        return rows.Select(x => new SafeDynamic(x));
    }
}

The only change to the sample code is to wrap the call to Dapper's Query method:

        var results = SafeDynamic.Create(_connection.Query("usp_sel_Solution", new { IdCase = caseId }, commandType: CommandType.StoredProcedure));

Thanks.

For posterity, I'm adding the link to the solution I've provided for how to do the same thing for Query<T> and note the Edit 25/1/17 "Improvements to avoid threading issues on the static dictionary", which also applies to the solution shown here.

Community
  • 1
  • 1
Richardissimo
  • 5,596
  • 2
  • 18
  • 36
  • 1
    With `dynamic` you bypass all this kind of compiler-type-checks. Thus you get an error at runtime, not at compile-time. – MakePeaceGreatAgain Sep 13 '16 at 10:00
  • My unit tests are indicating that you don't. It just assigns nothing into the target value. – Richardissimo Sep 13 '16 at 10:01
  • It is a good practice to add some good tag related to the post, so that it many people can reach to your question, and you may the answer very soon. – Aman Garg Sep 13 '16 at 10:04
  • 2
    I don´t unserstand your comment. You don´t what? To the second point: if you write `myDuck.woof` it obviously won´t assign anything to your duck, however at compile-time you would get an error *if* myDuck* was strongly typed as a `Duck`, whereas with `dynamic` you lose that check completely making it silently ignore the invalid call. – MakePeaceGreatAgain Sep 13 '16 at 10:05
  • My unit tests are indicating that you don't get an error at runtime. It just assigns nothing into the target value – Richardissimo Sep 13 '16 at 10:08
  • @AmanGarg - Feel free to suggest some, "dynamic" wasn't a tag. – Richardissimo Sep 13 '16 at 10:09
  • @Richardissimo Check my answer... ;) – Matías Fidemraizer Sep 13 '16 at 10:09
  • @Richardissimo thanks for improvement – Aman Garg Sep 13 '16 at 10:14
  • I've just tried mocking up a simple example which just used a dynamic, but this **does** give a runtime error, so @MatíasFidemraizer seems to be closest: Dapper must be giving me a customised dynamic object. But my question still remains: what can I do about it? – Richardissimo Sep 13 '16 at 10:27
  • Correction... I must have mis-typed it - there **is** a dynamic tag. I have now added it. @AmanGarg I don't think adding SQL as a tag would help - this isn't really anything to do with SQL. I have found a Dapper tag, so added that. – Richardissimo Sep 13 '16 at 10:32
  • @Richardissimo The best you can do is adapt your code to work with this dynamic object behavior. That's all. You should consider why you want an exception............. Are you validating what should the dynamic object contain with exceptions? – Matías Fidemraizer Sep 13 '16 at 11:29
  • Most people **do** want an exception. That's why the out of the box implementation of dynamic does throw a runtime exception, as pointed out by @HimBromBeere, and probably by the people who downvoted my question. Certainly in my case, I'm being given a dynamic object representing a row on my database table. So if there isn't a property with the name that I ask for, then it is definitely a problem. Anyway, solution is now posted as an edit to the original question. – Richardissimo Sep 13 '16 at 12:42

2 Answers2

1

The whole expected behavior may differ depending on the dynamic object being built.

It's not a requirement to throw an exception if a dynamic member isn't part of a dynamic object.

For example:

public class MyDynamic : DynamicObject
{
    public override bool TryGetMember(GetMemberBinder binder, out object result)
    {
           // I always set the result to null and I return true to tell
           // the runtime that I could get the value but I'm lying it!
           result = null;
           return true;
    }
}


dynamic myDynamic = new MyDynamic();
string text = myDynamic.text; // This won't throw a runtime exception!

Probably and for example Dapper tries to get the dynamic member and it doesn't complain if no member is found, and this might be by design.

ExpandoObject is a dynamic object which implements IDictionary<string, object> so you can effectively check if a member exists in the dynamic object using ContainsKey:

dynamic expando = new ExpandoObject();
expando.text = "hello world";

if((IDictionary<string, object>)expando).ContainsKey("text")) 
{
    // True
}

BTW, if a third-party library (or even first-party one) has implemented a dynamic object which doesn't hurt when you access an non existent member, you won't be able to force the opposite. You'll need to live with it, because it's a design decision.

Since duck typing heavily relies on documentation, if you know that a dynamic object works that way, you'll know what a property wasn't set if it receives property type's default value:

dynamic dyn = ...;

// x should be null once it's set and you'll know that
// dyn had no x member...
string x = dyn.x;
Matías Fidemraizer
  • 63,804
  • 18
  • 124
  • 206
  • This doesn't relate to Dapper specifically. This could be any dynamic type. I only mentioned Dapper to represent the fact that it's coming from a 3rd party library, so we have to accept what it gives us. – Richardissimo Sep 13 '16 at 10:11
  • @Richardissimo So it's still your answer, isn't it? I gave you a fact to make you think that, in .NET, not all dynamic objects must throw an exception when you try to access a not found member! – Matías Fidemraizer Sep 13 '16 at 10:13
  • I'm pointing out that I am not in control of the dynamic object that I'm being given, so what can I do about it? – Richardissimo Sep 13 '16 at 10:23
  • @Richardissimo Exactly what was told to you, you can do nothing, you're not in control of it. Aside from modifying the API / switching API you just have to live with it. Wether it throws or not is not something you can control on your side, it's in their implementation – Ronan Thibaudau Sep 13 '16 at 10:48
  • @RonanThibaudau: just to be clear: Matias edited his answer after I asked "what can I do about it?" – Richardissimo Sep 13 '16 at 10:53
  • @Richardissimo I didn't see that edit. BTW, unless you workaround your situatin with the other answerer answer, you'll need to live with it. – Matías Fidemraizer Sep 13 '16 at 11:23
0

You can add wrapping to source objects you have and implement the desired behavior in it (throwing or not trowing an exception, providing a default value or fixing the property names). Something like this:

public class WrapperDynamic : DynamicObject { private dynamic _source; public WrapperDynamic(dynamic source) { _source = source; }

public override bool TryGetMember(GetMemberBinder binder, out object result)
{                        
    if (_source.CheckTheProperyExist(binder))
    {
        result = _source.GetProperty(binder);
        return true;
    }
    return false;
}

}

You should implement CheckTheProperyExist and GetProperty depending on what kind of source objects.

They you should add covering to you selects

return results.Select(x=>new WrapperDynamic(x))
.Select(result => new Solution
        {
            IsDeleted = result.IsDeleted,
            FriendlyName = result.FriendlyName,
            DecisionLevel = (DecisionLevel?)result.DecisionLevel,
        }).ToList();

You can add name conversion for legacy names in this wrapper.

Sergey L
  • 1,402
  • 1
  • 9
  • 11
  • Excellent. Thankyou for ignoring the downvotes to the question, and other responders who said it couldn't be done. Your suggestion, combined with the accepted answer to [this](http://stackoverflow.com/questions/26659819/dapper-dynamic-return-types) gave me the answer. I will post the solution in my question so that it formats correctly. – Richardissimo Sep 13 '16 at 11:40