反思的替代品

我对Generics和Reflection的经验非常少。 我从以下示例中假设的是,它需要花费太多时间来执行。 有没有办法让我在不使用reflection的情况下完成以下操作。

SCENARIO我正在研究一种通用的方法。 它需要传递给它的类的实例,并从所有属性中生成SqlParameters。 以下是名为“Store”的generics方法的代码,以及另一种将c#type转换为DbType的SqlDbType的方法。

List parameters = new List(); public T Store(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); //eg 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 eg public ClassA obj{get;set;} Store(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(c); 

如果你想摆脱reflection,你可以在下面的代码中找到灵感。

这里对存储在数据库中的对象的所有访问以及sql属性值赋值都是由数据类型的运行时编译表达式构建来处理的。

假定保存值的表为test并假设字段名称与属性值相同。

对于每个属性,构造Mapping 。 它将包含一个包含数据库字段的FieldName ,一个应该正确插入SQL INSERT语句的SqlParameter (例如main ),最后如果包含已编译的操作,则可以获取输入T对象的实例并分配值为SqlParameters属性Value 。 构建这些映射的集合在Mapper类中完成。 代码内联以供解释。

最后, 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 { public Mapping(string fieldname, SqlParameter sqlParameter, Action assigner) { FieldName = fieldname; SqlParameter = sqlParameter; SqlParameterAssignment = assigner; } public string FieldName { get; private set; } public SqlParameter SqlParameter { get; private set; } public Action SqlParameterAssignment { get; private set; } } public class Mapper { public IEnumerable> 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>(assign, accessor, sqlParmAccessor).Compile(); yield return new Mapping(reflectionProperty.Name, // Table name sqlParm, // The constructed sql parameter assigner); // The action assigning from the input  } } } public static void Main(string[] args) { var sqlStuff = (new Mapper().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; } } } 

我认为您通常会受益于使用NHibernateEntity Framework等标准ORM。 两者都可以从类到关系数据库进行(可自定义)映射,NHibernate为所有标准DBMS系统提供了充分的灵活性。

话虽如此,您应该能够通过使用Linq表达式获得一些function,以后可以编译; 这应该会给你更好的表现。

有人告诉你,reflection确实很重要,但你还没有真正通过分析器运行你的代码。

我尝试了你的代码,并且需要18毫秒(65000个滴答)才能运行,我必须说它与将数据保存在数据库中所需的时间相比要快得多。 但你是对的,这真的太多了。 我发现你的代码在转换Byte []时调用了tc.ConvertFrom时引发了一个exception。 从clsParent中删除byte [] pImage,运行时间下降到850个tick。

这里的性能问题是exception,而不是reflection。

我冒昧地将你的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; } 

我希望这能帮助你完成任务。

不是替代方案,而只是一个建议:如果在运行时重复存储类型,您可以尝试通过引入一些缓存来调整reflection方法。

而不是:

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

尝试以下缓存方法:

 PropertyInfo[] props = GetProperties(type); 

其中GetProperties(Type)是这样实现的:

 private Dictionary 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); 。 也可以用查找替换ifs和switch。 但在你做这样的事情之前,你应该做一些分析。 它使代码变得很复杂,如果没有充分的理由你不应该这样做。

除非使用后置处理来重写代码,否则除了reflection之外别无选择。 如果仅使用reflection来准备动态发射类型/方法/委托并将其自然地包含在策略模式中,则可以使用reflection而不会出现任何性能问题。