의견에 언급 된 바와 같이, 문제는 지정된 속성에 대한 독자에 열이 없다는 것입니다. 아이디어는 먼저 독자의 열 이름으로 루프를하고 일치하는 속성이 있는지 확인하는 것입니다. 그러나 어떻게 열 이름 목록을 미리 얻습니까?
한 가지 아이디어는 표현 트리 자체를 사용하여 독자로부터 열 이름 목록을 작성하고 클래스의 속성에 대해 확인하는 것입니다. 이 같은
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
호출은 CASE 무의미하지 않으므로 열 이름이 속성 이름과 정확히 일치해야 함을 의미합니다. 나는 그 중 하나를 사용하여 둔감하지 않게 만들 것입니다.Bindings.Flag
.왜 당신이 왜
ConcurrentDictionary
여기서 캐싱 메커니즘으로. 일반 클래스의 정적 필드<T>
모든 사람에게 독특 할 것입니다T
. 일반 필드 자체는 캐시 역할을 할 수 있습니다. 또한 왜Value
부분의ConcurrentDictionary
유형의object
?앞서 말했듯이, 유형과 열 이름을 강력하게 묶는 것이 가장 좋지 않습니다 (특정 특정 캐싱으로하고 있습니다.
Action
대의원 유형). 동일한 유형의 경우에도 쿼리가 다른 열 세트를 선택하는 것과 다를 수 있습니다. 결정하기 위해 데이터 리더에게 맡기는 것이 가장 좋습니다.사용
Expression.Convert
대신에Expression.TypeAs
가치 유형 변환의 경우object
.
나는 다음과 같은 모든 것을 다시 작성할 것입니다.
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);
}