سؤال

أواجه مشكلة بسيطة (أشبه بالإزعاج) مع فئات الوصول إلى بيانات الربط الخاص بي. المشكلة هي أن التعيين يفشل عند عدم وجود عمود في القارئ للخاصية المقابلة في الفصل.

شفرة

ها هي فئة الخريطة:

// Map our datareader object to a strongly typed list
private static IList<T> Map<T>(DbDataReader dr) where T : new()
{
    try
    {
        // initialize our returnable list
        List<T> list = new List<T>();
        // fire up the lamda mapping
        var converter = new Converter<T>();
        while (dr.Read())
        {
            // read in each row, and properly map it to our T object
            var obj = converter.CreateItemFromRow(dr);
            // add it to our list
            list.Add(obj);
        }
        // reutrn it
        return list;
    }
    catch (Exception ex)
    {    
        return default(List<T>);
    }
}

فئة المحول:

/// <summary>
/// Converter class to convert returned Sql Records to strongly typed classes
/// </summary>
/// <typeparam name="T">Type of the object we'll convert too</typeparam>
internal class Converter<T> where T : new()
{
    // Concurrent Dictionay objects
    private static ConcurrentDictionary<Type, object> _convertActionMap = new ConcurrentDictionary<Type, object>();
    // Delegate action declaration
    private Action<IDataReader, T> _convertAction;

    // Build our mapping based on the properties in the class/type we've passed in to the class
    private static Action<IDataReader, T> GetMapFunc()
    {
        var exps = new List<Expression>();
        var paramExp = Expression.Parameter(typeof(IDataReader), "o7thDR");
        var targetExp = Expression.Parameter(typeof(T), "o7thTarget");
        var getPropInfo = typeof(IDataRecord).GetProperty("Item", new[] { typeof(string) });
        var _props = typeof(T).GetProperties();
        foreach (var property in _props)
        {

            var getPropExp = Expression.MakeIndex(paramExp, getPropInfo, new[] { Expression.Constant(property.Name, typeof(string)) });
            var castExp = Expression.TypeAs(getPropExp, property.PropertyType);
            var bindExp = Expression.Assign(Expression.Property(targetExp, property), castExp);
            exps.Add(bindExp);

        }
        // return our compiled mapping, this will ensure it is cached to use through our record looping
        return Expression.Lambda<Action<IDataReader, T>>(Expression.Block(exps), new[] { paramExp, targetExp }).Compile();
    }

    internal Converter()
    {
        // Fire off our mapping functionality
        _convertAction = (Action<IDataReader, T>)_convertActionMap.GetOrAdd(typeof(T), (t) => GetMapFunc());
    }

    internal T CreateItemFromRow(IDataReader dataReader)
    {
        T result = new T();
        _convertAction(dataReader, result);
        return result;
    }
}

استثناء

System.IndexOutOfRangeException {"Mileage"}

تتبع المكدس

at System.Data.ProviderBase.FieldNameLookup.GetOrdinal(String fieldName)
at System.Data.SqlClient.SqlDataReader.GetOrdinal(String name)
at System.Data.SqlClient.SqlDataReader.get_Item(String name)
at lambda_method(Closure , IDataReader , Typing )
at o7th.Class.Library.Data.Converter`1.CreateItemFromRow(IDataReader dataReader) in d:\Backup Folder\Development\o7th Web Design\o7th.Class.Library.C-Sharp\o7th.Class.Library\Data Access Object\Converter.cs:line 50
at o7th.Class.Library.Data.Wrapper.Map[T](DbDataReader dr) in d:\Backup Folder\Development\o7th Web Design\o7th.Class.Library.C-Sharp\o7th.Class.Library\Data Access Object\Wrapper.cs:line 33

سؤال

كيف يمكنني إصلاحه ، بحيث لن يفشل عندما يكون لدي خاصية إضافية قد لا يكون للقارئ كعمود والعكس صحيح؟ بالطبع سيكون الإسعافات الأولية السريعة هي ببساطة إضافة NULL As Mileage ومع ذلك ، فإن هذا الاستعلام على سبيل المثال ، هذا ليس حلاً للمشكلة :)


هنا Map<T> باستخدام الانعكاس:

// Map our datareader object to a strongly typed list
private static IList<T> Map<T>(DbDataReader dr) where T : new()
{
    try
    {
        // initialize our returnable list
        List<T> list = new List<T>();
        T item = new T();
        PropertyInfo[] properties = (item.GetType()).GetProperties();
        while (dr.Read()) {
            int fc = dr.FieldCount;
            for (int j = 0; j < fc; ++j) {
                var pn = properties[j].Name;
                var gn = dr.GetName(j);
                if (gn == pn) {
                    properties[j].SetValue(item, dr[j], null);
                }
            }
            list.Add(item);
        }
        // return it
        return list;
    }
    catch (Exception ex)
    {
        // Catch an exception if any, an write it out to our logging mechanism, in addition to adding it our returnable message property
        _Msg += "Wrapper.Map Exception: " + ex.Message;
        ErrorReporting.WriteEm.WriteItem(ex, "o7th.Class.Library.Data.Wrapper.Map", _Msg);
        // make sure this method returns a default List
        return default(List<T>);
    }
}

ملحوظة:هذه الطريقة أبطأ بنسبة 63 ٪ من استخدام أشجار التعبير ...

هل كانت مفيدة؟

المحلول

كما هو مذكور في التعليقات ، فإن المشكلة هي أنه لا يوجد عمود في القارئ للخاصية المحددة. تتمثل الفكرة في حلقت أسماء الأعمدة للقارئ أولاً ، والتحقق لمعرفة ما إذا كانت الممتلكات مطابقة موجودة. ولكن كيف يحصل المرء على قائمة أسماء الأعمدة مسبقًا؟

  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));
    }
    

    يمكن للمرء أن يستمر في التعبير النهائي ، ولكن هناك صيد هنا يبذل أي جهد إضافي على هذا الخط غير مجدي. ستجلب شجرة التعبير أعلاه أسماء الأعمدة في كل مرة يتم فيها تسمى المندوب النهائي والتي في حالتك لكل إنشاء كائن ، وهو ضد روح متطلباتك.

  2. هناك طريقة أخرى تتمثل في السماح لفئة المحول بالحصول على وعي محدد مسبقًا بأسماء الأعمدة لنوع معين ، عن طريق السمات (انظر للحصول على مثال) أو عن طريق الحفاظ على قاموس ثابت مثل (Dictionary<Type, IEnumerable<string>>). على الرغم من أنه يعطي المزيد من المرونة ، إلا أن الجانب الآخر هو أن استعلامك لا يحتاج دائمًا إلى تضمين جميع أسماء الأعمدة لجدول وأي reader[notInTheQueryButOnlyInTheTableColumn] من شأنه أن يؤدي إلى استثناء.

  3. أفضل نهج كما أراه هو جلب أسماء الأعمدة من كائن القارئ ، ولكن مرة واحدة فقط. أود إعادة كتابة الشيء مثل:

    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);
    }
    

هناك عدد من التحسينات التي يمكنني اقتراحها.

  1. عام new T() أنت تتصل CreateItemFromRow أبطأ ، يستخدم الانعكاس وراء الكواليس. يمكنك تفويض هذا الجزء إلى أشجار التعبير أيضًا التي يجب أن تكون أسرع

  2. فى الحال GetProperty المكالمة ليست حساسة للحالة ، مما يعني أنه سيتعين على أسماء الأعمدة مطابقة اسم الخاصية بالضبط. أود أن أجعلها غير حساسة لحالةها باستخدام واحد من هؤلاء Bindings.Flag.

  3. لست متأكدًا على الإطلاق سبب استخدامك ConcurrentDictionary كآلية التخزين المؤقت هنا. مجال ثابت في فئة عامة <T> سيكون فريدًا لكل شيء T. الحقل العام نفسه يمكن أن يكون بمثابة ذاكرة التخزين المؤقت. أيضا لماذا Value جزء من ConcurrentDictionary من النوع object?

  4. كما قلت سابقًا ، ليس من الأفضل ربط نوع الأسماء وأسماء الأعمدة (التي تقوم بها من خلال التخزين المؤقت بشكل خاص Action مندوب لكل يكتب). حتى بالنسبة لنفس النوع ، يمكن أن تكون استفساراتك مختلفة اختيار مجموعة مختلفة من الأعمدة. من الأفضل ترك الأمر لقارئ البيانات لتقريره.

  5. يستخدم Expression.Convert بدلاً من Expression.TypeAs لتحويل نوع القيمة من object.

  6. لاحظ أيضا ذلك 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);
}
مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top