C# change Dapper dynamic to complain if the property doesn't exist

杀马特。学长 韩版系。学妹 提交于 2019-12-25 07:59:20

问题


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

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!