كما هو مذكور في التعليقات ، فإن المشكلة هي أنه لا يوجد عمود في القارئ للخاصية المحددة. تتمثل الفكرة في حلقت أسماء الأعمدة للقارئ أولاً ، والتحقق لمعرفة ما إذا كانت الممتلكات مطابقة موجودة. ولكن كيف يحصل المرء على قائمة أسماء الأعمدة مسبقًا؟
تتمثل إحدى الأفكار في استخدام أشجار التعبير نفسها لإنشاء قائمة أسماء الأعمدة من القارئ والتحقق منها مقابل خصائص الفصل. شيء من هذا القبيل
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
?كما قلت سابقًا ، ليس من الأفضل ربط نوع الأسماء وأسماء الأعمدة (التي تقوم بها من خلال التخزين المؤقت بشكل خاص
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);
}