【问题标题】:C# change Dapper dynamic to complain if the property doesn't exist如果属性不存在,C# 更改 Dapper 动态以抱怨
【发布时间】:2016-09-13 09:58:27
【问题描述】:

我们正在使用 Dapper 从我们的 SQL 数据库中获取数据,该数据库将我们的数据作为“动态”集合返回。我了解动态类型的力量,以及“鸭子打字”的灵活性,基本上“如果它像鸭子一样嘎嘎叫,那么它就是鸭子——我不需要声明它是鸭子”。

但是,我不明白为什么如果我尝试从动态对象中获取它没有的属性,它为什么不抱怨?例如,如果我有一个不是鸭子的东西,我在上面叫了“嘎嘎”,我会认为期待它抱怨是合理的。编辑:见 cmets,这似乎与 Dapper 给我的动态有关,因为如果属性不存在,标准动态对象会给出运行时错误。

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

我的代码是一系列行,从“动态”中获取属性并将它们分配给强类型对象中的相应属性。属性名称并不总是捆绑在一起(由于遗留数据库命名标准)。目前,如果动态上的字段名称拼写错误,那么它将默默地失败。我想让它抱怨。我不想将每一行代码重写为5行“动态上是否存在[硬编码名称]属性”/“如果不抱怨”/“获取值并将其放在正确的位置”。

编辑:这是具体的代码,以防万一......不正确的字段名称是“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));

谢谢。

为了后代,我将添加指向我为 how to do the same thing for Query<T> 提供的解决方案的链接,并注意编辑 25/1/17 “改进以避免静态字典上的线程问题”,这也适用于解决方案显示在这里。

【问题讨论】:

  • 使用dynamic,您可以绕过所有此类编译器类型检查。因此,您会在运行时而不是在编译时收到错误。
  • 我的单元测试表明你没有。它只是没有给目标值赋值。
  • 添加一些与帖子相关的好标签是一个很好的做法,这样很多人可以到达您的问题,并且您可能很快就会得到答案。
  • 我不理解您的评论。你不是什么?第二点:如果你写 myDuck.woof 它显然不会为你的鸭子分配任何东西,但是在编译时你会得到一个错误 if myDuck* is strong typed as a Duck ,而使用dynamic,您会完全丢失该检查,使其默默地忽略无效调用。
  • 我的单元测试表明您在运行时没有收到错误。它只是没有给目标值赋值

标签: c# dynamic dapper


【解决方案1】:

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

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

例如:

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&lt;string, object&gt; 的动态对象,因此您可以使用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;

【讨论】:

  • 这与 Dapper 无关。这可以是任何动态类型。我只提到 Dapper 是为了表示它来自 3rd 方库,所以我们必须接受它给我们的东西。
  • @Richardissimo 所以这仍然是你的答案,不是吗?我给了你一个事实,让你认为,在 .NET 中,当你尝试访问未找到的成员时,并非所有动态对象都必须抛出异常!
  • 我指出我无法控制给我的动态对象,那么我能做些什么呢?
  • @Richardissimo 正是被告知的内容,您无能为力,您无法控制它。除了修改 API / 切换 API,你只需要忍受它。它是否抛出不是您可以控制的事情,而是在他们的实施中
  • @RonanThibaudau:明确一点:在我问“我该怎么办?”之后,Matias 编辑了他的答案
【解决方案2】:

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

公共类 WrapperDynamic : DynamicObject { 私有动态_source; 公共 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();

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

【讨论】:

  • 优秀。感谢您忽略对该问题的反对意见,以及其他表示无法完成的响应者。您的建议,再加上对this 的接受回答,给了我答案。我将在我的问题中发布解决方案,以便其格式正确。
猜你喜欢
  • 2020-05-02
  • 2021-07-02
  • 1970-01-01
  • 1970-01-01
  • 2020-01-06
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-05-20
相关资源
最近更新 更多