コメントで述べたように、問題は、指定されたプロパティの読者に列が存在しないことです。アイデアは、最初に読者の列名でループし、一致するプロパティが存在するかどうかを確認することです。しかし、どのようにして列名のリストを事前に取得するのでしょうか?
1つのアイデアは、表現ツリー自体を使用して、読者から列名のリストを作成し、クラスのプロパティに対してチェックすることです。このようなもの
var paramExp = Expression.Parameter(typeof(IDataRecord), "o7thDR"); var loopIncrementVariableExp = Expression.Parameter(typeof(int), "i"); var columnNamesExp = Expression.Parameter(typeof(List<string>), "columnNames"); var columnCountExp = Expression.Property(paramExp, "FieldCount"); var getColumnNameExp = Expression.Call(paramExp, "GetName", Type.EmptyTypes, Expression.PostIncrementAssign(loopIncrementVariableExp)); var addToListExp = Expression.Call(columnNamesExp, "Add", Type.EmptyTypes, getColumnNameExp); var labelExp = Expression.Label(columnNamesExp.Type); var getColumnNamesExp = Expression.Block( new[] { loopIncrementVariableExp, columnNamesExp }, Expression.Assign(columnNamesExp, Expression.New(columnNamesExp.Type)), Expression.Loop( Expression.IfThenElse( Expression.LessThan(loopIncrementVariableExp, columnCountExp), addToListExp, Expression.Break(labelExp, columnNamesExp)), labelExp));
に相当するでしょう
List<string> columnNames = new List<string>(); for (int i = 0; i < reader.FieldCount; i++) { columnNames.Add(reader.GetName(i)); }
最終的な表現を続けることができますが、ここではこのラインに沿ってさらに努力していないキャッチがあります。上記の式ツリーは、最終的なデリゲートが呼び出されるたびに列名を取得します。
別のアプローチは、コンバータークラスに、属性を使用して、特定のタイプの列名を事前に定義した認識を持たせることです(属性)例を参照してください)または(
Dictionary<Type, IEnumerable<string>>
)。柔軟性が高まりますが、フリップサイドは、クエリにテーブルのすべての列名を常に含める必要はないということです。reader[notInTheQueryButOnlyInTheTableColumn]
結果として例外になります。私が見るように最良のアプローチは、読者オブジェクトから列名を取得することですが、一度だけです。私は次のようなものを書き直します:
private static List<string> columnNames; private static Action<IDataReader, T> GetMapFunc() { var exps = new List<Expression>(); var paramExp = Expression.Parameter(typeof(IDataRecord), "o7thDR"); var targetExp = Expression.Parameter(typeof(T), "o7thTarget"); var getPropInfo = typeof(IDataRecord).GetProperty("Item", new[] { typeof(string) }); foreach (var columnName in columnNames) { var property = typeof(T).GetProperty(columnName); if (property == null) continue; // use 'columnName' instead of 'property.Name' to speed up reader lookups //in case of certain readers. var columnNameExp = Expression.Constant(columnName); var getPropExp = Expression.MakeIndex( paramExp, getPropInfo, new[] { columnNameExp }); var castExp = Expression.TypeAs(getPropExp, property.PropertyType); var bindExp = Expression.Assign( Expression.Property(targetExp, property), castExp); exps.Add(bindExp); } return Expression.Lambda<Action<IDataReader, T>>( Expression.Block(exps), paramExp, targetExp).Compile(); } internal T CreateItemFromRow(IDataReader dataReader) { if (columnNames == null) { columnNames = Enumerable.Range(0, dataReader.FieldCount) .Select(x => dataReader.GetName(x)) .ToList(); _convertAction = (Action<IDataReader, T>)_convertActionMap.GetOrAdd( typeof(T), (t) => GetMapFunc()); } T result = new T(); _convertAction(dataReader, result); return result; }
これで質問が頼まれます。なぜデータリーダーをコンストラクターに直接渡してみませんか?そのほうがいい。
private IDataReader dataReader; private Action<IDataReader, T> GetMapFunc() { var exps = new List<Expression>(); var paramExp = Expression.Parameter(typeof(IDataRecord), "o7thDR"); var targetExp = Expression.Parameter(typeof(T), "o7thTarget"); var getPropInfo = typeof(IDataRecord).GetProperty("Item", new[] { typeof(string) }); var columnNames = Enumerable.Range(0, dataReader.FieldCount) .Select(x => dataReader.GetName(x)); foreach (var columnName in columnNames) { var property = typeof(T).GetProperty(columnName); if (property == null) continue; // use 'columnName' instead of 'property.Name' to speed up reader lookups //in case of certain readers. var columnNameExp = Expression.Constant(columnName); var getPropExp = Expression.MakeIndex( paramExp, getPropInfo, new[] { columnNameExp }); var castExp = Expression.TypeAs(getPropExp, property.PropertyType); var bindExp = Expression.Assign( Expression.Property(targetExp, property), castExp); exps.Add(bindExp); } return Expression.Lambda<Action<IDataReader, T>>( Expression.Block(exps), paramExp, targetExp).Compile(); } internal Converter(IDataReader dataReader) { this.dataReader = dataReader; _convertAction = (Action<IDataReader, T>)_convertActionMap.GetOrAdd( typeof(T), (t) => GetMapFunc()); } internal T CreateItemFromRow() { T result = new T(); _convertAction(dataReader, result); return result; }
そのように呼んでください
List<T> list = new List<T>(); var converter = new Converter<T>(dr); while (dr.Read()) { var obj = converter.CreateItemFromRow(); list.Add(obj); }
しかし、私が提案できる多くの改善があります。
ジェネリック
new T()
あなたは電話をかけていますCreateItemFromRow
遅いです、 舞台裏で反射を使用します. 。あなたはその部分を木を表現するために委任することもできます これはより速いはずですたった今
GetProperty
通話は症例ではありません。つまり、列名はプロパティ名と正確に一致する必要があります。私はそれらのいずれかを使用してそれを鈍感にしますBindings.Flag
.なぜあなたが使用しているのかまったくわかりません
ConcurrentDictionary
ここでキャッシュメカニズムとして。 一般的なクラスの静的フィールド<T>
すべてにユニークになりますT
. 。ジェネリックフィールド自体はキャッシュとして機能します。また、なぜですかValue
一部のConcurrentDictionary
タイプのobject
?先ほど言ったように、タイプと列名を強く結び付けるのが最善ではありません(特定の1つをキャッシュすることでやっています
Action
あたりの委任 タイプ)。同じタイプであっても、あなたのクエリは異なる列のセットを選択することが異なる場合があります。データリーダーに任せて決定するのが最善です。使用する
Expression.Convert
それ以外のExpression.TypeAs
からの値タイプの変換用object
.また、それに注意してください reader.getordinalは、データリーダールックアップを実行するためのはるかに速い方法です。
私は次のようなすべてを書き直します:
readonly Func<IDataReader, T> _converter;
readonly IDataReader dataReader;
private Func<IDataReader, T> GetMapFunc()
{
var exps = new List<Expression>();
var paramExp = Expression.Parameter(typeof(IDataRecord), "o7thDR");
var targetExp = Expression.Variable(typeof(T));
exps.Add(Expression.Assign(targetExp, Expression.New(targetExp.Type)));
//does int based lookup
var indexerInfo = typeof(IDataRecord).GetProperty("Item", new[] { typeof(int) });
var columnNames = Enumerable.Range(0, dataReader.FieldCount)
.Select(i => new { i, name = dataReader.GetName(i) });
foreach (var column in columnNames)
{
var property = targetExp.Type.GetProperty(
column.name,
BindingFlags.Public | BindingFlags.Instance | BindingFlags.IgnoreCase);
if (property == null)
continue;
var columnNameExp = Expression.Constant(column.i);
var propertyExp = Expression.MakeIndex(
paramExp, indexerInfo, new[] { columnNameExp });
var convertExp = Expression.Convert(propertyExp, property.PropertyType);
var bindExp = Expression.Assign(
Expression.Property(targetExp, property), convertExp);
exps.Add(bindExp);
}
exps.Add(targetExp);
return Expression.Lambda<Func<IDataReader, T>>(
Expression.Block(new[] { targetExp }, exps), paramExp).Compile();
}
internal Converter(IDataReader dataReader)
{
this.dataReader = dataReader;
_converter = GetMapFunc();
}
internal T CreateItemFromRow()
{
return _converter(dataReader);
}