我已经通过 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 中创建正确的列表?