将 DataTable 转换为 IEnumerable<T>

Posted

技术标签:

【中文标题】将 DataTable 转换为 IEnumerable<T>【英文标题】:Convert DataTable to IEnumerable<T> 【发布时间】:2011-03-24 11:38:49 【问题描述】:

我正在尝试将 DataTable 转换为 IEnumerable。其中 T 是我创建的自定义类型。我知道我可以通过创建List&lt;T&gt; 来做到这一点,但我在想是否有一种更巧妙的方法可以使用 IEnumerable 来做到这一点。这是我现在拥有的:

private IEnumerable<TankReading> ConvertToTankReadings(DataTable dataTable)

    var tankReadings = new List<TankReading>();
    foreach (DataRow row in dataTable.Rows)
    
        var tankReading = 
              new TankReading
              
                  TankReadingsID = Convert.ToInt32(row["TRReadingsID"]),
                  TankID = Convert.ToInt32(row["TankID"]),
                  ReadingDateTime = Convert.ToDateTime(row["ReadingDateTime"]),
                  ReadingFeet = Convert.ToInt32(row["ReadingFeet"]),
                  ReadingInches = Convert.ToInt32(row["ReadingInches"]),
                  MaterialNumber = row["MaterialNumber"].ToString(),
                  EnteredBy = row["EnteredBy"].ToString(),
                  ReadingPounds = Convert.ToDecimal(row["ReadingPounds"]),
                  MaterialID = Convert.ToInt32(row["MaterialID"]),
                  Submitted = Convert.ToBoolean(row["Submitted"]),
              ;

        tankReadings.Add(tankReading);
    
    return tankReadings.AsEnumerable();

关键部分是我正在创建一个List&lt;T&gt;,然后使用AsEnumerable() 返回它。

【问题讨论】:

【参考方案1】:

该实现没有错。你可以试试yield 关键字,看看你喜欢它:

private IEnumerable<TankReading> ConvertToTankReadings(DataTable dataTable)
    
        foreach (DataRow row in dataTable.Rows)
        
            yield return new TankReading
                                  
                                      TankReadingsID = Convert.ToInt32(row["TRReadingsID"]),
                                      TankID = Convert.ToInt32(row["TankID"]),
                                      ReadingDateTime = Convert.ToDateTime(row["ReadingDateTime"]),
                                      ReadingFeet = Convert.ToInt32(row["ReadingFeet"]),
                                      ReadingInches = Convert.ToInt32(row["ReadingInches"]),
                                      MaterialNumber = row["MaterialNumber"].ToString(),
                                      EnteredBy = row["EnteredBy"].ToString(),
                                      ReadingPounds = Convert.ToDecimal(row["ReadingPounds"]),
                                      MaterialID = Convert.ToInt32(row["MaterialID"]),
                                      Submitted = Convert.ToBoolean(row["Submitted"]),
                                  ;
        

    

AsEnumerable 也不是必需的,因为 List&lt;T&gt; 已经是 IEnumerable&lt;T&gt;

【讨论】:

【参考方案2】:

还有一个名为“AsEnumerable()”的 DataSetExtension 方法(在 System.Data 中),它接受一个 DataTable 并返回一个 Enumerable。有关更多详细信息,请参阅the MSDN doc,但它基本上很简单:

dataTable.AsEnumerable()

缺点是它枚举 DataRow,而不是您的自定义类。但是,“Select()”LINQ 调用可以转换行数据:

private IEnumerable<TankReading> ConvertToTankReadings(DataTable dataTable)

    return dataTable.AsEnumerable().Select(row => new TankReading      
                  
                TankReadingsID = Convert.ToInt32(row["TRReadingsID"]),      
                TankID = Convert.ToInt32(row["TankID"]),      
                ReadingDateTime = Convert.ToDateTime(row["ReadingDateTime"]),      
                ReadingFeet = Convert.ToInt32(row["ReadingFeet"]),      
                ReadingInches = Convert.ToInt32(row["ReadingInches"]),      
                MaterialNumber = row["MaterialNumber"].ToString(),      
                EnteredBy = row["EnteredBy"].ToString(),      
                ReadingPounds = Convert.ToDecimal(row["ReadingPounds"]),      
                MaterialID = Convert.ToInt32(row["MaterialID"]),      
                Submitted = Convert.ToBoolean(row["Submitted"]),      
            );

【讨论】:

哦,不错。我没有注意到dataTable.AsEnumerable(),而且总是做得更长更丑:dataTable.Rows.Cast&lt;DataSetName.SomeLongDataTableRowName&gt;() AsEnumerable 扩展方法位于 System.Data 命名空间中,但请务必引用 System.Data.DataSetExtensions 程序集以使用它。 对数据视图使用 .Cast() 扩展。 如果您使用的是旧版本的 .NET,您还可以通过 dataTable.Cast&lt;DataRow&gt;() 获取 IEnumerable【参考方案3】:
        PagedDataSource objPage = new PagedDataSource();

        DataView dataView = listData.DefaultView;
        objPage.AllowPaging = true;
        objPage.DataSource = dataView;
        objPage.PageSize = PageSize;

        TotalPages = objPage.PageCount;

        objPage.CurrentPageIndex = CurrentPage - 1;

        //Convert PagedDataSource to DataTable
        System.Collections.IEnumerator pagedData = objPage.GetEnumerator();

        DataTable filteredData = new DataTable();
        bool flagToCopyDTStruct = false;
        while (pagedData.MoveNext())
        
            DataRowView rowView = (DataRowView)pagedData.Current;
            if (!flagToCopyDTStruct)
            
                filteredData = rowView.Row.Table.Clone();
                flagToCopyDTStruct = true;
            
            filteredData.LoadDataRow(rowView.Row.ItemArray, true);
        

        //Here is your filtered DataTable
        return filterData; 

【讨论】:

【参考方案4】:

使用System.Data.DataSetExtensions的简单方法:

table.AsEnumerable().Select(row => new TankReading
        TankReadingsID = Convert.ToInt32(row["TRReadingsID"]),
        TankID = Convert.ToInt32(row["TankID"]),
        ReadingDateTime = Convert.ToDateTime(row["ReadingDateTime"]),
        ReadingFeet = Convert.ToInt32(row["ReadingFeet"]),
        ReadingInches = Convert.ToInt32(row["ReadingInches"]),
        MaterialNumber = row["MaterialNumber"].ToString(),
        EnteredBy = row["EnteredBy"].ToString(),
        ReadingPounds = Convert.ToDecimal(row["ReadingPounds"]),
        MaterialID = Convert.ToInt32(row["MaterialID"]),
        Submitted = Convert.ToBoolean(row["Submitted"]),
    );

或者:

TankReading TankReadingFromDataRow(DataRow row)
    return new TankReading
        TankReadingsID = Convert.ToInt32(row["TRReadingsID"]),
        TankID = Convert.ToInt32(row["TankID"]),
        ReadingDateTime = Convert.ToDateTime(row["ReadingDateTime"]),
        ReadingFeet = Convert.ToInt32(row["ReadingFeet"]),
        ReadingInches = Convert.ToInt32(row["ReadingInches"]),
        MaterialNumber = row["MaterialNumber"].ToString(),
        EnteredBy = row["EnteredBy"].ToString(),
        ReadingPounds = Convert.ToDecimal(row["ReadingPounds"]),
        MaterialID = Convert.ToInt32(row["MaterialID"]),
        Submitted = Convert.ToBoolean(row["Submitted"]),
    ;


// Now you can do this
table.AsEnumerable().Select(row => return TankReadingFromDataRow(row));

或者,更好的是,创建一个TankReading(DataRow r) 构造函数,然后变成:

    table.AsEnumerable().Select(row => return new TankReading(row));

【讨论】:

【参考方案5】:

如果您从 SQL 查询中生成 DataTable,您是否考虑过简单地使用 Dapper?

然后,而不是使用 SqlParametersDataTableDataAdapter 来制作 SqlCommand 等等,然后您必须费力地将其转换为类,您只需定义类,制作查询列名与字段名匹配,参数通过名称轻松绑定。您已经定义了 TankReading 类,所以它真的很简单!

using Dapper;

// Below can be SqlConnection cast to DatabaseConnection, too.
DatabaseConnection connection = // whatever
IEnumerable<TankReading> tankReadings = connection.Query<TankReading>(
   "SELECT * from TankReading WHERE Value = @value",
   new  value = "tank1"  // note how `value` maps to `@value`
);
return tankReadings;

现在是不是很棒? Dapper 进行了非常优化,可以为您提供与直接使用DataAdapter 阅读相当的性能。

如果你的类有任何逻辑或者是不可变的或者没有无参数的构造函数,那么你可能确实需要一个DbTankReading 类(作为一个纯 POCO/Plain Old Class Object):

// internal because it should only be used in the data source project and not elsewhere
internal sealed class DbTankReading 
   int TankReadingsID  get; set; 
   DateTime? ReadingDateTime  get; set; 
   int ReadingFeet  get; set; 
   int ReadingInches  get; set; 
   string MaterialNumber  get; set; 
   string EnteredBy  get; set; 
   decimal ReadingPounds  get; set; 
   int MaterialID  get; set; 
   bool Submitted  get; set; 

你会这样使用它:

IEnumerable<TankReading> tankReadings = connection
   .Query<DbTankReading>(
      "SELECT * from TankReading WHERE Value = @value",
      new  value = "tank1"  // note how `value` maps to `@value`
   )
   .Select(tr => new TankReading(
      tr.TankReadingsID,
      tr.ReadingDateTime,
      tr.ReadingFeet,
      tr.ReadingInches,
      tr.MaterialNumber,
      tr.EnteredBy,
      tr.ReadingPounds,
      tr.MaterialID,
      tr.Submitted
   );

尽管映射工作,这仍然没有数据表方法那么痛苦。这也可以让您执行某种逻辑,但如果逻辑不仅仅是非常简单的直接映射,我会将逻辑放入单独的 TankReadingMapper 类中。

【讨论】:

【参考方案6】:

DataTable 的通用扩展方法。可能有人会很有趣。我从另一篇文章中获取创建动态属性的想法:https://***.com/a/15819760/8105226

    public static IEnumerable<dynamic> AsEnumerable(this DataTable dt)
    
        List<dynamic> result = new List<dynamic>();
        Dictionary<string, object> d;
        foreach (DataRow dr in dt.Rows)
        
            d = new Dictionary<string, object>();

            foreach (DataColumn dc in dt.Columns)
                d.Add(dc.ColumnName, dr[dc]);

            result.Add(GetDynamicObject(d));
        
        return result.AsEnumerable<dynamic>();
    

    public static dynamic GetDynamicObject(Dictionary<string, object> properties)
    
        return new MyDynObject(properties);
    

    public sealed class MyDynObject : DynamicObject
    
        private readonly Dictionary<string, object> _properties;

        public MyDynObject(Dictionary<string, object> properties)
        
            _properties = properties;
        

        public override IEnumerable<string> GetDynamicMemberNames()
        
            return _properties.Keys;
        

        public override bool TryGetMember(GetMemberBinder binder, out object result)
        
            if (_properties.ContainsKey(binder.Name))
            
                result = _properties[binder.Name];
                return true;
            
            else
            
                result = null;
                return false;
            
        

        public override bool TrySetMember(SetMemberBinder binder, object value)
        
            if (_properties.ContainsKey(binder.Name))
            
                _properties[binder.Name] = value;
                return true;
            
            else
            
                return false;
            
        
    

【讨论】:

【参考方案7】:

如果您想将任何 DataTable 转换为等效的 IEnumerable 向量函数。

请看一下下面的通用函数,这可能对您的需求有所帮助(您可能需要根据您的需要包含针对不同数据类型的编写案例)。

/// <summary>
    /// Get entities from DataTable
    /// </summary>
    /// <typeparam name="T">Type of entity</typeparam>
    /// <param name="dt">DataTable</param>
    /// <returns></returns>
    public IEnumerable<T> GetEntities<T>(DataTable dt)
    
        if (dt == null)
        
            return null;
        

        List<T> returnValue = new List<T>();
        List<string> typeProperties = new List<string>();

        T typeInstance = Activator.CreateInstance<T>();

        foreach (DataColumn column in dt.Columns)
        
            var prop = typeInstance.GetType().GetProperty(column.ColumnName, BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Public);
            if (prop != null)
            
                typeProperties.Add(column.ColumnName);
            
        

        foreach (DataRow row in dt.Rows)
        
            T entity = Activator.CreateInstance<T>();

            foreach (var propertyName in typeProperties)
            

                if (row[propertyName] != DBNull.Value)
                
                    string str = row[propertyName].GetType().FullName;

                    if (entity.GetType().GetProperty(propertyName).PropertyType == typeof(System.String))
                    
                        object Val = row[propertyName].ToString();
                        entity.GetType().GetProperty(propertyName, BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Public).SetValue(entity, Val, BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Public, null, null, null);
                    
                    else if (entity.GetType().GetProperty(propertyName).PropertyType == typeof(System.Guid)) 
                    
                        object Val = Guid.Parse(row[propertyName].ToString());
                        entity.GetType().GetProperty(propertyName, BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Public).SetValue(entity, Val, BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Public, null, null, null);
                    
                    else
                    
                        entity.GetType().GetProperty(propertyName, BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Public).SetValue(entity, row[propertyName], BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Public, null, null, null);
                    
                
                else
                
                    entity.GetType().GetProperty(propertyName, BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Public).SetValue(entity, null, BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Public, null, null, null);
                
            

            returnValue.Add(entity);
        

        return returnValue.AsEnumerable();
    

【讨论】:

【参考方案8】:

我在here 上写了一篇关于这个主题的文章。 我想它可以帮助你。

通常它会这样做:

static void Main(string[] args)

    // Convert from a DataTable source to an IEnumerable.
    var usersSourceDataTable = CreateMockUserDataTable();
    var usersConvertedList = usersSourceDataTable.ToEnumerable<User>();

    // Convert from an IEnumerable source to a DataTable.
    var usersSourceList = CreateMockUserList();
    var usersConvertedDataTable = usersSourceList.ToDataTable<User>();

【讨论】:

以上是关于将 DataTable 转换为 IEnumerable<T>的主要内容,如果未能解决你的问题,请参考以下文章

NPOI 将excel转换为datatable或者将datatable转换为excel

将数组转换为 DataTable

Cucumber DataTable 错误 - io.cucumber.datatable.UndefinedDataTableTypeException:无法将 DataTable 转换为 cucu

C#实现将json转换为DataTable的方法

将 DataTable 转换为字典 C#

.NET - 将通用集合转换为 DataTable