Wie in Kommentaren erwähnt, besteht das Problem darin, dass im Leser keine Spalte für die angegebene Eigenschaft vorhanden ist. Die Idee besteht darin, zuerst die Spaltennamen des Lesers zu schleifen und zu überprüfen, ob die Übereinstimmungseigenschaft vorhanden ist. Aber wie bekommt man die Liste der Spaltennamen vorher?
Eine Idee besteht darin, Ausdrucksbäume selbst zu verwenden, um die Liste der Spaltennamen vom Leser zu erstellen und sie gegen Eigenschaften der Klasse zu überprüfen. Etwas wie das
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));
wäre das Äquivalent von
List<string> columnNames = new List<string>(); for (int i = 0; i < reader.FieldCount; i++) { columnNames.Add(reader.GetName(i)); }
Man kann mit dem endgültigen Ausdruck fortgesetzt werden, aber hier gibt es einen Haken, der weitere Anstrengungen entlang dieser Linie vergeblich macht. Der obige Ausdrucksbaum holt jedes Mal, wenn der endgültige Delegierte aufgerufen wird, die in Ihrem Fall für jede Objekterstellung, die gegen den Geist Ihrer Anforderung ist, die Spaltennamen ab.
Ein anderer Ansatz besteht darin, die Konverterklasse ein vordefiniertes Bewusstsein für die Spaltennamen für einen bestimmten Typ mittels Attribute zuzulassen (Attribute (Ein Beispiel für ein Beispiel) oder durch Aufrechterhaltung eines statischen Wörterbuchs wie (
Dictionary<Type, IEnumerable<string>>
). Obwohl es mehr Flexibilität gibt, ist die Flip -Seite, dass Ihre Abfrage nicht immer alle Spaltennamen einer Tabelle und alle enthalten mussreader[notInTheQueryButOnlyInTheTableColumn]
würde zu Ausnahme führen.Der beste Ansatz, den ich sehe, besteht darin, die Spaltennamen aus dem Leserobjekt zu holen, aber nur einmal. Ich würde das Ding wie:
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; }
Das wirft die Frage auf, warum nicht den Datenleser direkt an den Konstruktor weitergeben? Das wäre besser.
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; }
Nennen Sie es wie
List<T> list = new List<T>(); var converter = new Converter<T>(dr); while (dr.Read()) { var obj = converter.CreateItemFromRow(); list.Add(obj); }
Es gibt jedoch eine Reihe von Verbesserungen, die ich vorschlagen kann.
Das generische
new T()
Du rufst einCreateItemFromRow
ist langsamer, Es verwendet Reflexion hinter den Kulissen. Sie können diesen Teil auch an die Ausdrucksbäume delegieren das sollte schneller seinIm Augenblick
GetProperty
Anruf ist nicht unempfindlich, was bedeutet, dass Ihre Spaltennamen genau mit dem Eigenschaftsnamen übereinstimmen müssen. Ich würde es bei einem von denen unempfindlich machen lassenBindings.Flag
.Ich bin mir überhaupt nicht sicher, warum Sie a verwenden
ConcurrentDictionary
Als Caching -Mechanismus hier. Ein statisches Feld in einer generischen Klasse<T>
wird für jeden einzigartig seinT
. Das generische Feld selbst kann als Cache wirken. Auch warum ist dasValue
Teil vonConcurrentDictionary
vom Typobject
?Wie ich bereits sagte, ist es nicht das Beste, einen Typ und die Spaltennamen stark zu binden (was Sie tun, indem Sie einen bestimmten zwischengespeichern
Action
per delegieren Typ). Selbst für denselben Typ können Ihre Abfragen unterschiedliche Spaltenmenge auswählen. Es ist am besten, es dem Datenleser zu überlassen.Verwenden
Expression.Convert
Anstatt vonExpression.TypeAs
Für Werttypkonvertierung vonobject
.Beachten Sie auch das Reader.getordinal ist viel schneller, um Datenlesern durchzuführen.
Ich würde das Ganze wieder schreiben wie:
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);
}