C# 反思的替代方案
我在泛型和反射方面的经验很少。从下面的示例中,我假设它需要花费太多的时间来执行。有没有一种方法可以让我在不使用反射的情况下完成以下任务 情景 我正在研究一种通用的方法。它获取传递给它的类的实例,并从所有属性生成SqlParameters。下面是名为“Store”的泛型方法的代码,以及另外一个将c#type转换为DbType的SqlDbType的方法C# 反思的替代方案,c#,generics,reflection,C#,Generics,Reflection,我在泛型和反射方面的经验很少。从下面的示例中,我假设它需要花费太多的时间来执行。有没有一种方法可以让我在不使用反射的情况下完成以下任务 情景 我正在研究一种通用的方法。它获取传递给它的类的实例,并从所有属性生成SqlParameters。下面是名为“Store”的泛型方法的代码,以及另外一个将c#type转换为DbType的SqlDbType的方法 List<SqlParameter> parameters = new List<SqlParameter>
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;
}
List参数=新列表();
公共T商店(T)
{
Type Type=t.GetType();
PropertyInfo[]props=(t.GetType()).GetProperties();
foreach(道具中的属性信息)
{
SqlParameter param=新的SqlParameter();
类型propType=p.PropertyType;
if(propType.BaseType.Name.Equals(“ValueType”)| | propType.BaseType.Name.Equals(“数组”))
{
param.SqlDbType=GetDBType(propType);//例如启用公共bool的{get;set;}或公共字节[]img{get;set;}
}
else if(propType.BaseType.Name.Equals(“对象”))
{
if(propType.Name.Equals(“String”)//用于字符串值
param.SqlDbType=GetDBType(propType);
其他的
{
动态d=p.GetValue(t,null);//用于引用,例如公共类A对象{get;set;}
仓库(d);
}
}
param.ParameterName=p.Name;
参数。添加(参数);
}
返回t;
}
//从类型获取DbType或SqlDbType的方法。。。
私有SqlDbType GetDBType(System.Type)
{
SqlParameter参数;
System.ComponentModel.TypeConverter tc;
param=新的SqlParameter();
tc=System.ComponentModel.TypeDescriptor.GetConverter(param.DbType);
if(tc.CanConvertFrom(类型))
{
param.DbType=(DbType)tc.ConvertFrom(type.Name);
}
其他的
{
//极力改变
尝试
{
param.DbType=(DbType)tc.ConvertFrom(type.Name);
}
捕获(例外e)
{
开关(类型.名称)
{
案例“Char”:
param.SqlDbType=SqlDbType.Char;
打破
案例“SByte”:
param.SqlDbType=SqlDbType.SmallInt;
打破
案例“UInt16”:
param.SqlDbType=SqlDbType.SmallInt;
打破
案例“UInt32”:
param.SqlDbType=SqlDbType.Int;
打破
案例“UInt64”:
param.SqlDbType=SqlDbType.Decimal;
打破
大小写“Byte[]”:
param.SqlDbType=SqlDbType.Binary;
打破
}
}
}
返回param.SqlDbType;
}
要调用我的方法,假设我有两个类,如下所示
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);
公共类clsParent
{
公共int-pID{get;set;}
公共字节[]pImage{get;set;}
公共字符串pName{get;set;}
}
和
公共类clsChild
{
公共十进制子ID{get;set;}
公共字符串childName{get;set;}
公共clsParent父项{get;set;}
}
这是一个电话
clsParent p=新的clsParent();
p、 pID=101;
p、 pImage=新字节[1000];
p、 pName=“约翰”;
clsChild c=新的clsChild();
c、 childId=1;
c、 childName=“a”;
c、 父母=p;
仓库(c);
我认为使用标准的ORM(如NHibernate
或实体框架)通常会让您受益匪浅。两者都可以实现(可定制)从类到关系数据库的映射,NHibernate为您提供了所有标准DBMS系统之间的完全灵活性
话虽如此,您应该能够通过使用Linq
表达式获得一些功能,这些表达式可以在以后进行编译;这会给你更好的表现 有人告诉您,反射的性能非常高,但您还没有真正通过分析器运行代码
我尝试了你的代码,运行了18毫秒(65000个滴答声),我必须说,与将数据保存到数据库中所需的时间相比,它相当快。
但你说得对,时间实在太多了。
我发现您的代码在转换字节[]时调用tc.ConvertFrom时引发了一个异常。
从clsParent中删除字节[]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;
}
我希望这将有助于您的探索。不是一个替代方案,而是一个建议:如果在运行时重复存储类型,您可以尝试通过引入一些缓存来调整反射方法
而不是:
PropertyInfo[] props = (t.GetType()).GetProperties();
尝试以下缓存方法:
PropertyInfo[] props = GetProperties(type);
其中GetProperties(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;
}
}
私有字典属性缓存;
// ...
公共属性信息[]获取属性(类型t)
{
if(propertyCache.ContainsKey(t))
{
返回属性缓存[t];
}
其他的
{
var propertyInfos=t.GetProperties();
propertyCache[t]=propertyInfos;
归还财产;
}
}
这就是缓存Type.GetProperties()方法调用的方式。您可以应用相同的方法,例如查找
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; }
}
}