关于c#:将DataTable转换为IEnumerable< T>

Convert DataTable to IEnumerable<T>

我正在尝试将数据表转换为IEnumerable。其中t是我创建的自定义类型。我知道我可以通过创建一个列表来完成它,但我认为有一种更巧妙的方法可以使用IEnumerable来完成它。这是我现在拥有的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
    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();
    }

关键部分是我正在创建一个列表,然后使用aseNumerable()返回它。


还有一个名为"AsEnumerable()(在System.Data中)的DataSetExtension方法,它获取一个DataTable并返回一个Enumerable。有关更多详细信息,请参阅msdn文档,但它基本上很简单:

1
dataTable.AsEnumerable()

缺点是它正在枚举数据行,而不是您的自定义类。"select()"LINQ调用可以转换行数据,但是:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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"]),      
            });
}


这个实现没有任何错误。你可以给yield关键字一个快照,看看你喜欢它:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
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已经是IEnumerable了。


如果您是从SQL查询生成DataTable,那么您是否考虑只使用dapper?

然后,不需要用SqlParametersDataTable以及DataAdapter等等制作一个SqlCommand,而需要费力地转换成一个类,只需定义类,使查询列名与字段名相匹配,参数很容易被名称绑定。您已经定义了TankReading类,所以这将非常简单!

1
2
3
4
5
6
7
8
9
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的旧类对象):

1
2
3
4
5
6
7
8
9
10
11
12
// 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; }
}

你可以这样使用:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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类中。


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

1
2
3
4
5
6
7
8
9
10
11
12
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"]),
    });

或:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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)构造函数,然后这就变成:

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

如果要将任何数据表转换为等效的IEnumerable向量函数。

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
/// <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();
    }

数据表的通用扩展方法。可能是某个有趣的人。我从另一篇文章中获得了创建动态属性的想法:https://stackoverflow.com/a/15819760/8105226

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
    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;
            }
        }
    }


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
        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;