我有一个来自上一个 SO 问题的表达式树函数。它基本上允许将数据行转换为特定的类。
除非您正在处理可以更大或更小的数据类型(例如 Int32/Int64),否则此代码可以正常工作。
当值适合 Int32
时(例如, 3000)。
我应该吗?
- 尝试在代码中解决这个问题? (如果是这样,有什么指示吗?)
-
保持代码不变。
private Func; getExpressionDelegate()
{
// 保留 row[string] 属性
var indexerProperty = typeof(SqlDataReader).GetProperty("Item", new[] { typeof(string) });
// 动态方法中的语句列表
var 语句 = new List<表达式>();
// 存储属性设置的实例
ParameterExpression实例参数 = Expression.Variable(typeof(T));
ParameterExpression sqlDataReaderParameter = Expression.Parameter(typeof(SqlDataReader));
// 创建新 T 并将其分配给变量: var instance = new T();
BinaryExpression createInstance = Expression.Assign(instanceParameter, Expression.New(typeof(T)));
语句.Add(createInstance);
foreach(typeof(T).GetProperties() 中的 var 属性)
{
// 实例.MyProperty
MemberExpression getProperty = Expression.Property(instanceParameter, property);
// row[property] -- 注意:这假设列名称与 T 上的 PropertyInfo 名称相同
IndexExpression readValue = Expression.MakeIndex(sqlDataReaderParameter, indexerProperty, new[] { Expression.Constant(property.Name) });
// 实例.MyProperty = 行[属性]
BinaryExpression allocateProperty = Expression.Assign(getProperty, Expression.Convert(readValue, property.PropertyType));
语句。添加(分配属性);
}
var returnStatement = 实例参数;
语句.Add(returnStatement);
var body = Expression.Block(instanceParameter.Type, new[] { instanceParameter }, statements.ToArray());
var lambda = Expression.Lambda>(body, sqlDataReaderParameter);
// 缓存我!
返回 lambda.Compile();
}
更新:
我现在已经放弃并认为这是不值得的。从下面的评论中,我得到了这样的信息:
if (readValue.Type != property.PropertyType)
{
BinaryExpression assignProperty = Expression.Assign(getProperty, Expression.Convert(Expression.Call(property.PropertyType, "Parse", null, new Expression[] { Expression.ConvertChecked(readValue, typeof(string)) }), property.PropertyType));
statements.Add(assignProperty);
}
else
{
// instance.MyProperty = row[property]
BinaryExpression assignProperty = Expression.Assign(getProperty, Expression.Convert(readValue, property.PropertyType));
statements.Add(assignProperty);
}
我不认为我离得太远,请随意完成它并发布答案,如果你弄清楚了:)
I have an expression tree function from a previous SO question. It basically allows the conversion of a data row into a specific class.
This code works fine, unless you're dealing with data types that can be bigger or smaller (eg. Int32/Int64).
The code throws an invalid cast exception when going from an Int64
to an Int32
when the value would fit in an Int32
(eg. numbers in the 3000).
Should I?
- Attempt to fix this in the code? (If so, any pointers?)
-
Leave the code as it is.
private Func<SqlDataReader, T> getExpressionDelegate<T>()
{
// hang on to row[string] property
var indexerProperty = typeof(SqlDataReader).GetProperty("Item", new[] { typeof(string) });
// list of statements in our dynamic method
var statements = new List<Expression>();
// store instance for setting of properties
ParameterExpression instanceParameter = Expression.Variable(typeof(T));
ParameterExpression sqlDataReaderParameter = Expression.Parameter(typeof(SqlDataReader));
// create and assign new T to variable: var instance = new T();
BinaryExpression createInstance = Expression.Assign(instanceParameter, Expression.New(typeof(T)));
statements.Add(createInstance);
foreach (var property in typeof(T).GetProperties())
{
// instance.MyProperty
MemberExpression getProperty = Expression.Property(instanceParameter, property);
// row[property] -- NOTE: this assumes column names are the same as PropertyInfo names on T
IndexExpression readValue = Expression.MakeIndex(sqlDataReaderParameter, indexerProperty, new[] { Expression.Constant(property.Name) });
// instance.MyProperty = row[property]
BinaryExpression assignProperty = Expression.Assign(getProperty, Expression.Convert(readValue, property.PropertyType));
statements.Add(assignProperty);
}
var returnStatement = instanceParameter;
statements.Add(returnStatement);
var body = Expression.Block(instanceParameter.Type, new[] { instanceParameter }, statements.ToArray());
var lambda = Expression.Lambda<Func<SqlDataReader, T>>(body, sqlDataReaderParameter);
// cache me!
return lambda.Compile();
}
Update:
I have now given up and decided it is not worth it. From the comments below, I got as far as:
if (readValue.Type != property.PropertyType)
{
BinaryExpression assignProperty = Expression.Assign(getProperty, Expression.Convert(Expression.Call(property.PropertyType, "Parse", null, new Expression[] { Expression.ConvertChecked(readValue, typeof(string)) }), property.PropertyType));
statements.Add(assignProperty);
}
else
{
// instance.MyProperty = row[property]
BinaryExpression assignProperty = Expression.Assign(getProperty, Expression.Convert(readValue, property.PropertyType));
statements.Add(assignProperty);
}
I don't think I was too far off, feel free to finish it and post the answer if you figure it out :)
发布评论
评论(2)
您可以尝试在使用
Expression 分配 ie 之前通过“转换选中”来修复它.ConvertChecked
值而不是Expression.Convert
。现在无法尝试,但这应该可以解决您描述的情况...
编辑 - 根据评论,这可能是一个拳击问题:
在这种情况下,您可以尝试使用
Expression.TypeAs
或Expression.Unbox
进行转换或使用Expression.Call
用于调用方法来执行以下操作转换...使用Call
的示例可以在 http://msdn.microsoft.com/en-us/library/bb349020.aspxYou could try to fix it by "convert checked" before assigning i.e. using
Expression.ConvertChecked
on the value instead ofExpression.Convert
.Couldn't try it right now but this should take care of the case you describe...
EDIT - as per comment this could be a boxing issue:
In this case you could try using
Expression.TypeAs
orExpression.Unbox
for the conversion or useExpression.Call
for calling a method to do the conversion... an example for usingCall
can be found at http://msdn.microsoft.com/en-us/library/bb349020.aspx如果您想支持 .NET 和 SQL 中 100% 的原语,那么您尝试构建的内容实际上要复杂得多。
如果您不关心一些边缘情况(可以为 null 的类型、枚举、字节数组等),有两个技巧可以让您做到 90%:
不要在 IDataRecord 上使用索引器,它返回一个对象和装箱/拆箱会降低性能。相反,请注意 IDataRecord 具有 Get[typeName] 方法。这些对于所有 .NET 基元类型都存在(注意:它是 GetFloat,而不是 GetSingle,这是一个巨大的烦恼)。
您可以使用 IDataRecord.GetFieldType 来确定需要为给定列调用哪个 Get 方法。一旦完成,您可以使用 Expression.Convert 将数据库列类型强制为目标属性的类型(如果它们不同)。对于我上面列出的一些边缘情况,对于那些需要自定义逻辑的情况,这将失败。
What you're trying to build is actually much more complicated if you want to support 100% of the primitives in .NET and SQL.
If you don't care about some of the edge cases (nullable types, enums, byte arrays, etc), two tips to get you 90% there:
Don't use the indexer on IDataRecord, it returns an object and the boxing/unboxing will kill performance. Instead, notice that IDataRecord has Get[typeName] methods on it. These exist for all .NET primitive types (note: it's GetFloat, not GetSingle, huge annoyance).
You can use IDataRecord.GetFieldType to figure out which Get method you need to call for a given column. Once you have that, you can use Expression.Convert to coerce the DB column type to the target property's type (if they're different). This will fail for some of the edge cases I listed above, for those you need custom logic.