如果 属性 不存在,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();
您可以在此包装器中为旧名称添加名称转换。
我们正在使用 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();
您可以在此包装器中为旧名称添加名称转换。