Получить IDataReader из типизированного списка

У меня есть List<MyObject> с миллионом элементов. (На самом деле это коллекция SubSonic, но она не загружается из базы данных).

В настоящее время я использую SqlBulkCopy следующим образом:

private string FastInsertCollection(string tableName, DataTable tableData)
{
    string sqlConn = ConfigurationManager.ConnectionStrings[SubSonicConfig.DefaultDataProvider.ConnectionStringName].ConnectionString;
    using (SqlBulkCopy s = new SqlBulkCopy(sqlConn, SqlBulkCopyOptions.TableLock))
    {
        s.DestinationTableName = tableName;
        s.BatchSize = 5000;
        s.WriteToServer(tableData);
        s.BulkCopyTimeout = SprocTimeout;
        s.Close();
    }
    return sqlConn;
}

Я использую SubSonic MyObjectCollection.ToDataTable () для создания DataTable из моей коллекции. Однако это дублирует объекты в памяти и неэффективно. Я хотел бы использовать метод SqlBulkCopy.WriteToServer, который использует IDataReader вместо DataTable, чтобы я не дублировал свою коллекцию в памяти.

Как проще всего получить IDataReader из моего списка? Полагаю, я мог бы реализовать пользовательский считыватель данных (например, здесь http://blogs.microsoft.co.il/blogs/aviwortzel/archive/2008/05/06/implementing-sqlbulkcopy-in-linq-to-sql.aspx), но должно быть что-то более простое, что я могу сделать без написания кучи общего кода.

Изменить: не похоже, что можно легко сгенерировать IDataReader из коллекции объектов. Принимаю текущий ответ, хотя я надеялся на что-то встроенное в структуру.


person Jason Kealey    schedule 13.02.2010    source источник
comment
возбудил мое любопытство, поэтому я добавил читателя данных IList ‹T›. Посмотрим, поможет ли это тебе.   -  person Sky Sanders    schedule 13.02.2010
comment
IEnumerable ‹T› достаточно просто. слегка проверено.   -  person Sky Sanders    schedule 15.02.2010
comment
Ваша очередь предоставить пример использования EnumerableDataReader ‹T›, как вы описываете.   -  person Sky Sanders    schedule 15.02.2010
comment
Джейсон, можете ли вы подтвердить, что код в обновленном ответе по-прежнему работает в ваших тестах массовой вставки?   -  person Sky Sanders    schedule 16.02.2010
comment
опубликовано в блоге, если вы хотите видеть свое имя в свете .. skysanders.net/subtext/archive/2010/02/19/   -  person Sky Sanders    schedule 20.02.2010
comment
Подтверждаю, что последняя версия отлично работает!   -  person Jason Kealey    schedule 20.02.2010
comment
@ Джейсон - Привет, я наконец нашел необходимость в enumerabledatareader, и я согласен с вами. Отлично работает! ;-). meta.stackexchange.com/questions/44330/. Я немного улучшил его по ходу дела. Я думаю, что единственная обнаруженная мною ошибка была в методе .GetSchemaTable. Когда у объекта были поля, допускающие значение NULL, он не работал. Вы можете получить последнюю версию из источника в этом посте. ваше здоровье.   -  person Sky Sanders    schedule 28.03.2010


Ответы (3)


Получите последнюю версию из кода на этот пост

Ничего подобного оттоку кода на виду: вот довольно полная реализация. Вы можете создать экземпляр IDataReader через IList IEnumerable, IEnumerable (следовательно, IQueryable). Нет никаких веских причин показывать параметр универсального типа для читателя, и, опуская его, я могу разрешить IEnumerable ‹'a> (анонимные типы). Смотрите тесты.

Исходный код, за вычетом xmldocs, достаточно короткий, чтобы включить сюда пару тестов. Остальная часть исходного кода с xmldocs и тестами находится здесь в разделе Важные.Данные.


using System;
using System.Linq;
using NUnit.Framework;

namespace Salient.Data.Tests
{
    [TestFixture]
    public class EnumerableDataReaderEFFixture
    {
        [Test]
        public void TestEnumerableDataReaderWithIQueryableOfAnonymousType()
        {
            var ctx = new NorthwindEntities();

            var q =
                ctx.Orders.Where(o => o.Customers.CustomerID == "VINET").Select(
                    o =>
                    new
                        {
                            o.OrderID,
                            o.OrderDate,
                            o.Customers.CustomerID,
                            Total =
                        o.Order_Details.Sum(
                        od => od.Quantity*((float) od.UnitPrice - ((float) od.UnitPrice*od.Discount)))
                        });

            var r = new EnumerableDataReader(q);
            while (r.Read())
            {
                var values = new object[4];
                r.GetValues(values);
                Console.WriteLine("{0} {1} {2} {3}", values);
            }
        }
    }
}

using System;
using System.Collections;
using System.Collections.Generic;
using System.Data;
using NUnit.Framework;

namespace Salient.Data.Tests
{
    public class DataObj
    {
        public string Name { get; set; }
        public int Age { get; set; }
    }

    [TestFixture]
    public class EnumerableDataReaderFixture
    {

        private static IEnumerable<DataObj> DataSource
        {
            get
            {
                return new List<DataObj>
                           {
                               new DataObj {Name = "1", Age = 16},
                               new DataObj {Name = "2", Age = 26},
                               new DataObj {Name = "3", Age = 36},
                               new DataObj {Name = "4", Age = 46}
                           };
            }
        }

        [Test]
        public void TestIEnumerableCtor()
        {
            var r = new EnumerableDataReader(DataSource, typeof(DataObj));
            while (r.Read())
            {
                var values = new object[2];
                int count = r.GetValues(values);
                Assert.AreEqual(2, count);

                values = new object[1];
                count = r.GetValues(values);
                Assert.AreEqual(1, count);

                values = new object[3];
                count = r.GetValues(values);
                Assert.AreEqual(2, count);

                Assert.IsInstanceOf(typeof(string), r.GetValue(0));
                Assert.IsInstanceOf(typeof(int), r.GetValue(1));

                Console.WriteLine("Name: {0}, Age: {1}", r.GetValue(0), r.GetValue(1));
            }
        }

        [Test]
        public void TestIEnumerableOfAnonymousType()
        {
            // create generic list
            Func<Type, IList> toGenericList =
                type => (IList)Activator.CreateInstance(typeof(List<>).MakeGenericType(new[] { type }));

            // create generic list of anonymous type
            IList listOfAnonymousType = toGenericList(new { Name = "1", Age = 16 }.GetType());

            listOfAnonymousType.Add(new { Name = "1", Age = 16 });
            listOfAnonymousType.Add(new { Name = "2", Age = 26 });
            listOfAnonymousType.Add(new { Name = "3", Age = 36 });
            listOfAnonymousType.Add(new { Name = "4", Age = 46 });

            var r = new EnumerableDataReader(listOfAnonymousType);
            while (r.Read())
            {
                var values = new object[2];
                int count = r.GetValues(values);
                Assert.AreEqual(2, count);

                values = new object[1];
                count = r.GetValues(values);
                Assert.AreEqual(1, count);

                values = new object[3];
                count = r.GetValues(values);
                Assert.AreEqual(2, count);

                Assert.IsInstanceOf(typeof(string), r.GetValue(0));
                Assert.IsInstanceOf(typeof(int), r.GetValue(1));

                Console.WriteLine("Name: {0}, Age: {1}", r.GetValue(0), r.GetValue(1));
            }
        }

        [Test]
        public void TestIEnumerableOfTCtor()
        {
            var r = new EnumerableDataReader(DataSource);
            while (r.Read())
            {
                var values = new object[2];
                int count = r.GetValues(values);
                Assert.AreEqual(2, count);

                values = new object[1];
                count = r.GetValues(values);
                Assert.AreEqual(1, count);

                values = new object[3];
                count = r.GetValues(values);
                Assert.AreEqual(2, count);

                Assert.IsInstanceOf(typeof(string), r.GetValue(0));
                Assert.IsInstanceOf(typeof(int), r.GetValue(1));

                Console.WriteLine("Name: {0}, Age: {1}", r.GetValue(0), r.GetValue(1));
            }
        } 
        // remaining tests omitted for brevity
    }
}

/*!
 * Project: Salient.Data
 * File   : EnumerableDataReader.cs
 * http://spikes.codeplex.com
 *
 * Copyright 2010, Sky Sanders
 * Dual licensed under the MIT or GPL Version 2 licenses.
 * See LICENSE.TXT
 * Date: Sat Mar 28 2010 
 */


using System;
using System.Collections;
using System.Collections.Generic;

namespace Salient.Data
{
    /// <summary>
    /// Creates an IDataReader over an instance of IEnumerable&lt;> or IEnumerable.
    /// Anonymous type arguments are acceptable.
    /// </summary>
    public class EnumerableDataReader : ObjectDataReader
    {
        private readonly IEnumerator _enumerator;
        private readonly Type _type;
        private object _current;

        /// <summary>
        /// Create an IDataReader over an instance of IEnumerable&lt;>.
        /// 
        /// Note: anonymous type arguments are acceptable.
        /// 
        /// Use other constructor for IEnumerable.
        /// </summary>
        /// <param name="collection">IEnumerable&lt;>. For IEnumerable use other constructor and specify type.</param>
        public EnumerableDataReader(IEnumerable collection)
        {
            // THANKS DANIEL!
            foreach (Type intface in collection.GetType().GetInterfaces())
            {
                if (intface.IsGenericType && intface.GetGenericTypeDefinition() == typeof (IEnumerable<>))
                {
                    _type = intface.GetGenericArguments()[0]; 
                }
            }

            if (_type ==null && collection.GetType().IsGenericType)
            {
                _type = collection.GetType().GetGenericArguments()[0];

            }


            if (_type == null )
            {
                throw new ArgumentException(
                    "collection must be IEnumerable<>. Use other constructor for IEnumerable and specify type");
            }

            SetFields(_type);

            _enumerator = collection.GetEnumerator();

        }

        /// <summary>
        /// Create an IDataReader over an instance of IEnumerable.
        /// Use other constructor for IEnumerable&lt;>
        /// </summary>
        /// <param name="collection"></param>
        /// <param name="elementType"></param>
        public EnumerableDataReader(IEnumerable collection, Type elementType)
            : base(elementType)
        {
            _type = elementType;
            _enumerator = collection.GetEnumerator();
        }

        /// <summary>
        /// Helper method to create generic lists from anonymous type
        /// </summary>
        /// <param name="type"></param>
        /// <returns></returns>
        public static IList ToGenericList(Type type)
        {
            return (IList) Activator.CreateInstance(typeof (List<>).MakeGenericType(new[] {type}));
        }

        /// <summary>
        /// Return the value of the specified field.
        /// </summary>
        /// <returns>
        /// The <see cref="T:System.Object"/> which will contain the field value upon return.
        /// </returns>
        /// <param name="i">The index of the field to find. 
        /// </param><exception cref="T:System.IndexOutOfRangeException">The index passed was outside the range of 0 through <see cref="P:System.Data.IDataRecord.FieldCount"/>. 
        /// </exception><filterpriority>2</filterpriority>
        public override object GetValue(int i)
        {
            if (i < 0 || i >= Fields.Count)
            {
                throw new IndexOutOfRangeException();
            }

            return Fields[i].Getter(_current);
        }

        /// <summary>
        /// Advances the <see cref="T:System.Data.IDataReader"/> to the next record.
        /// </summary>
        /// <returns>
        /// true if there are more rows; otherwise, false.
        /// </returns>
        /// <filterpriority>2</filterpriority>
        public override bool Read()
        {
            bool returnValue = _enumerator.MoveNext();
            _current = returnValue ? _enumerator.Current : _type.IsValueType ? Activator.CreateInstance(_type) : null;
            return returnValue;
        }
    }
}

// <copyright project="Salient.Data" file="ObjectDataReader.cs" company="Sky Sanders">
// This source is a Public Domain Dedication.
// Please see http://spikes.codeplex.com/ for details.   
// Attribution is appreciated
// </copyright> 
// <version>1.0</version>


using System;
using System.Collections.Generic;
using System.Data;
using Salient.Reflection;

namespace Salient.Data
{
    public abstract class ObjectDataReader : IDataReader
    {
        protected bool Closed;
        protected IList<DynamicProperties.Property> Fields;

        protected ObjectDataReader()
        {
        }

        protected ObjectDataReader(Type elementType)
        {
            SetFields(elementType);
            Closed = false;
        }

        #region IDataReader Members

        public abstract object GetValue(int i);
        public abstract bool Read();

        #endregion

        #region Implementation of IDataRecord

        public int FieldCount
        {
            get { return Fields.Count; }
        }

        public virtual int GetOrdinal(string name)
        {
            for (int i = 0; i < Fields.Count; i++)
            {
                if (Fields[i].Info.Name == name)
                {
                    return i;
                }
            }

            throw new IndexOutOfRangeException("name");
        }

        object IDataRecord.this[int i]
        {
            get { return GetValue(i); }
        }

        public virtual bool GetBoolean(int i)
        {
            return (Boolean) GetValue(i);
        }

       public virtual byte GetByte(int i)
        {
            return (Byte) GetValue(i);
        }

        public virtual char GetChar(int i)
        {
            return (Char) GetValue(i);
        }

        public virtual DateTime GetDateTime(int i)
        {
            return (DateTime) GetValue(i);
        }

        public virtual decimal GetDecimal(int i)
        {
            return (Decimal) GetValue(i);
        }

        public virtual double GetDouble(int i)
        {
            return (Double) GetValue(i);
        }

        public virtual Type GetFieldType(int i)
        {
            return Fields[i].Info.PropertyType;
        }

        public virtual float GetFloat(int i)
        {
            return (float) GetValue(i);
        }

        public virtual Guid GetGuid(int i)
        {
            return (Guid) GetValue(i);
        }

        public virtual short GetInt16(int i)
        {
            return (Int16) GetValue(i);
        }

        public virtual int GetInt32(int i)
        {
            return (Int32) GetValue(i);
        }

        public virtual long GetInt64(int i)
        {
            return (Int64) GetValue(i);
        }

        public virtual string GetString(int i)
        {
            return (string) GetValue(i);
        }

        public virtual bool IsDBNull(int i)
        {
            return GetValue(i) == null;
        }

        object IDataRecord.this[string name]
        {
            get { return GetValue(GetOrdinal(name)); }
        }


        public virtual string GetDataTypeName(int i)
        {
            return GetFieldType(i).Name;
        }


        public virtual string GetName(int i)
        {
            if (i < 0 || i >= Fields.Count)
            {
                throw new IndexOutOfRangeException("name");
            }
            return Fields[i].Info.Name;
        }

        public virtual int GetValues(object[] values)
        {
            int i = 0;
            for (; i < Fields.Count; i++)
            {
                if (values.Length <= i)
                {
                    return i;
                }
                values[i] = GetValue(i);
            }
            return i;
        }

        public virtual IDataReader GetData(int i)
        {
            // need to think about this one
            throw new NotImplementedException();
        }

        public virtual long GetBytes(int i, long fieldOffset, byte[] buffer, int bufferoffset, int length)
        {
            // need to keep track of the bytes got for each record - more work than i want to do right now
            // http://msdn.microsoft.com/en-us/library/system.data.idatarecord.getbytes.aspx
            throw new NotImplementedException();
        }

        public virtual long GetChars(int i, long fieldoffset, char[] buffer, int bufferoffset, int length)
        {
            // need to keep track of the bytes got for each record - more work than i want to do right now
            // http://msdn.microsoft.com/en-us/library/system.data.idatarecord.getchars.aspx
            throw new NotImplementedException();
        }

        #endregion

        #region Implementation of IDataReader

        public virtual void Close()
        {
            Closed = true;
        }


        public virtual DataTable GetSchemaTable()
        {
            var dt = new DataTable();
            foreach (DynamicProperties.Property field in Fields)
            {
                dt.Columns.Add(new DataColumn(field.Info.Name, field.Info.PropertyType));
            }
            return dt;
        }

        public virtual bool NextResult()
        {
            throw new NotImplementedException();
        }


        public virtual int Depth
        {
            get { throw new NotImplementedException(); }
        }

        public virtual bool IsClosed
        {
            get { return Closed; }
        }

        public virtual int RecordsAffected
        {
            get
            {
                // assuming select only?
                return -1;
            }
        }

        #endregion

        #region Implementation of IDisposable

        public virtual void Dispose()
        {
            Fields = null;
        }

        #endregion

        protected void SetFields(Type elementType)
        {
            Fields = DynamicProperties.CreatePropertyMethods(elementType);
        }
    }
}

// <copyright project="Salient.Reflection" file="DynamicProperties.cs" company="Sky Sanders">
// This source is a Public Domain Dedication.
// Please see http://spikes.codeplex.com/ for details.   
// Attribution is appreciated
// </copyright> 
// <version>1.0</version>
using System;
using System.Collections.Generic;
using System.Reflection;
using System.Reflection.Emit;

namespace Salient.Reflection
{
    /// <summary>
    /// Gets IL setters and getters for a property.
    /// 
    /// started with http://jachman.wordpress.com/2006/08/22/2000-faster-using-dynamic-method-calls/
    /// </summary>
    public static class DynamicProperties
    {
        #region Delegates

        public delegate object GenericGetter(object target);

        public delegate void GenericSetter(object target, object value);

        #endregion

        public static IList<Property> CreatePropertyMethods(Type T)
        {
            var returnValue = new List<Property>();

            foreach (PropertyInfo prop in T.GetProperties())
            {
                returnValue.Add(new Property(prop));
            }
            return returnValue;
        }


        public static IList<Property> CreatePropertyMethods<T>()
        {
            var returnValue = new List<Property>();

            foreach (PropertyInfo prop in typeof (T).GetProperties())
            {
                returnValue.Add(new Property(prop));
            }
            return returnValue;
        }


        /// <summary>
        /// Creates a dynamic setter for the property
        /// </summary>
        /// <param name="propertyInfo"></param>
        /// <returns></returns>
        public static GenericSetter CreateSetMethod(PropertyInfo propertyInfo)
        {
            /*
            * If there's no setter return null
            */
            MethodInfo setMethod = propertyInfo.GetSetMethod();
            if (setMethod == null)
                return null;

            /*
            * Create the dynamic method
            */
            var arguments = new Type[2];
            arguments[0] = arguments[1] = typeof (object);

            var setter = new DynamicMethod(
                String.Concat("_Set", propertyInfo.Name, "_"),
                typeof (void), arguments, propertyInfo.DeclaringType);
            ILGenerator generator = setter.GetILGenerator();
            generator.Emit(OpCodes.Ldarg_0);
            generator.Emit(OpCodes.Castclass, propertyInfo.DeclaringType);
            generator.Emit(OpCodes.Ldarg_1);

            if (propertyInfo.PropertyType.IsClass)
                generator.Emit(OpCodes.Castclass, propertyInfo.PropertyType);
            else
                generator.Emit(OpCodes.Unbox_Any, propertyInfo.PropertyType);

            generator.EmitCall(OpCodes.Callvirt, setMethod, null);
            generator.Emit(OpCodes.Ret);

            /*
            * Create the delegate and return it
            */
            return (GenericSetter) setter.CreateDelegate(typeof (GenericSetter));
        }


        /// <summary>
        /// Creates a dynamic getter for the property
        /// </summary>
        /// <param name="propertyInfo"></param>
        /// <returns></returns>
        public static GenericGetter CreateGetMethod(PropertyInfo propertyInfo)
        {
            /*
            * If there's no getter return null
            */
            MethodInfo getMethod = propertyInfo.GetGetMethod();
            if (getMethod == null)
                return null;

            /*
            * Create the dynamic method
            */
            var arguments = new Type[1];
            arguments[0] = typeof (object);

            var getter = new DynamicMethod(
                String.Concat("_Get", propertyInfo.Name, "_"),
                typeof (object), arguments, propertyInfo.DeclaringType);
            ILGenerator generator = getter.GetILGenerator();
            generator.DeclareLocal(typeof (object));
            generator.Emit(OpCodes.Ldarg_0);
            generator.Emit(OpCodes.Castclass, propertyInfo.DeclaringType);
            generator.EmitCall(OpCodes.Callvirt, getMethod, null);

            if (!propertyInfo.PropertyType.IsClass)
                generator.Emit(OpCodes.Box, propertyInfo.PropertyType);

            generator.Emit(OpCodes.Ret);

            /*
            * Create the delegate and return it
            */
            return (GenericGetter) getter.CreateDelegate(typeof (GenericGetter));
        }

        #region Nested type: Property

        public class Property
        {
            public GenericGetter Getter;
            public PropertyInfo Info;
            public GenericSetter Setter;

            public Property(PropertyInfo info)
            {
                Info = info;
                Setter = CreateSetMethod(info);
                Getter = CreateGetMethod(info);
            }
        }

        #endregion

        ///// <summary>
        ///// An expression based Getter getter found in comments. untested.
        ///// Q: i don't see a reciprocal setter expression?
        ///// </summary>
        ///// <typeparam name="T"></typeparam>
        ///// <param name="propName"></param>
        ///// <returns></returns>
        //public static Func<T> CreateGetPropValue<T>(string propName)
        //{
        //    var param = Expression.Parameter(typeof(object), "container");
        //    var func = Expression.Lambda(
        //    Expression.Convert(Expression.PropertyOrField(Expression.Convert(param, typeof(T)), propName), typeof(object)), param);
        //    return (Func<T>)func.Compile();
        //}
    }
}

person Community    schedule 13.02.2010
comment
Похоже, этот код делает именно то, что мне нужно, но я удивлен, что нет решения, которое не требует создания собственного устройства чтения данных. - person Jason Kealey; 14.02.2010
comment
Хорошо, дайте мне знать, сработает ли это для вас. Кстати, дозвуковые сборники; они действительно реализуют IList ‹T›? - person Sky Sanders; 14.02.2010
comment
Да; вы можете сделать .GetList () в дозвуковой коллекции, чтобы добраться до внутреннего IList ‹T› - person Jason Kealey; 14.02.2010
comment
Вердикт: изначально у меня было необработанное исключение: System.InvalidOperationException: данное ColumnMapping не соответствует ни одному столбцу в источнике или месте назначения. используя мои объекты SubSonic напрямую. Однако импорт работал нормально с использованием обычных объектов, у которых не было дополнительных свойств SubSonic. - person Jason Kealey; 15.02.2010
comment
Джейсон, свяжите полную реализацию в начале ответа и посмотрите, работает ли это. Я реализовал все, кроме самых нерелевантных методов IDataReader. Это может сработать. Кроме того, если бы вы могли провести тест, включая дозвуковой слой и некоторый DDL, мне было бы интересно посмотреть, смогу ли я выполнить эту работу. Хорошая идея... - person Sky Sanders; 15.02.2010
comment
На самом деле мне лучше обойти SubSonic в этом контексте. Я избегаю дорогостоящего создания объектов и настройки свойств и повышаю производительность. SubSonic работает быстро, но в данном конкретном случае его снятие дает мне необходимый дополнительный импульс. Я использую ваш полный код в своих тестах, и он работает хорошо! - person Jason Kealey; 15.02.2010
comment
Рад, что смог помочь. Я знаю, что вы могли бы сделать это легко, но это выглядело неплохо, так как раньше я сталкивался с той же проблемой с массовой вставкой из памяти. просто думайте обо мне как об сапожных эльфах. - person Sky Sanders; 15.02.2010
comment
Предостережение в отношении этого кода: он работает с SqlBulkCopy только в том случае, если поля базы данных находятся в точном порядке, правильного типа, ни одно не отсутствует, ни одно не является лишним. Хорошее усовершенствование позволило бы IDataReader работать с коллекцией с заданной целевой схемой (и теперь я понимаю, почему она не встроена в структуру). - person Jason Kealey; 15.02.2010
comment
Кроме того, было бы интересно, если бы он работал в IEnumerable ‹T› вместо / в дополнение к IList ‹T›, потому что ему можно было бы передать оператор linq, который будет использоваться при чтении, вместо загрузки полного списка в память. . - person Jason Kealey; 15.02.2010
comment
Джейсон, поведение массового копирования, о котором вы говорите, не связано с читателем, ЛЮБЫЕ IDataRecord или Reader, которые вы вставляете в массовое копирование, должны точно совпадать. Так же, как если бы вы вводили файл. Первый ключ к разгадке заключается в том, что единственное «получение», которое использует массовое копирование, - это GetValue (i). Он вообще не запрашивает схему. - person Sky Sanders; 15.02.2010
comment
Я обновил свою кодовую базу, и новая версия IEnumerable отлично работает. Превосходная работа! Вы должны опубликовать это где-нибудь в блоге! - person Jason Kealey; 16.02.2010
comment
Конструктор, который принимает IEnumerable ‹T›, должен быть изменен, чтобы получить общий аргумент интерфейса, а не конкретный тип. Это позволит ему работать с перечислениями, возвращаемыми из методов с yield return в них: foreach (var intface in collection.GetType().GetInterfaces()) { if (intface.IsGenericType && intface.GetGenericTypeDefinition() == typeof(IEnumerable<>)) { _type = intface.GetGenericArguments()[0]; /* ... */ } } - person Daniel Plaisted; 30.03.2010
comment
@ Даниэль, мне кажется, я тебя неправильно понял. Позвольте мне поразмышлять над этим пару часов. - person Sky Sanders; 30.03.2010
comment
@Daniel + для вас. Почему-то у меня сложилось неверное впечатление от вашего комментария. Действительно, расширение ctor путем проверки интерфейсов улучшает удобство использования класса. Большое спасибо. - person Sky Sanders; 30.03.2010

Вы можете использовать FastMember от Марка Гравелла:

IDataReader reader = ObjectReader.Create(myEnumerable); //all columns
IDataReader reader = ObjectReader.Create(myEnumerable, "Id", "Name", "Description");

Помимо абстрагирования логики создания, он также использует IL для получения значений свойств / полей, что быстрее, чем отражение.

person Arithmomaniac    schedule 27.04.2015
comment
Это современный спутник SqlBulkCopy. Зачем сворачивать свои собственные, если в NuGet уже есть средство чтения объектных данных. Просто найдите FastMember на NuGet. Спасибо! - person wezzix; 03.03.2016
comment
Это одна из лучших и простых в использовании библиотек. Я добавил это в конструктор ObjectReader, чтобы сохранить исходный порядок классов: members = type.GetProperties().Select(p => p.Name).Union(typeMembers.Select(m => m.Name)).Distinct().ToArray(); - person nh43de; 07.04.2016
comment
если у моего объекта коллекции есть вложенный объект, и мне нужно сослаться на это свойство, как я могу этого добиться? - person Muds; 26.04.2018
comment
@Muds использует Enumerable.Select для создания анонимного объекта и использует для этого FastMember. - person Arithmomaniac; 27.04.2018
comment
Реализация IsDBNull в ObjectReader работает только тогда, когда значение явно равно DBNull.Value. int? не может иметь это значение ... - person JJS; 27.12.2018
comment
@JJS Если вы считаете, что это нежелательно, вы сообщили об ошибке на GitHub? - person Arithmomaniac; 03.01.2019
comment
@Arithmomaniac У меня еще нет, но я должен пойти дальше и отправить Марку запрос на перенос ... - person JJS; 04.01.2019

Спасибо Скай Сандерсу за его первоначальный ответ, это было большим подспорьем.

Я написал общую версию EnumerableDataReader Скай Сандерса, но внес следующие изменения:

  • Читатель теперь поддерживает как общедоступные свойства, так и общедоступные поля.
  • Конструктор теперь принимает список имен полей / свойств, которые должны быть доступны в DataReader. Это позволяет ограничивать и определять порядок свойств и / или полей, чтобы они соответствовали макету вашей таблицы. Если не указан список полей, доступно только одно поле, а именно сам объект, таким образом считыватель можно использовать, например, со списком.
  • Исходный код, использующий DynamicMethod и ILGenerator, не работал с интерфейсами и требовал изменения кода для структур, поэтому я изменил его с помощью Linq.Expressions, который, похоже, работает во всех случаях (и в качестве бонуса он более компактный). Производительность для полей такая же, как у ILGenerator, производительность для свойств кажется немного ниже, чем с ILGenerator, но все же намного быстрее, чем при использовании отражения.
  • Возможно, это не плюс, но я поместил весь код в один класс (с некоторыми вспомогательными подклассами), удалив весь ненужный код. (Я не говорю, что код бесполезен, просто в моем случае он мне не понадобился.)

Надеюсь, это поможет, и если у вас есть замечания, исправления или улучшения, сообщите об этом :)

/// <summary>
/// IDataReader that can be used for "reading" an IEnumerable<T> collection
/// </summary>
public class EnumerableDataReader<T> : IDataReader
{
    /// <summary>
    /// Constructor
    /// </summary>
    /// <param name="collection">The collection to be read</param>
    /// <param name="fields">The list of public field/properties to read from each T (in order), OR if no fields are given only one field will be available: T itself</param>
    public EnumerableDataReader(IEnumerable<T> collection, params string[] fields)
    {
        if (collection == null)
            throw new ArgumentNullException("collection");

        m_Enumerator = collection.GetEnumerator();

        if (m_Enumerator == null)
            throw new NullReferenceException("collection does not implement GetEnumerator");

        SetFields(fields);
    }
    private IEnumerator<T> m_Enumerator;
    private T m_Current = default(T);
    private bool m_EnumeratorState = false;

    private void SetFields(ICollection<string> fields)
    {
        if (fields.Count > 0)
        {
            Type type = typeof(T);
            foreach (string field in fields)
            {
                PropertyInfo pInfo = type.GetProperty(field);
                if (pInfo != null)
                    m_Fields.Add(new Property(pInfo));
                else
                {
                    FieldInfo fInfo = type.GetField(field);
                    if (fInfo != null)
                        m_Fields.Add(new Field(fInfo));
                    else
                        throw new NullReferenceException(string.Format("EnumerableDataReader<T>: Missing property or field '{0}' in Type '{1}'.", field, type.Name));
                }
            }
        }
        else
            m_Fields.Add(new Self());
    }
    private List<BaseField> m_Fields = new List<BaseField>();

    #region IDisposable Members
    public void Dispose()
    {
        if (m_Enumerator != null)
        {
            m_Enumerator.Dispose();
            m_Enumerator = null;
            m_Current = default(T);
            m_EnumeratorState = false;
        }
        m_Closed = true;
    }
    #endregion

    #region IDataReader Members
    public void Close()
    {
        m_Closed = true;
    }
    private bool m_Closed = false;

    public int Depth
    {
        get { return 0; }
    }

    public DataTable GetSchemaTable()
    {
        var dt = new DataTable();
        foreach (BaseField field in m_Fields)
        {
            dt.Columns.Add(new DataColumn(field.Name, field.Type));
        }
        return dt;
    }

    public bool IsClosed
    {
        get { return m_Closed; }
    }

    public bool NextResult()
    {
        return false;
    }

    public bool Read()
    {
        if (IsClosed)
            throw new InvalidOperationException("DataReader is closed");
        m_EnumeratorState = m_Enumerator.MoveNext();
        m_Current = m_EnumeratorState ? m_Enumerator.Current : default(T);
        return m_EnumeratorState;
    }

    public int RecordsAffected
    {
        get { return -1; }
    }
    #endregion

    #region IDataRecord Members
    public int FieldCount
    {
        get { return m_Fields.Count; }
    }

    public Type GetFieldType(int i)
    {
        if (i < 0 || i >= m_Fields.Count)
            throw new IndexOutOfRangeException();
        return m_Fields[i].Type;
    }

    public string GetDataTypeName(int i)
    {
        return GetFieldType(i).Name;
    }

    public string GetName(int i)
    {
        if (i < 0 || i >= m_Fields.Count)
            throw new IndexOutOfRangeException();
        return m_Fields[i].Name;
    }

    public int GetOrdinal(string name)
    {
        for (int i = 0; i < m_Fields.Count; i++)
            if (m_Fields[i].Name == name)
                return i;
        throw new IndexOutOfRangeException("name");
    }

    public bool IsDBNull(int i)
    {
        return GetValue(i) == null;
    }

    public object this[string name]
    {
        get { return GetValue(GetOrdinal(name)); }
    }

    public object this[int i]
    {
        get { return GetValue(i); }
    }

    public object GetValue(int i)
    {
        if (IsClosed || !m_EnumeratorState)
            throw new InvalidOperationException("DataReader is closed or has reached the end of the enumerator");
        if (i < 0 || i >= m_Fields.Count)
            throw new IndexOutOfRangeException();
        return m_Fields[i].GetValue(m_Current);
    }

    public int GetValues(object[] values)
    {
        int length = Math.Min(m_Fields.Count, values.Length);
        for (int i = 0; i < length; i++)
            values[i] = GetValue(i);
        return length;
    }

    public bool GetBoolean(int i) { return (bool)GetValue(i); }
    public byte GetByte(int i) { return (byte)GetValue(i); }
    public char GetChar(int i) { return (char)GetValue(i); }
    public DateTime GetDateTime(int i) { return (DateTime)GetValue(i); }
    public decimal GetDecimal(int i) { return (decimal)GetValue(i); }
    public double GetDouble(int i) { return (double)GetValue(i); }
    public float GetFloat(int i) { return (float)GetValue(i); }
    public Guid GetGuid(int i) {return (Guid)GetValue(i); }
    public short GetInt16(int i) { return (short)GetValue(i); }
    public int GetInt32(int i) { return (int)GetValue(i); }
    public long GetInt64(int i) { return (long)GetValue(i); }
    public string GetString(int i) { return (string)GetValue(i); }

    public long GetBytes(int i, long fieldOffset, byte[] buffer, int bufferoffset, int length) { throw new NotSupportedException(); }
    public long GetChars(int i, long fieldoffset, char[] buffer, int bufferoffset, int length) { throw new NotSupportedException(); }
    public IDataReader GetData(int i) { throw new NotSupportedException(); }
    #endregion

    #region Helper Classes
    private abstract class BaseField
    {
        public abstract Type Type { get; }
        public abstract string Name { get; }
        public abstract object GetValue(T instance);

        protected static void AddGetter(Type classType, string fieldName, Func<T, object> getter)
        {
            m_GetterDictionary.Add(string.Concat(classType.FullName, fieldName), getter);
        }

        protected static Func<T, object> GetGetter(Type classType, string fieldName)
        {
            Func<T, object> getter = null;
            if (m_GetterDictionary.TryGetValue(string.Concat(classType.FullName, fieldName), out getter))
                return getter;
            return null;
        }
        private static Dictionary<string, Func<T, object>> m_GetterDictionary = new Dictionary<string, Func<T, object>>();
    }

    private class Property : BaseField
    {
        public Property(PropertyInfo info)
        {
            m_Info = info;
            m_DynamicGetter = CreateGetMethod(info);
        }
        private PropertyInfo m_Info;
        private Func<T, object> m_DynamicGetter;

        public override Type Type { get { return m_Info.PropertyType; } }
        public override string Name { get { return m_Info.Name; } }

        public override object GetValue(T instance)
        {
            //return m_Info.GetValue(instance, null); // Reflection is slow
            return m_DynamicGetter(instance);
        }

        // Create dynamic method for faster access instead via reflection
        private Func<T, object> CreateGetMethod(PropertyInfo propertyInfo)
        {
            Type classType = typeof(T);
            Func<T, object> dynamicGetter = GetGetter(classType, propertyInfo.Name);
            if (dynamicGetter == null)
            {
                ParameterExpression instance = Expression.Parameter(classType);
                MemberExpression property = Expression.Property(instance, propertyInfo);
                UnaryExpression convert = Expression.Convert(property, typeof(object));
                dynamicGetter = (Func<T, object>)Expression.Lambda(convert, instance).Compile();
                AddGetter(classType, propertyInfo.Name, dynamicGetter);
            }

            return dynamicGetter;
        }
    }

    private class Field : BaseField
    {
        public Field(FieldInfo info)
        {
            m_Info = info;
            m_DynamicGetter = CreateGetMethod(info);
        }
        private FieldInfo m_Info;
        private Func<T, object> m_DynamicGetter;

        public override Type Type { get { return m_Info.FieldType; } }
        public override string Name { get { return m_Info.Name; } }

        public override object GetValue(T instance)
        {
            //return m_Info.GetValue(instance); // Reflection is slow
            return m_DynamicGetter(instance);
        }

        // Create dynamic method for faster access instead via reflection
        private Func<T, object> CreateGetMethod(FieldInfo fieldInfo)
        {
            Type classType = typeof(T);
            Func<T, object> dynamicGetter = GetGetter(classType, fieldInfo.Name);
            if (dynamicGetter == null)
            {
                ParameterExpression instance = Expression.Parameter(classType);
                MemberExpression property = Expression.Field(instance, fieldInfo);
                UnaryExpression convert = Expression.Convert(property, typeof(object));
                dynamicGetter = (Func<T, object>)Expression.Lambda(convert, instance).Compile();
                AddGetter(classType, fieldInfo.Name, dynamicGetter);
            }

            return dynamicGetter;
        }
    }

    private class Self : BaseField
    {
        public Self()
        {
            m_Type = typeof(T);
        }
        private Type m_Type;

        public override Type Type { get { return m_Type; } }
        public override string Name { get { return string.Empty; } }
        public override object GetValue(T instance) { return instance; }
    }
    #endregion
}
person Marc    schedule 09.09.2013
comment
отличная реализация! это, должно быть, потребовало времени и терпения. Спасибо, что поделился - person Michael McCarthy; 02.02.2015