C# でリフレクションを使用して DataTable を List に変換することについて、StackOverflow (および他の Web サイト) を既に検索しました。
これまでの結果はかなり良好です。3.5 秒 (ハードコード モードでは 0.5 秒) で 200k 行を反映できます。
しかし、私のエンティティ (私のデータを表すクラスですが、既にご存知だと思います) は次のパターンに従います。
私のデータベースには次のような列があります(実際にはこれを行いませんが、アイデアは得られます):
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 に変換する主な方法を次に示します。私の母国語はポルトガル語ですが、上記の説明に合わせてコメントを翻訳しようとしました.
このコードの考え方は次のとおりです。設定する必要がある列をメイン クラスで見つけようとします。見つからない場合は、オブジェクトであるプロパティでプロパティを検索します。Client 内の 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 レベルのリフレクションを実行できるため、必要な多くのレベルを満たすには再帰的なアプローチが必要です。私はこれをやろうとしましたが、非常に多くのスタックオーバーフローとnull参照例外が発生し、ほとんどあきらめました。
このコードを使用すると、データベースの行をフェッチするのがはるかに簡単になります。必要なライブラリまたは何かを既に見つけましたか? そうでない場合、DataTable から適切なリストを作成するために n レベルのリフレクションを実現するにはどうすればよいですか?