两种方法提升subsonic ORMapping的速度
Subsonic是一个轻量级的ORMapping工具,比较适合短平快项目的开发。在和ado.net相比后发现 ado.net的速度是subsonic的10倍多,取10万条记录并封装成实体,ado.net 的平均速度是subsonic3 的 9倍。这个成本有点太高了。
SubSonic (milsecond) |
ADO.net (milisecond) |
subsonic/ADO.net |
|
10078 |
1140 |
8.840351 |
|
10078 |
1109 |
9.087466 |
|
10062 |
1140 |
8.826316 |
|
9953 |
1140 |
8.730702 |
|
10625 |
1125 |
9.444444 |
|
10062 |
1109 |
9.073039 |
|
10000 |
1109 |
9.017133 |
|
10000 |
1109 |
9.017133 |
|
10000 |
1125 |
8.888889 |
|
10265 |
1156 |
8.879758 |
|
101123 |
11262 |
8.979133
|
经过多次debug,最后发现封装成实体的过程中,每一行记录的每一列都会调用ProviderFactory的 new DbDataProvider(connectionString, providerName), 于是用dictionary缓存provider。
private static Dictionary<string, IDataProvider> SingleDProviderList;
public static IDataProvider GetProvider(string connectionString, string providerName)
{
bool providerExist;
if (!SingleDProviderList.Instance.ContainsKey(connectionString))
{
tempProvider = new DbDataProvider(connectionString, providerName);
SingleDProviderList.Instance.Add(connectionString, tempProvider);
}
providerExist = SingleDProviderList.Instance.TryGetValue(connectionString, out tempProvider);
return tempProvider;
}
这个改变让mapping的速度提升了3秒。
6倍多的差异还是有点高,因此继续想办法。 经过30多分钟的debug, 发现DataBase.cs 文件中的Load()
/// <summary>
/// Coerces an IDataReader to try and load an object using name/property matching
/// </summary>
public static void Load<T>(this IDataReader rdr, T item, List<string> ColumnNames) //mike added ColumnNames
{
Type iType = typeof(T);
PropertyInfo[] cachedProps = iType.GetProperties();
FieldInfo[] cachedFields = iType.GetFields();
PropertyInfo currentProp;
FieldInfo currentField = null;
// var typeObj = Activator.CreateInstance(iType);
for (int i = 0; i < rdr.FieldCount; i++)
{
string pName = rdr.GetName(i);
currentProp = cachedProps.SingleOrDefault(x => x.Name.Equals(pName, StringComparison.InvariantCultureIgnoreCase));
//mike if the property is null and ColumnNames has data then look in ColumnNames for match
if (currentProp == null && ColumnNames != null && ColumnNames.Count > i)
{
currentProp = cachedProps.First(x => x.Name == ColumnNames[i]);
}
//if the property is null, likely it's a Field
if (currentProp == null)
currentField = cachedFields.SingleOrDefault(x => x.Name.Equals(pName, StringComparison.InvariantCultureIgnoreCase));
if (currentProp != null && !DBNull.Value.Equals(rdr.GetValue(i)))
{
Type valueType = rdr.GetValue(i).GetType();
if (valueType == typeof(Boolean))
{
string value = rdr.GetValue(i).ToString();
currentProp.SetValue(item, value == "1" || value == "True", null);
}
else if (currentProp.PropertyType == typeof(Guid))
{
currentProp.SetValue(item, rdr.GetGuid(i), null);
}
else if (Objects.IsNullableEnum(currentProp.PropertyType))
{
var nullEnumObjectValue = Enum.ToObject(Nullable.GetUnderlyingType(currentProp.PropertyType), rdr.GetValue(i));
currentProp.SetValue(item, nullEnumObjectValue, null);
}
else if (currentProp.PropertyType.IsEnum)
{
var enumValue = Enum.ToObject(currentProp.PropertyType, rdr.GetValue(i));
currentProp.SetValue(item, enumValue, null);
}
else
{
var val = rdr.GetValue(i);
var valType = val.GetType();
//try to assign it
if (currentProp.PropertyType.IsAssignableFrom(valueType))
{
currentProp.SetValue(item, val, null);
}
else
{
currentProp.SetValue(item, val.ChangeTypeTo(currentProp.PropertyType), null);
}
}
}
else if (currentField != null && !DBNull.Value.Equals(rdr.GetValue(i)))
{
Type valueType = rdr.GetValue(i).GetType();
if (valueType == typeof(Boolean))
{
string value = rdr.GetValue(i).ToString();
currentField.SetValue(item, value == "1" || value == "True");
}
else if (currentField.FieldType == typeof(Guid))
{
currentField.SetValue(item, rdr.GetGuid(i));
}
else if (Objects.IsNullableEnum(currentField.FieldType))
{
var nullEnumObjectValue = Enum.ToObject(Nullable.GetUnderlyingType(currentField.FieldType), rdr.GetValue(i));
currentField.SetValue(item, nullEnumObjectValue);
}
else
currentField.SetValue(item, rdr.GetValue(i).ChangeTypeTo(valueType));
}
}
if (item is IActiveRecord) {
var arItem = (IActiveRecord)item;
arItem.SetIsLoaded(true);
arItem.SetIsNew(false);
}
}
这段代码对每行记录的每一列进行mapping,这是没有必要的,借鉴以往项目的mapping方法, 改写代码为
/// <summary>
/// Coerces an IDataReader to try and load an object using name/property matching
/// </summary>
public static void Load<T>(this IDataReader rdr, T item, List<string> ColumnNames) //mike added ColumnNames
{
Type iType = typeof(T);
PropertyInfo[] cachedProps = iType.GetProperties();
FieldInfo[] cachedFields = iType.GetFields();
PropertyInfo currentProp;
FieldInfo currentField = null;
// var typeObj = Activator.CreateInstance(iType);
if (item.GetType().GetInterface("IORMapping") != null)
((IORMapping)item).ORMapping(rdr);
else
{
#region origional mapping
for (int i = 0; i < rdr.FieldCount; i++)
{
string pName = rdr.GetName(i);
currentProp = cachedProps.SingleOrDefault(x => x.Name.Equals(pName, StringComparison.InvariantCultureIgnoreCase));
//mike if the property is null and ColumnNames has data then look in ColumnNames for match
if (currentProp == null && ColumnNames != null && ColumnNames.Count > i)
{
currentProp = cachedProps.First(x => x.Name == ColumnNames[i]);
}
//if the property is null, likely it's a Field
if (currentProp == null)
currentField = cachedFields.SingleOrDefault(x => x.Name.Equals(pName, StringComparison.InvariantCultureIgnoreCase));
if (currentProp != null && !DBNull.Value.Equals(rdr.GetValue(i)))
{
Type valueType = rdr.GetValue(i).GetType();
if (valueType == typeof(Boolean))
{
string value = rdr.GetValue(i).ToString();
currentProp.SetValue(item, value == "1" || value == "True", null);
}
else if (currentProp.PropertyType == typeof(Guid))
{
currentProp.SetValue(item, rdr.GetGuid(i), null);
}
else if (Objects.IsNullableEnum(currentProp.PropertyType))
{
var nullEnumObjectValue = Enum.ToObject(Nullable.GetUnderlyingType(currentProp.PropertyType), rdr.GetValue(i));
currentProp.SetValue(item, nullEnumObjectValue, null);
}
else if (currentProp.PropertyType.IsEnum)
{
var enumValue = Enum.ToObject(currentProp.PropertyType, rdr.GetValue(i));
currentProp.SetValue(item, enumValue, null);
}
else
{
var val = rdr.GetValue(i);
var valType = val.GetType();
//try to assign it
if (currentProp.PropertyType.IsAssignableFrom(valueType))
{
currentProp.SetValue(item, val, null);
}
else
{
currentProp.SetValue(item, val.ChangeTypeTo(currentProp.PropertyType), null);
}
}
}
else if (currentField != null && !DBNull.Value.Equals(rdr.GetValue(i)))
{
Type valueType = rdr.GetValue(i).GetType();
if (valueType == typeof(Boolean))
{
string value = rdr.GetValue(i).ToString();
currentField.SetValue(item, value == "1" || value == "True");
}
else if (currentField.FieldType == typeof(Guid))
{
currentField.SetValue(item, rdr.GetGuid(i));
}
else if (Objects.IsNullableEnum(currentField.FieldType))
{
var nullEnumObjectValue = Enum.ToObject(Nullable.GetUnderlyingType(currentField.FieldType), rdr.GetValue(i));
currentField.SetValue(item, nullEnumObjectValue);
}
else
currentField.SetValue(item, rdr.GetValue(i).ChangeTypeTo(valueType));
}
}
#endregion
}
if (item is IActiveRecord) {
var arItem = (IActiveRecord)item;
arItem.SetIsLoaded(true);
arItem.SetIsNew(false);
}
}
如果类实现了IORMapping接口,则用ORMapping 方法转换。
IORMapping接口定义为:
public interface IORMapping
{
void ORMapping(IDataRecord dataRecord);
}
修改ActiveRecord.tt, 为每个类添加IORMapping的继承,及ORMapping方法的实现
public void ORMapping(IDataRecord dataRecord)
{
SubSonic.IReadRecord readRecord = SubSonic.SqlReadRecord.GetIReadRecord();
readRecord.DataRecord = dataRecord;
<#
foreach(Column col in tbl.Columns)
{
#>
<#=col.Name#> = readRecord.get_<#=col.SysType.ToLower().Replace("[]","s")#>("<#=col.Name#>",null);
<#}#>
}
定义接口IReadRecord
namespace SubSonic
{
/// <summary>
/// 从IDataRecord中读取数据的接口
/// field 字段名
/// def 默认值
/// </summary>
public interface IReadRecord
{
/// <summary>
/// DataRecord接口
/// </summary>
IDataRecord DataRecord { get; set; }
string get_string(string field);
string get_string(string field, string def);
Int32 get_int(string field, Int32? def);
Int64 get_long(string field, Int32? def);
bool get_bool(string field, bool? def);
double get_double(string field, double? def);
DateTime get_datetime(string field, DateTime? def);
Decimal get_decimal(string field, Decimal? def);
Guid get_guid(string field, Guid? def);
byte? get_byte(string field, byte? def);
byte[] get_bytes(string field, byte[] def);
short? get_short(string field, short? def);
}
}
为sqlserver数据库实现接口IReadRecord
namespace SubSonic
{
/// <summary>
/// 实现IReadRecord接口,从IDataRecord中读取数据
/// </summary>
public class SqlReadRecord : IReadRecord
{
private static SqlReadRecord sqlReadRecord = null;
private IDataRecord dataRecord = null;
public SqlReadRecord()
{
}
public SqlReadRecord(IDataRecord dataRecord)
{
this.dataRecord = dataRecord;
}
public static IReadRecord GetIReadRecord()
{
if (sqlReadRecord == null)
sqlReadRecord = new SqlReadRecord();
return (IReadRecord)sqlReadRecord;
}
public IDataRecord DataRecord
{
get { return dataRecord; }
set { dataRecord = value; }
}
#region public methods
public string get_string(string AField)
{
try
{
return dataRecord[AField].ToString();
}
catch (Exception)
{
return "";
}
}
public string get_string(string AField, string ADef)
{
return get_string(AField);
}
public Int32 get_int(string AField, Int32? ADef)
{
try
{
return Convert.ToInt32(get_string(AField));
}
catch
{
return ADef == null ? 0 : ADef.Value;
}
}
public Int64 get_long(string AField, int? ADef)
{
try
{
return Convert.ToInt64(get_string(AField));
}
catch
{
return ADef == null ? 0 : ADef.Value;
}
}
public bool get_bool(string AField)
{
try
{
return Convert.ToBoolean(get_string(AField));
}
catch
{
return get_bool(AField, false);
}
}
public bool get_bool(string AField, bool? ADef)
{
try
{
return Convert.ToBoolean(get_string(AField));
}
catch
{
return ADef == null ? false : ADef.Value;
}
}
public double get_double(string AField, double? ADef)
{
try
{
return Convert.ToDouble(get_string(AField));
}
catch
{
return ADef == null ? Convert.ToDouble("0") : ADef.Value;
}
}
public DateTime get_datetime(string AField, DateTime? ADef)
{
try
{
return Convert.ToDateTime(get_string(AField));
}
catch
{
return ADef == null ? DateTime.Now : ADef.Value;
}
}
public Decimal get_decimal(string AField, Decimal? ADef)
{
try
{
return Convert.ToDecimal(get_string(AField));
}
catch
{
return ADef == null ? Convert.ToDecimal(0M) : ADef.Value;
}
}
public Guid get_guid(string AField, Guid? ADef)
{
try
{
return new Guid(get_string(AField));
}
catch
{
return new Guid();
}
}
//reload method
public Decimal get_decimal(string AField)
{
return get_decimal(AField, null);
}
public Guid get_guid(string AField)
{
return get_guid(AField, Guid.NewGuid());
}
public byte? get_byte(string AField, byte? ADef)
{
try
{
return Convert.ToByte(dataRecord[AField]);
}
catch
{
return ADef == null ? Convert.ToByte(' ') : ADef.Value;
}
}
public byte[] get_bytes(string AField, byte[] ADef)
{
System.Text.UTF8Encoding encoding = new System.Text.UTF8Encoding();
try
{
return encoding.GetBytes(get_string(AField));
}
catch
{
return ADef == null ? encoding.GetBytes("") : ADef;
}
}
public short? get_short(string AField, short? ADef)
{
try
{
return short.Parse(get_string(AField));
}
catch
{
return ADef == null ? 0 : ADef;
}
}
#endregion
}
}
经过以上处理,Ado.net 和subsonic相比,装箱的操作速度相差3.4倍左右。 尽管还是不够理想, 但是相比原来已经有了比较大的提升。
代码比较生硬,希望大家多提意见!