问题
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.
回答1:
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.
回答2:
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;
来源:https://stackoverflow.com/questions/39467199/c-sharp-change-dapper-dynamic-to-complain-if-the-property-doesnt-exist