Better way to write this method using lambda expression and generics
I need a method for retrieving data from an IDataRecord without using the annoying "magic strings" to express field names. After some researching I came up with that extension method:
public static T2 ReadValue<T1, T2>(this IDataRecord record, Expression<Func<T1, T2>> expression)
{
MemberExpression body = (MemberExpression)expression.Body;
string fiel开发者_如何学GodName = body.Member.Name;
int ordinal = record.GetOrdinal(fieldName);
return (T2)(record.IsDBNull(ordinal) ? default(T2) : record.GetValue(ordinal));
}
And then I use this method like that:
product.Name = record.ReadValue<Product, string>(p => p.Name);
Is there another way to simplify this method? I like the behavior, but not the style! :)
Thank you veeeeery much!
The issue is that you can infer the string
result type from the property, but you still have to specify it in the type arguments because you also have to specify Product
.
A nice way to approach this would be a typed data record:
IDataRecord<Product> productRecord = ...;
string name = productRecord.ReadValue(p => p.Name);
This seems feasible because, in an ORM context like the one you are implying, you should know the data type represented by a record.
The tough part is the ...
in the above code. It requires some infrastructure, but you only have to write it once and you can use it everywhere. The first step is to derive the typed data record:
public interface IDataRecord<T> : IDataRecord
{
TValue GetValue<TValue>(Expression<Func<T, TValue>> getter);
}
Next, implement the typed data record using the Decorator pattern (boring but straightforward):
public class DataRecord<T> : IDataRecord<T>
{
private readonly IDataRecord _untypedRecord;
public DataRecord(IDataRecord untypedRecord)
{
_untypedRecord = untypedRecord;
}
public TValue GetValue<TValue>(Expression<Func<T, TValue>> getter)
{
...the original code...
}
...pass through all other members to the untyped record...
}
Finally, add a transformation from the untyped to the typed record:
public static class TypedDataRecords
{
public static IDataRecord<T> TypedAs<T>(this IDataRecord untypedRecord)
{
return new DataRecord<T>(untypedRecord);
}
}
Now, the example looks like this:
IDataRecord<Product> productRecord = record.TypedAs<Product>();
string name = productRecord.ReadValue(p => p.Name);
decimal price = productRecord.ReadValue(p => p.Price);
...
Maybe this would work (I didn't test it):
public static void ReadValue<T1, T2>(this IDataRecord record, T1 product, Expression<Func<T1, T2>> expression)
{
MemberExpression body = (MemberExpression)expression.Body;
string fieldName = body.Member.Name;
int ordinal = record.GetOrdinal(fieldName);
var val = (T2)(record.IsDBNull(ordinal) ? default(T2) : record.GetValue(ordinal));
((PropertyInfo)body.Member).SetValue(product, val, null);
}
Then you could call it like so:
record.ReadValue(product, p => p.Name);
Name is only specified once, compiler infers the generic types. Obviously T1 needs to be a reference type.
精彩评论