2

Generics と Reflection の経験はほとんどありません。次のサンプルから私がそう推測したのは、実行に時間がかかりすぎるということです。リフレクションを使用せずに次のことを達成する方法はありますか..

シナリオ 汎用的なメソッドに取り組んでいます。渡されたクラスのインスタンスを受け取り、すべてのプロパティから SqlParameters を作成します。以下は、「Store」と呼ばれる汎用メソッドのコードと、C# 型を DbType の SqlDbType に変換するもう 1 つのメソッドです。

        List<SqlParameter> parameters = new List<SqlParameter>();
        public T Store<T>(T t)
        {
            Type type = t.GetType();
            PropertyInfo[] props = (t.GetType()).GetProperties();
            foreach (PropertyInfo p in props)
            {
                SqlParameter param = new SqlParameter();
                Type propType = p.PropertyType;
                if (propType.BaseType.Name.Equals("ValueType") || propType.BaseType.Name.Equals("Array"))
                {
                    param.SqlDbType = GetDBType(propType); //e.g. public bool enabled{get;set;} OR public byte[] img{get;set;}
                }
                else if (propType.BaseType.Name.Equals("Object"))
                {
                    if (propType.Name.Equals("String"))// for string values
                        param.SqlDbType = GetDBType(propType);
                    else
                    {
                        dynamic d = p.GetValue(t, null); // for referrences e.g. public ClassA obj{get;set;}
                        Store<dynamic>(d);
                    }
                }
                param.ParameterName = p.Name;
                parameters.Add(param);
            }
            return t;
        }



        // mehthod for getting the DbType OR SqlDbType from the type...
        private SqlDbType GetDBType(System.Type type)
        {
            SqlParameter param;
            System.ComponentModel.TypeConverter tc;
            param = new SqlParameter();
            tc = System.ComponentModel.TypeDescriptor.GetConverter(param.DbType);
            if (tc.CanConvertFrom(type))
            {
                param.DbType = (DbType)tc.ConvertFrom(type.Name);
            }
            else
            {
                // try to forcefully convert
                try
                {
                    param.DbType = (DbType)tc.ConvertFrom(type.Name);
                }
                catch (Exception e)
                {
                    switch (type.Name)
                    {
                        case "Char":
                            param.SqlDbType = SqlDbType.Char;
                            break;
                        case "SByte":
                            param.SqlDbType = SqlDbType.SmallInt;
                            break;
                        case "UInt16":
                            param.SqlDbType = SqlDbType.SmallInt;
                            break;
                        case "UInt32":
                            param.SqlDbType = SqlDbType.Int;
                            break;
                        case "UInt64":
                            param.SqlDbType = SqlDbType.Decimal;
                            break;
                        case "Byte[]":
                            param.SqlDbType = SqlDbType.Binary;
                            break;
                    }
                }
            }
            return param.SqlDbType;
        }

私のメソッドを呼び出すには、次のように2つのクラスがあるとします

public class clsParent
{
    public int pID { get; set; }
    public byte[] pImage { get; set; }
    public string pName { get; set; }
}

and

public class clsChild
{
    public decimal childId { get; set; }
    public string childName { get; set; }
    public clsParent parent { get; set; }
}

and this is a call 


clsParent p = new clsParent();
p.pID = 101;
p.pImage = new byte[1000];
p.pName = "John";
clsChild c = new clsChild();
c.childId = 1;
c.childName = "a";
c.parent = p;

Store<clsChild>(c);
4

5 に答える 5

3

リフレクションを取り除きたい場合は、以下のコードでインスピレーションを得ることができます。

ここでは、データベースに格納するオブジェクトへのすべてのアクセスと sql プロパティ値の割り当ては、データ型からビルドされたランタイム コンパイル式によって処理されます。

値を保持するテーブルは であるtestと見なされ、フィールド名はプロパティ値と同一であると見なされます。

各プロパティに対して aMapping<T>が構築されます。FieldNameこれは、データベース フィールドを含むを保持します。このフィールドは、SQLステートメント ( の例) にSqlParameter正しく挿入されることになっています。最後に、コンパイルされたアクションを含む場合は、入力オブジェクトのインスタンスを取得し、プロパティに値を割り当てることができます。これらのマッピングのコレクションの構築は、クラスで行われます。コードは説明のためにインライン化されています。INSERTmainTSqlParametersValueMapper<T>

最後に、このmainメソッドは、それらを結合する方法を示しています。

using System;
using System.Collections.Generic;
using System.Data.SqlClient;
using System.Diagnostics;
using System.Linq;
using System.Linq.Expressions;

namespace ExpTest
{
    class Program
    {
        public class Mapping<T>
        {
            public Mapping(string fieldname, SqlParameter sqlParameter, Action<T, SqlParameter> assigner)
            {
                FieldName = fieldname;
                SqlParameter = sqlParameter;
                SqlParameterAssignment = assigner;
            }
            public string FieldName { get; private set; }
            public SqlParameter SqlParameter { get; private set; }
            public Action<T, SqlParameter> SqlParameterAssignment { get; private set; }
        }

        public class Mapper<T>
        {
            public IEnumerable<Mapping<T>> GetMappingElements()
            {
                foreach (var reflectionProperty in typeof(T).GetProperties())
                {
                    // Input parameters to the created assignment action
                    var accessor = Expression.Parameter(typeof(T), "input");
                    var sqlParmAccessor = Expression.Parameter(typeof(SqlParameter), "sqlParm");

                    // Access the property (compiled later, but use reflection to locate property)
                    var property = Expression.Property(accessor, reflectionProperty);

                    // Cast the property to ensure it is assignable to SqlProperty.Value 
                    // Should contain branching for DBNull.Value when property == null
                    var castPropertyToObject = Expression.Convert(property, typeof(object));


                    // The sql parameter
                    var sqlParm = new SqlParameter(reflectionProperty.Name, null);

                    // input parameter for assignment action
                    var sqlValueProp = Expression.Property(sqlParmAccessor, "Value");

                    // Expression assigning the retrieved property from input object 
                    // to the sql parameters 'Value' property
                    var dbnull = Expression.Constant(DBNull.Value);
                    var coalesce = Expression.Coalesce(castPropertyToObject, dbnull);
                    var assign = Expression.Assign(sqlValueProp, coalesce);

                    // Compile into action (removes reflection and makes real CLR object)
                    var assigner = Expression.Lambda<Action<T, SqlParameter>>(assign, accessor, sqlParmAccessor).Compile();

                    yield return
                        new Mapping<T>(reflectionProperty.Name, // Table name
                            sqlParm, // The constructed sql parameter
                            assigner); // The action assigning from the input <T> 

                }
            }
        }

        public static void Main(string[] args)
        {
            var sqlStuff = (new Mapper<Data>().GetMappingElements()).ToList();

            var sqlFieldsList = string.Join(", ", sqlStuff.Select(x => x.FieldName));
            var sqlValuesList = string.Join(", ", sqlStuff.Select(x => '@' + x.SqlParameter.ParameterName));

            var sqlStmt = string.Format("INSERT INTO test ({0}) VALUES ({1})", sqlFieldsList, sqlValuesList);

            var dataObjects = Enumerable.Range(1, 100).Select(id => new Data { Foo = 1.0 / id, ID = id, Title = null });

            var sw = Stopwatch.StartNew();

            using (SqlConnection cnn = new SqlConnection(@"server=.\sqlexpress;database=test;integrated security=SSPI"))
            {
                cnn.Open();

                SqlCommand cmd = new SqlCommand(sqlStmt, cnn);
                cmd.Parameters.AddRange(sqlStuff.Select(x => x.SqlParameter).ToArray());

                dataObjects.ToList()
                    .ForEach(dto =>
                        {
                            sqlStuff.ForEach(x => x.SqlParameterAssignment(dto, x.SqlParameter));
                            cmd.ExecuteNonQuery();
                        });
            }


            Console.WriteLine("Done in: " + sw.Elapsed);
        }
    }

    public class Data
    {
        public string Title { get; set; }
        public int ID { get; set; }
        public double Foo { get; set; }
    }
}
于 2013-01-20T21:47:22.570 に答える
2

リフレクションは非常にパフォーマンスが高いと誰かが言っていましたが、実際にはプロファイラーでコードを実行していません。

あなたのコードを試してみましたが、実行に 18 ミリ秒 (65000 ティック) かかりました。データベースにデータを保存するのにかかる時間と比較して、かなり高速であると言わざるを得ません。しかし、それは本当に時間がかかりすぎるということについては正しいです。Byte[] を変換するときに tc.ConvertFrom を呼び出したときに、コードで 1 つの例外が発生したことがわかりました。clsParent から byte[] pImage を削除すると、ランタイムが 850 ティックに低下しました。

ここでのパフォーマンスの問題は、リフレクションではなく例外でした。

GetDBType を次のように自由に変更しました。

    private SqlDbType GetDBType(System.Type type)
    {
        SqlParameter param;
        System.ComponentModel.TypeConverter tc;
        param = new SqlParameter();
        tc = System.ComponentModel.TypeDescriptor.GetConverter(param.DbType);
        if (tc.CanConvertFrom(type))
        {
            param.DbType = (DbType)tc.ConvertFrom(type.Name);
        }
        else
        {
            switch (type.Name)
            {
                case "Char":
                    param.SqlDbType = SqlDbType.Char;
                    break;
                case "SByte":
                    param.SqlDbType = SqlDbType.SmallInt;
                    break;
                case "UInt16":
                    param.SqlDbType = SqlDbType.SmallInt;
                    break;
                case "UInt32":
                    param.SqlDbType = SqlDbType.Int;
                    break;
                case "UInt64":
                    param.SqlDbType = SqlDbType.Decimal;
                    break;
                case "Byte[]":
                    param.SqlDbType = SqlDbType.Binary;
                    break;

                default:
                    try
                    {
                        param.DbType = (DbType)tc.ConvertFrom(type.Name);
                    }
                    catch
                    {
                        // Some error handling
                    }
                    break;
            }
        }
        return param.SqlDbType;
    }

これがあなたの探求に役立つことを願っています。

于 2013-01-20T09:45:12.533 に答える
2

代替案ではありませんが、提案のみです。実行時に型が繰り返し格納される場合は、キャッシングを導入してリフレクション アプローチを微調整することができます。

代わりに:

PropertyInfo[] props = (t.GetType()).GetProperties();

次のキャッシングアプローチを試してください:

PropertyInfo[] props = GetProperties(type);

whereGetProperties(Type)は次のように実装されています:

private Dictionary<Type, PropertyInfo[]> propertyCache;
// ...
public PropertyInfo[] GetProperties(Type t)
{
    if (propertyCache.ContainsKey(t))
    {
        return propertyCache[t];
    }
    else
    {
        var propertyInfos = t.GetProperties();
        propertyCache[t] = propertyInfos;
        return propertyInfos;
    }
}

これは、Type.GetProperties() メソッド呼び出しをキャッシュする方法です。コードの他の部分のルックアップと同じアプローチを適用できます。たとえば、使用する場所param.DbType = (DbType)tc.ConvertFrom(type.Name);。if と switch を lookup に置き換えることもできます。しかし、このようなことを行う前に、実際にプロファイリングを行う必要があります。これはコードを非常に複雑にするため、正当な理由なしにこれを行うべきではありません。

于 2013-01-20T11:58:30.773 に答える
2

NHibernateまたはのような標準的な ORM を使用すると、一般的にメリットがあると思いますEntity Framework。どちらもクラスからリレーショナル データベースへの (カスタマイズ可能な) マッピングを行うことができ、NHibernate はすべての標準 DBMS システム間で完全な柔軟性を提供します。

Linqそうは言っても、後でコンパイルできる式を使用して、機能の一部を取得できるはずです。これにより、パフォーマンスが向上するはずです。

于 2013-01-20T08:26:47.687 に答える
0

ビルド後の処理を使用してコードを書き直さない限り、リフレクションに代わるものはありません。動的に発行される型/メソッド/デリゲートを準備し、それを戦略パターンとして自然に含めるためだけに使用する場合は、パフォーマンスの問題なしにリフレクションを使用できます。

于 2014-12-30T13:56:58.897 に答える