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.