2

我已经通过 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

应该结果 a 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 在 Client 中一样。这段代码很乱。

 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 中创建正确的列表?

4

3 回答 3

2

您的问题确实很常见,实际上流通中的每个 ORM 都解决了这个问题。
当然,更改已经编写好的应用程序以利用 ORM 通常是不切实际的,但是有一些简单的 ORM 真的很容易添加到现有应用程序中,并且可以让您逐步替换已经编写的代码。

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

迄今为止,我发现的唯一麻烦是 POCO 属性和字段名称的一对一映射,以及当您的键未命名时,PK 和 FK 之间有时会规避规则ID。但那是我,我还没有完全理解这些规则。

于 2013-10-07T21:38:14.323 回答
1

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

于 2013-10-07T13:30:42.910 回答
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;
        }
    }
于 2013-10-08T00:32:33.570 回答