天天看點

如何将LINQ查詢到的結果由匿名類型var轉換成DataTable對象

.NET中的LINQ對于操作集合對象提供了很多的便利,使得我們可以在C#中使用類似于SQL語句的方式對集合中的對象進行查找、分組、統計等。使用LINQ寫出來的代碼簡單明了,省去了我們原本需要使用大量for循環或者foreach循環才能實作的效果。衆所周知,通過LINQ查詢所傳回的結果一般來說是一個以var所辨別的匿名類型,該類型繼承自IEnumerable接口,我們可以直接将它綁定到任何一個資料綁定控件,如DropDownList,ListBox,DataGridView等。但這裡有一個問題,對于DataGridView(WinForm版)來說,如果使用LINQ傳回的匿名對象進行資料綁定的話,會失去DataGridView中單擊列标題進行資料排序的功能,這是因為DataGridView不能從一個匿名對象中擷取到進行資料排序的具體規則。要解決這個問題,你可以給這個匿名對象編寫具體的排序算法,不過最簡單的做法還是将這個匿名對象轉換成我們所熟悉的集合對象,如DataTable,然後再綁定到DataGridView中。

<a></a>

// Bind the System.Windows.Forms.DataGridView object

// to the System.Windows.Forms.BindingSource object.

dataGridView.DataSource = bindingSource;

// Fill the DataSet.

DataSet ds = new DataSet();

ds.Locale = CultureInfo.InvariantCulture;

FillDataSet(ds);

DataTable orders = ds.Tables["SalesOrderHeader"];

// Query the SalesOrderHeader table for orders placed 

// after August 8, 2001.

IEnumerable&lt;DataRow&gt; query =

    from order in orders.AsEnumerable()

    where order.Field&lt;DateTime&gt;("OrderDate") &gt; new DateTime(2001, 8, 1)

    select order;

// Create a table from the query.

DataTable boundTable = query.CopyToDataTable&lt;DataRow&gt;();

// Bind the table to a System.Windows.Forms.BindingSource object, 

// which acts as a proxy for a System.Windows.Forms.DataGridView object.

bindingSource.DataSource = boundTable;

  不過這個方法不是我們所希望的!原因是其中的泛型類型必須是DataRow而不能是自定義類型。怎麼辦呢?我們可不可以将這個方法修改一下讓它能支援任意類型?

  C#擴充方法是給現有類型“添加”一個方法,現有類型可以是基本資料類型(如int,string等),也可以是自定義類型。定義規則是擴充方法必須定義在一個任意命名的靜态類中,該方法必須是靜态方法,可以任意命名,方法的參數清單必須以this關鍵字開始,第二個即為要擴充的資料類型,第三個是一個變量名,同時參數清單中允許定義多個其它參數以實作方法的重載。來看一個例子。

namespace ExtensionMethods

{

    public static class MyExtensions

    {

        public static int WordCount(this String str)

        {

            return str.Split(new char[] { ' ', '.', '?' }, 

                             StringSplitOptions.RemoveEmptyEntries).Length;

        }

    }   

}

  靜态類MyExtensions被定義在命名空間ExtensionMethods中,靜态方法WordCount的參數清單中規定了該方法是對String類型的方法進行了擴充。在實際應用中,你需要在代碼中添加對ExtensionMethods命名空間的引用,然後通過String.WordCount()的方式來調用這個擴充方法。是不是很神奇啊?再看一個例子。

namespace MyExtension

    public static class Test    {

        public static XElement ToXml(this DirectoryInfo dir)

            // TO Do Something

    } 

  上面的代碼片段對DirectoryInfo類的方法進行了擴充,将上述代碼補充完整,便可以直接通過下面的方式調用新擴充的方法。

DirectoryInfo dir = new DirectoryInfo(path);

dir.ToXml();

  C#擴充方法允許對自定義的類型進行擴充,同時允許帶參數,支援重載。看下面的例子。

namespace TestExtendMethod

    public class Student

        public string Description()

            return "Student.............";

        public string Description(string name)

            return "the student’s name is " + name;

    }

    public static class Extensions

        public static string TestMethod(this Student s)

            return s.Description();

        public static string TestMethod(this Student s, string name)

            return s.Description(name);

  于是,自定義的Student類具有了包含一個重載的TestMethod方法,該方法允許接收一個string類型的參數或者沒有參數。

  好了!回到我們的主題上來。既然C#擴充方法允許我們對類型添加方法,那麼我們完全可以對已有的IEnumerable接口擴充一個CopyToDataTable方法,使其可以将LINQ傳回的var匿名類型轉換成DataTable。來看下具體的實作。

using System;

using System.Collections.Generic;

using System.ComponentModel;

using System.Data;

using System.Drawing;

using System.Linq;

using System.Text;

using System.Windows.Forms;

using System.Reflection;

namespace WindowsFormsApplication2

    public partial class Form1 : Form

        public Form1()

            InitializeComponent();

            // create sequence 

            Item[] items = new Item[] { new Book{Id = 1, Price = 13.50, Genre = "Comedy", Author = "Jim Bob"}, 

                                        new Book{Id = 2, Price = 8.50, Genre = "Drama", Author = "John Fox"},  

                                        new Movie{Id = 1, Price = 22.99, Genre = "Comedy", Director = "Phil Funk"},

                                        new Movie{Id = 1, Price = 13.40, Genre = "Action", Director = "Eddie Jones"}};

            var query1 = from i in items

                         where i.Price &gt; 9.99

                         orderby i.Price

                         select i;

            // load into new DataTable

            DataTable table1 = query1.CopyToDataTable();

            this.dataGridView1.DataSource = table1;

    public class Item

        public int Id { get; set; }

        public double Price { get; set; }

        public string Genre { get; set; }

    public class Book : Item

        public string Author { get; set; }

    public class Movie : Item

        public string Director { get; set; }

    public static class DataSetLinqOperators

        public static DataTable CopyToDataTable&lt;T&gt;(this IEnumerable&lt;T&gt; source)

            return new ObjectShredder&lt;T&gt;().Shred(source, null, null);

        public static DataTable CopyToDataTable&lt;T&gt;(this IEnumerable&lt;T&gt; source,

                                                    DataTable table, LoadOption? options)

            return new ObjectShredder&lt;T&gt;().Shred(source, table, options);

    public class ObjectShredder&lt;T&gt;

        private FieldInfo[] _fi;

        private PropertyInfo[] _pi;

        private Dictionary&lt;string, int&gt; _ordinalMap;

        private Type _type;

        public ObjectShredder()

            _type = typeof(T);

            _fi = _type.GetFields();

            _pi = _type.GetProperties();

            _ordinalMap = new Dictionary&lt;string, int&gt;();

        public DataTable Shred(IEnumerable&lt;T&gt; source, DataTable table, LoadOption? options)

            if (typeof(T).IsPrimitive)

            {

                return ShredPrimitive(source, table, options);

            }

            if (table == null)

                table = new DataTable(typeof(T).Name);

            // now see if need to extend datatable base on the type T + build ordinal map

            table = ExtendTable(table, typeof(T));

            table.BeginLoadData();

            using (IEnumerator&lt;T&gt; e = source.GetEnumerator())

                while (e.MoveNext())

                {

                    if (options != null)

                    {

                        table.LoadDataRow(ShredObject(table, e.Current), (LoadOption)options);

                    }

                    else

                        table.LoadDataRow(ShredObject(table, e.Current), true);

                }

            table.EndLoadData();

            return table;

        public DataTable ShredPrimitive(IEnumerable&lt;T&gt; source, DataTable table, LoadOption? options)

            if (!table.Columns.Contains("Value"))

                table.Columns.Add("Value", typeof(T));

                Object[] values = new object[table.Columns.Count];

                    values[table.Columns["Value"].Ordinal] = e.Current;

                        table.LoadDataRow(values, (LoadOption)options);

                        table.LoadDataRow(values, true);

        public DataTable ExtendTable(DataTable table, Type type)

            // value is type derived from T, may need to extend table.

            foreach (FieldInfo f in type.GetFields())

                if (!_ordinalMap.ContainsKey(f.Name))

                    DataColumn dc = table.Columns.Contains(f.Name) ? table.Columns[f.Name]

                        : table.Columns.Add(f.Name, f.FieldType);

                    _ordinalMap.Add(f.Name, dc.Ordinal);

            foreach (PropertyInfo p in type.GetProperties())

                if (!_ordinalMap.ContainsKey(p.Name))

                    DataColumn dc = table.Columns.Contains(p.Name) ? table.Columns[p.Name]

                        : table.Columns.Add(p.Name, p.PropertyType);

                    _ordinalMap.Add(p.Name, dc.Ordinal);

        public object[] ShredObject(DataTable table, T instance)

            FieldInfo[] fi = _fi;

            PropertyInfo[] pi = _pi;

            if (instance.GetType() != typeof(T))

                ExtendTable(table, instance.GetType());

                fi = instance.GetType().GetFields();

                pi = instance.GetType().GetProperties();

            Object[] values = new object[table.Columns.Count];

            foreach (FieldInfo f in fi)

                values[_ordinalMap[f.Name]] = f.GetValue(instance);

            foreach (PropertyInfo p in pi)

                values[_ordinalMap[p.Name]] = p.GetValue(instance, null);

            return values;

   Item,Book,Movie都是自定義類型,擴充方法對IEnumerable泛型接口添加了能支援任意類型并傳回DataTable的方法CopyToDataTable,于是,我們可以直接對LINQ傳回的var匿名類型使用CopyDoDataTable方法并将傳回值指派給DataTable對象。然後将DataTable直接綁定給DataGridView進而擷取點選列标題進行資料排序的功能。還有稍微複雜一點的應用,給一個代碼片段的截圖。

本文轉自Jaxu部落格園部落格,原文連結:http://www.cnblogs.com/jaxu/archive/2011/08/02/2125055.html,如需轉載請自行聯系原作者