如果 属性 不存在,C# 将 Dapper 动态更改为抱怨

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

我们正在使用 Dapper 从我们的 SQL 数据库中获取数据,其中 returns 我们的数据是 'dynamic' 的集合。我了解动态类型的强大功能,以及 "duck typing" 的灵​​活性,基本上是 "if it quacks like a duck, then it is a duck - I don't need to declare that it's a duck".

但是,我不明白为什么如果我尝试从动态对象中获取 没有 的 属性,为什么没有抱怨?例如,如果我有一些 不是 鸭子的东西,我在上面调用了 "Quack",我会认为期望它抱怨是合理的。编辑:查看评论,这似乎与 Dapper 给我的动态有关,因为如果 属性 不存在,标准动态对象会给出运行时错误。

有什么方法可以让它抱怨吗?

我的代码是一系列行,从 'dynamic' 中获取属性并将它们分配给强类型对象中相应的 属性。 属性 名称并不总是相互关联(由于遗留数据库命名标准)。目前,如果字段名称在动态中拼写错误,那么它将静默失败。我想让它抱怨。我不想将每一行代码重写为 5 行 "does [hard-coded name] property exist on the dynamic"/"if not complain"/"get the value and put it in the right place".

编辑:这是具体的代码,以防万一...不正确的字段名称是 "result.DecisionLevel",我没有收到运行时错误,它只是将 null 分配给目标属性

        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();

解决方案:this 的已接受答案结合 Sergey 的回答使我得到了这个解决方案:

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));
    }
}

示例代码的唯一变化是包装对 Dapper 的 Query 方法的调用:

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

谢谢。

为了后代,我将 link 添加到我为 how to do the same thing for Query<T> 提供的解决方案中,并注意编辑 25/1/17 "Improvements to avoid threading issues on the static dictionary",这也适用于此处显示的解决方案。

整个预期行为可能因构建的动态对象而异。

如果动态成员不是动态对象的一部分,则不需要抛出异常。

例如:

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!

例如,Dapper 可能会尝试获取动态成员,如果没有找到成员,它不会抱怨,这可能是设计使然。

ExpandoObject 是实现 IDictionary<string, object> 的动态对象,因此您可以使用 ContainsKey:

有效地检查动态对象中是否存在成员
dynamic expando = new ExpandoObject();
expando.text = "hello world";

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

顺便说一句,如果第三方库(甚至是第一方库)实现了一个动态对象,当您访问一个不存在的成员时不会造成伤害,您将无法强制执行相反的操作。您需要接受它,因为这是一个设计决定。

由于 duck typing 严重依赖文档,如果您知道动态对象以这种方式工作,您就会知道 属性 没有设置它接收 属性 类型的默认值:

dynamic dyn = ...;

// x should be null once it's set and you'll know that
// dyn had no x member...
string x = dyn.x;

您可以将包装添加到您拥有的源对象并在其中实现所需的行为(抛出或不抛出异常,提供默认值或修复 属性 名称)。像这样:

public class WrapperDynamic : DynamicObject { 私有动态源; public WrapperDynamic(动态源) { _source = 来源; }

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

}

您应该根据源对象的种类实施 CheckTheProperyExist 和 GetProperty。

他们你应该给你加掩护选择

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

您可以在此包装器中为旧名称添加名称转换。