2

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 レベルのリフレクションを実現するにはどうすればよいですか?

4

3 に答える 3

2

あなたの問題は本当に一般的であり、実際に出回っているすべての ORM がこの問題に対処しています。
もちろん、ORM を利用するために既に作成されたアプリケーションを変更することは、多くの場合非現実的ですが、既存のアプリケーションに非常に簡単に追加でき、既に作成されたコードを段階的に置き換えることができる単純な ORM がいくつかあります。

これらの ORM の 1 つがDAPPERです。これは、POCO クラスとリポジトリ メソッドを使用して同じプロジェクトに直接含めることができる 1 つのソース ファイルで構成されます (または、コンパイルされたアセンブリを参照するだけです)。習得は非常に簡単で、実行する作業の複雑さを考えると信じられないほど高速です。言うまでもなく、この小さな宝石の作成者は定期的にこのサイトにアクセスして、自分の仕事に関する質問に答えています。#dapperタグで検索してみてください

私がこれまでに発見した唯一の厄介な点は、POCO プロパティとフィールド名からの 1 対 1 のマッピングと、キーに名前が付けられていない場合の PK と FK の間のルールの回避IDです。しかし、私はまだこれらのルールを完全に理解していません。

于 2013-10-07T21:38:14.323 に答える
1

EntityFrameworkの使用を検討してください。このすべての作業を自動化します。

于 2013-10-07T13:30:42.910 に答える