在C#</t>中将DataTable转换为List <t>

时间:2013-10-07 13:28:55

标签: c# sql list reflection datatable

我已经通过StackOverflow(和其他网站)搜索了如何使用C#中的反射将DataTable转换为List。

到目前为止,我的结果非常好:我可以在3.5秒内反映200k行(在硬编码模式下为0.5秒)。

但是我的实体(代表我的数据的类,但我认为你已经知道了)遵循这种模式:

我的数据库有这样的列(我实际上并没有这样做,但你会明白这一点):

Table: Clients
Columns:
    ClientID, ClientName, ClientPhone, CityID[FK]

我正在使用SqlConnection(MySqlConnection),因此我必须对我的实体进行硬编码并将数据库结果转换为此实体的列表。喜欢:

Select *, cit.* from Clients cli
Inner join Cities cit on (cit.CityID == cli.CityID)
Inner join Countries cou on (cou.CountryID == cit.CountID)

我不知道这个SQL是否正确,但我认为你有了这个想法。这应该返回一些这样的字段:

ClientID, ClientName, ClientPhone, CityID, CityName, CountryID, CountryName

结果为List<Client>

问题在于:我有2个内部联接,我在这样的实体中表示这些数据(我喜欢“喜欢这个”这个表达式):

public class Client
{
    public int ClientID { get; set; }
    public string ClientName { get; set; }
    public string ClientPhone { get; set; }
    public City ClientCity { get; set; }
}

public class City
{
    public int CityID { get; set; }
    public string CityName { get; set; }
    public Country CityCountry { get; set; }
}

public class Country
{
    public int ContryID { get; set; }
    public string CountryName { get; set; }
}

所以,如果我有Client个对象,我会通过表达式client.ClientCity.CityCountry.CountryName获取其国家/地区名称。我把它称为3级财产代理人。

我想要正确反映它。这是将DataTable转换为List的主要方法。我的母语是葡萄牙语,但我试着翻译我的评论以符合上面的描述。

这段代码的想法是:我尝试在主类中找到我必须设置的列。如果我找不到它,我会在属性对象中搜索属性。像客户端内ClientCity中的CityName。这段代码很乱。

 public List<T> ToList<T>(DataTable dt) where T : new()
    {

        Type type= typeof(T);
        ReflectionHelper h = new ReflectionHelper(type);
        insertPropInfo(tipo);  //a pre-reflection work, I cache some delegates, etc..
        List<T> list = new List<T>();
        DataTableReader dtr = dt.CreateDataReader();
        while (dtr.Read())
        {
            T obj = new T();
            for (int i = 0; i < dtr.FieldCount; i++)
            {
                GetObject(ref obj, tipo, dtr.GetName(i), dtr.GetValue(i));
            }
            list.Add(obj);
        }

        return lista;

    }
        //ref T obj: the object I create before calling this method
        //Type classType: the type of the object (say, Client)
        //string colName: this is the Database Column i'm trying to fill. Like ClientID or CityName or CountryName.
        //colLineData: the data I want to put in the colName.

    public void GetObject<T>(ref T obj, Type classType, string colName, object colLineData) where T : new()
    {


        //I do some caching to reflect just once, and after the first iteration, I think all the reflection I need is already done.
        foreach (PropertyInfo info in _classPropInfos[classType])
        {
            //If the current PropertyInfo is a valuetype (like int, int64) or string, and so on
            if (info.PropertyType.IsValueType || info.PropertyType == typeof(string))
            {
                //I think string.Equals is a little faster, but i had not much difference using "string" == "string"
                if (info.Name.Equals(colName)) //did I found the property?

                    if (info.PropertyType != typeof(char)) //I have to convert the type if this is a Char. MySql returns char as string.
                    {
                        _delegateSetters[info](obj, colLineData); //if it isn't a char, just set it.
                    }
                    else
                    {
                        _delegateSetters[info](obj, Convert.ChangeType(colLineData, typeof(char)));
                    }
                break;
            }
            else //BUT, if the property is a class, like ClientCity:
            {
                //I reflect the City class, if it isn't reflected yet:
                if (!_classPropInfos.ContainsKey(info.PropertyType))
                {
                    insertPropInfo(info.PropertyType);
                }
                //now I search for the property:
                Boolean foundProperty = false;
                object instance = _delegateGetters[info](obj); //Get the existing instance of ClientCity, so I can fill the CityID and CityName in the same object.

                foreach (PropertyInfo subInfo in _classPropInfos[info.PropertyType])
                {
                    if (subInfo.Name.Equals(colName))//did I found the property?
                    {
                        if (instance == null)
                        {
                            //This will happen if i'm trying to set the first property of the class, like CityID. I have to instanciate it, so in the next iteration it won't be null, and will have it's CityID filled.
                            instance = _initializers[info.PropertyType]();//A very fast object initializer. I'm worried about the Dictionary lookups, but i have no other idea about how to cache it.
                        }
                        _delegateSetters[subInfo](instance, colLineData);//set the data. This method is very fast. Search about lambda getters & setters using System.Linq.Expression.
                        foundProperty = true;
                        break;//I break the loops when I find the property, so it wont iterate anymore.
                    }

                }
                if (foundProperty)//if I found the property in the code above, I set the instance of ClientCity to the Client object.
                {
                    _delegateSetters[info](obj, instance);
                    break;
                }
            }
        }
    }

此代码存在问题:我可以访问CityID和CityName,然后填写它。但是CountryID和CountryName不会。因为这段代码可以进行2级反射,所以我需要一些递归方法来填充我需要的许多级别。我试图这样做但是我得到了很多堆栈溢出和空引用异常我几乎放弃了。

此代码可以更轻松地获取数据库行,您是否已经找到了一些库或任何可以实现我想要的东西?如果没有,我怎样才能实现n级反射,从DataTable中创建一个合适的List?

3 个答案:

答案 0 :(得分:2)

你的问题非常普遍,几乎所有流通的ORM都解决了这个问题 当然,更改已编写的应用程序以利用ORM通常是不切实际的,但是有一些简单的ORM很容易添加到现有应用程序中,并允许您逐步替换已编写的代码。

其中一个ORM是DAPPER。它只包含一个源文件,您可以将其与POCO类和存储库方法直接包含在同一项目中(或者仅引用已编译的程序集)。考虑到要执行的工作的复杂性,它非常容易学习并且速度非常快。更不用说这个小宝石的作者经常在这个网站上回答他们的工作问题。只需使用#dapper代码进行搜索

即可

我迄今发现的唯一令人讨厌的是POCO属性和字段名称一对一的映射,以及当您的密钥未命名为ID时,PK和FK之间的某些规则。但那就是我,我还没有完全理解这些规则。

答案 1 :(得分:1)

考虑使用EntityFramework。它将自动完成所有这些工作。

答案 2 :(得分:1)

这是基于您获取包含3个表的数据集并创建正确的DataRelation。 在你的特定情况下(200k行)我不知道它将如何表现,但不应该那么糟糕:)。

您的主叫代码可能是这样的:

List<Clients> clients = Test.CreateListFromTable<Clients>(ds.Tables["Clients"]);

请记住,正如我所说,它基于你获取数据集并创建关系。 接下来是具有相关方法的类(ClientsToCity和CityToCountry是数据关系的名称,您可以自己放置):

    public class Test
    {
        // function that set the given object from the given data row
        public static void SetItemFromRow<T>(T item, DataRow row) where T : new()
        {
            foreach (DataColumn c in row.Table.Columns)
            {
                PropertyInfo prop = item.GetType().GetProperty(c.ColumnName);

                if (prop != null && row[c] != DBNull.Value)
                {
                    prop.SetValue(item, row[c], null);
                }
                else
                {
                    if (c.ColumnName == "CityID")
                    {
                        object obj = Activator.CreateInstance(typeof(City));

                        SetItemFromRow<City>(obj as City, row.GetChildRows("ClientsToCity")[0]);
                        PropertyInfo nestedprop = item.GetType().GetProperty("ClientCity");
                        nestedprop.SetValue(item, obj, null);
                    }
                    else if (c.ColumnName == "CountryID")
                    {
                        object obj = Activator.CreateInstance(typeof(Country));

                        SetItemFromRow<Country>(obj as Country, row.GetChildRows("CityToCountry")[0]);
                        PropertyInfo nestedprop = item.GetType().GetProperty("CityCountry");
                        nestedprop.SetValue(item, obj, null);
                    }
                }

            }
        }

        // function that creates an object from the given data row
        public static T CreateItemFromRow<T>(DataRow row) where T : new()
        {
            T item = new T();

            SetItemFromRow(item, row);

            return item;
        }

        // function that creates a list of an object from the given data table
        public static List<T> CreateListFromTable<T>(DataTable tbl) where T : new()
        {
            List<T> lst = new List<T>();

            foreach (DataRow r in tbl.Rows)
            {
                lst.Add(CreateItemFromRow<T>(r));
            }

            return lst;
        }
    }