前言

在各种ORM框架或者SQL映射框架(例如MyBatis,SOD框架之SQL-MAP功能)中,都有将查询的结果映射为内存对象的需求,包括映射到实体类、简单类型(例如Java的POJO,.NET的POCO)的对象。在.NET中,这个过程可以通过ADO.NET的DataReader对象来读取数据,然后将读取的数据映射到内存对象。本篇文章来讨论下不同方式的数据读取方式对性能的影响。

在写这篇文章之前,我在想现在都2020年全民奔小康了,除了微软官方的EF框架之外,各种ORM框架层出不穷,连笔者的SOD框架都诞生15年了,还有必要研究这么Low的问题吗?后来想了想,自己写博客主要是总结经验,记录问题分析过程的,虽然笔者在2013年就做过一个测试,写了《用事实说话,成熟的ORM性能不是瓶颈,灵活性不是问题:EF5.0、PDF.NET5.0、Dapper原理分析与测试手记》,但这篇文章已经过去6年多时间了,.NET框架都发展到跨平台的.NET Core了,现在Dapper更火了,基于Emit和表达式树的ORM轮子层出不穷,性能和易用性都不错,这些优秀的ORM框架获得了很高的关注,而SOD框架一直很低调,因为它一直没用采用Emit和表达式树技术,也没有采用反射,而是最原始的DataReader的非类型化数据读取方式,性能上可能比不上这些ORM框架,但会有多大的差异呢?SOD框架一直强调自己不仅仅是一个ORM框架,ORM仅仅是它的一个功能组件,不过大家既然都这么强调性能,于是决定重新测试一下DataReader的非类型化数据读取与类型化数据读取的性能差异,演示下正确使用两者的方式。

映射对象

下面的测试方法都是将数据库同样的数据通过DataReader读取出来映射到不同的对象中,本篇文章测试用来映射的对象一个是SOD框架的实体类,一个是普通的DTO对象,DTO是POCO的一种。下面是这两种对象的定义:

SOD实体对象类User的定义:

 public class User : EntityBase
{
public User()
{
TableName="Tb_User1"; IdentityName = "UserID";
PrimaryKeys.Add("UserID");
} /// <summary>
/// 设置字段名数组,如果不实现该方法,框架会自动反射获取到字段名数组,因此从效率考虑,建议实现该方法
/// </summary>
protected override void SetFieldNames()
{
PropertyNames = new string[] { "UserID", "Name", "Pwd", "RegistedDate" };
} /// <summary>
/// 获取实体类全局唯一标识;重写该方法,可以加快访问效率
/// </summary>
/// <returns></returns>
public override string GetGolbalEntityID()
{
//使用工具-》创建GUID 生成
return "F1344072-AB1E-4BCF-A28C-769C7C4AA06B";
} public int ID
{
get { return getProperty<int>("UserID"); }
set { setProperty("UserID", value); }
} public string Name
{
get { return getProperty<string>("Name"); }
set { setProperty("Name", value, ); }
} public string Pwd
{
get { return getProperty<string>("Pwd"); }
set { setProperty("Pwd", value, ); }
} public DateTime RegistedDate
{
get { return getProperty<DateTime>("RegistedDate"); }
set { setProperty("RegistedDate", value); }
} }

DTO类 UserDto的定义,跟实体类User完全一样的属性名称和属性类型:

 public class UserDto
{
public UserDto()
{
} public int UserID
{ get; set; } public string Name
{ get; set; } public string Pwd
{ get; set; } public DateTime RegistedDate
{ get; set; }
}

下面开始不同的查询方式测试。

1,手写查询映射

测试方案为将DataReader读取出来的数据手工逐一映射到一个POCO对象的属性上,例如下面映射到UserDto对象上。根据查询时候的SQL语句中指定的数据列的顺序和类型来使用DataReader是效率最高的方式,也就是DataReader类型化数据读取方法,使用字段索引而不是字段名称来读取数据的方式,如下面示例代码中的reader.GetInt32(0) :

//手写DataReader查询
private static long HandQuery(AdoHelper db, System.Diagnostics.Stopwatch watch)
{
watch.Restart();
string sql = "select UserID, Name, Pwd, RegistedDate from Tb_User1";
IList<UserDto> list = db.ExecuteMapper(sql).MapToList<UserDto>(reader => new UserDto
{
UserID = reader.IsDBNull()? default(int): reader.GetInt32(),
Name = reader.IsDBNull() ? default(string) : reader.GetString(),
Pwd = reader.IsDBNull() ? default(string) : reader.GetString(),
RegistedDate = reader.IsDBNull() ? default(DateTime) : reader.GetDateTime()
});
watch.Stop();
Console.WriteLine("HandQuery List (100000 item) 耗时:(ms)" + watch.ElapsedMilliseconds);
return watch.ElapsedMilliseconds;
}

代码说明:

方法的第一个参数db是SOD框架的AdoHelper对象,它是对各种数据库进行访问的一个提供程序类,封装了ADO.NET各种对象的访问,包括自动管理连接、执行查询、管理事务和记录日志等功能。在当前测试程序中这里它的实例对象是SQL Server访问提供程序。AdoHelper对象的ExecuteMapper方法将数据查询结果封装成一个DataReaderMapper对象,然后可以使用该对象的MapToList方法使用DataReader对象的类型化数据读取方法,将读取的值赋值给要映射的对象的属性,例如这里的UserDto对象。需要注意的是,在调用DataReader的类型化数据读取方法的时候,必须先判断当前位置的数据是否空数据(DBNull),否则会出错。例如上面的示例代码中,如果索引位置0的数据为空数据,则给UserDto对象的UserID属性赋值int类型的默认值0。MapToList方法会读取结果集的所有数据,读取完后自动关闭连接。

AdoHelper对象的封装比较简单,并且上面的查询会查询Tb_User1表的全部10万条数据,所以在讨论查询性能的时候,可以认为绝大部分时间都是在处理DataReader读取数据的问题,并且还采用了比字段名定位数据读取位置更高效的字段索引读取的方式,因此可以认为HandQuery方法的查询等同于最高效的手写查询方式。

2,映射数据到POCO对象

上面的手写测试代码看起来简单,但是必须清楚当前读取字段的索引位置和当前字段的数据类型,当SQL比较复杂或者SQL语句不在当前方法内设置的,那么要写这种代码就很困难了并且还容易出错,所以手写代码使用类型化数据读取和对象属性映射就是一个费力不讨好的“体力活”,除非对性能有极高要求否则一般人都不会这样直接处理查询映射。要解决这个问题我们可以使用反射、Emit或者表达式树来动态生成这种跟手写查询一样的代码。

SOD框架并没有使用上面的几种方式来模拟手写查询代码,而是使用DataReader的非类型化数据读取方式,再结合委托和缓存的方式来高效访问要映射的对象,例如当前要映射的POCO对象。这个过程可以通过AdoHelper对象的QueryList方法来完成,请看下面的示例代码:

 private static long QueryPOCO(AdoHelper db, System.Diagnostics.Stopwatch watch)
{
watch.Restart();
string sql = "select UserID, Name, Pwd, RegistedDate from Tb_User1";
IList<UserDto> list = db.QueryList<UserDto>(sql);
watch.Stop();
Console.WriteLine("QueryPOCO List (100000 item) 耗时:(ms)" + watch.ElapsedMilliseconds);
return watch.ElapsedMilliseconds;
}

代码说明:

使用AdoHelper对象的QueryList方法要求要映射的对象的属性名字和查询结果集的字段名必须严格一致,如果名字不一致,可以在SQL语句中使用字段别名。QueryList方法可以接受多个参数,除了第一个参数是要执行的SQL语句之外,其它参数可以是SQL语句中的“参数”。所以这个查询方式非常简单,只需要一行代码就可完成查询,类似Dapper的功能,所以这个功能算是SOD框架中的“微型ORM”。

下面是QueryList方法的定义和使用示例:

  /// <summary>
/// 根据SQL格式化串和可选的参数,直接查询结果并映射到POCO 对象
/// <example>
/// <code>
/// <![CDATA[
/// //假设UserPoco 对象跟 Table_User 表是映射的相同结构
/// AdoHelper dbLocal = new SqlServer();
/// dbLocal.ConnectionString = "Data Source=.;Initial Catalog=LocalDB;Integrated Security=True";
/// var list=dbLoal.QueryList<UserPoco>("SELECT UID,Name FROM Table_User WHERE Sex={0} And Height>={1:5.2}",1, 1.60M);
/// ]]>
/// </code>
/// </example>
/// </summary>
/// <typeparam name="T">POCO 对象类型</typeparam>
/// <param name="sqlFormat">SQL格式化串</param>
/// <param name="parameters">可选的参数</param>
/// <returns>POCO 对象列表</returns>
public List<T> QueryList<T>(string sqlFormat, params object[] parameters) where T : class, new()
{
IDataReader reader = FormatExecuteDataReader(sqlFormat, parameters);
return QueryList<T>(reader);
}

如上代码所示,方法第一个参数是一个SQL格式化字符串,在这个格式化字符串中可以有多个参数,就像string.Format方法的使用一样。例如上面方法的注释中查询条件Sex字段的参数和Height字段的参数,其中Height字段的参数的格式是精度为5,小数位数为2的浮点数。

上面的方法调用了QueryList泛型方法来处理DataReader对象读取的数据,下面看看它的实现:

/// <summary>
/// 采用快速的方法,将数据阅读器的结果映射到一个POCO类的列表上
/// </summary>
/// <typeparam name="T">POCO类类型</typeparam>
/// <param name="reader">抽象数据阅读器</param>
/// <returns>POCO类的列表</returns>
public static List<T> QueryList<T>(IDataReader reader) where T : class, new()
{
List<T> list = new List<T>();
using (reader)
{
if (reader.Read())
{
int fcount = reader.FieldCount;
//使用类型化委托读取正确的数据,解决MySQL等数据库可能的问题,感谢网友 @卖女孩的小肥羊 发现此问题
Dictionary<Type, MyFunc<IDataReader, int, object>> readerDelegates = DataReaderDelegate();
MyFunc<IDataReader, int, object>[] getDataMethods = new MyFunc<IDataReader, int, object>[fcount]; INamedMemberAccessor[] accessors = new INamedMemberAccessor[fcount];
DelegatedReflectionMemberAccessor accessorMethod = new DelegatedReflectionMemberAccessor();
for (int i = ; i < fcount; i++)
{
accessors[i] = accessorMethod.FindAccessor<T>(reader.GetName(i));
//修改成从POCO实体类的属性上来获取DataReader类型化数据访问的方法,而不是之前的DataReader 的字段的类型
if (!readerDelegates.TryGetValue(accessors[i].MemberType, out getDataMethods[i]))
{
getDataMethods[i] = (rd, ii) => rd.GetValue(ii);
}
} do
{
T t = new T();
for (int i = ; i < fcount; i++)
{
if (!reader.IsDBNull(i))
{
MyFunc<IDataReader, int, object> read = getDataMethods[i];
object value=read(reader,i);
accessors[i].SetValue(t, value);
} }
list.Add(t);
} while (reader.Read());
}
}
return list;
}

在上面的代码中的do循环之前,为要映射的POCO对象的每个属性访问器构建了一个MyFunc<IDataReader, int, object> 委托,该委托实际上来自于SOD框架预定义的一个处理DataReader类型化数据读取的委托,为了通用,上面这个委托方法返回值定义成了object类型,这样在实际调用的时候会进行“装箱”操作,也就是上面方法的代码:

 object value=read(reader,i);
accessors[i].SetValue(t, value);

之所以要进行装箱,是因为属性访问器方法SetValue需要一个object类型参数。

返回DataReader类型化数据读取方法委托的DataReaderDelegate方法定义如下:

 private static Dictionary<Type, MyFunc<IDataReader, int, object>> dictReaderDelegate = null;
private static Dictionary<Type, MyFunc<IDataReader, int, object>> DataReaderDelegate()
{
if (dictReaderDelegate == null)
{
Dictionary<Type, MyFunc<IDataReader, int, object>> dictReader = new Dictionary<Type, MyFunc<IDataReader, int, object>>();
dictReader.Add(typeof(int), (reader, i) => reader.GetInt32(i));
dictReader.Add(typeof(bool), (reader, i) => reader.GetBoolean(i));
dictReader.Add(typeof(byte), (reader, i) => reader.GetByte(i));
dictReader.Add(typeof(char), (reader, i) => reader.GetChar(i));
dictReader.Add(typeof(DateTime), (reader, i) => reader.GetDateTime(i));
dictReader.Add(typeof(decimal), (reader, i) => reader.GetDecimal(i));
dictReader.Add(typeof(double), (reader, i) => reader.GetDouble(i));
dictReader.Add(typeof(float), (reader, i) => reader.GetFloat(i));
dictReader.Add(typeof(Guid), (reader, i) => reader.GetGuid(i));
dictReader.Add(typeof(System.Int16), (reader, i) => reader.GetInt16(i));
dictReader.Add(typeof(System.Int64), (reader, i) => reader.GetInt64(i));
dictReader.Add(typeof(string), (reader, i) => reader.GetString(i));
dictReader.Add(typeof(object), (reader, i) => reader.GetValue(i)); dictReaderDelegate = dictReader;
}
return dictReaderDelegate;
}

3,SOD框架的DataReader非类型化数据读取

SOD框架的实体类查询方法直接使用了DataReader非类型化数据读取方式,一次性将一行数据读取到一个object[]对象数组中,SOD实体类将直接使用这个object[]对象数组,这使得数据映射过程可以大大简化代码,并且取得不错的效率。下面是测试实体类查询方法的示例代码:

private static long EntityQuery(AdoHelper db, System.Diagnostics.Stopwatch watch)
{
watch.Restart();
User user = new User();
OQL q = OQL.From(user).Select(user.ID, user.Name, user.Pwd, user.RegistedDate).END;
//q.Limit(5000);
var list = EntityQuery<User>.QueryList(q, db);
watch.Stop();
Console.WriteLine("SOD QueryList List (100000 item) 耗时:(ms)" + watch.ElapsedMilliseconds);
return watch.ElapsedMilliseconds;
}

下面是QueryList方法有关数据读取和映射的具体实现部分:

  /// <summary>
/// 根据数据阅读器对象,查询实体对象集合(注意查询完毕将自动释放该阅读器对象)
/// </summary>
/// <param name="reader">数据阅读器对象</param>
/// <param name="tableName">指定实体类要映射的表名字,默认不指定</param>
/// <returns>实体类集合</returns>
public static List<T> QueryList(System.Data.IDataReader reader,string tableName)
{
List<T> list = new List<T>();
if (reader == null)
return list;
using (reader)
{
if (reader.Read())
{
int fcount = reader.FieldCount;
string[] names = new string[fcount]; for (int i = ; i < fcount; i++)
names[i] = reader.GetName(i);
T t0 = new T();
if (!string.IsNullOrEmpty(tableName))
t0.MapNewTableName(tableName);
t0.PropertyNames = names;
do
{
object[] values = new object[fcount];
reader.GetValues(values); T t = (T)t0.Clone(false ); //t.PropertyNames = names;
t.PropertyValues = values; list.Add(t); } while (reader.Read()); }
}
return list;
}

上面的方法直接使用了DataReader对象的非类型化数据读取方法GetValues,将数据读取到values数组对象中。在当前QueryList方法中没用对DataReader对象读取的数据进行装箱,但是这种方式相比测试方式1的手写映射方式性能还是要低,猜测方法内部进行了复杂的处理,否则无法解释测试方式2测试代码中类型化数据读取后数据进行装箱后供数据访问器使用,测试2的测试性能仍然高于当前测试方式3,但不会有太大的性能差距。

4,类型化读取到数组元素中

如果DataReader对象类型化读取速度一定比非类型化数据读取方法GetValues快,那么可以尝试将类型化数据读取的值装箱到数组元素中,这样有可能提高SOD框架现有的QueryList方法的性能。下面模拟对QueryList方法进行修改,使得DataReader对象类型化读取到数组元素中。请看下面的示例代码:

 private static long EntityQuery2(AdoHelper db, System.Diagnostics.Stopwatch watch)
{
watch.Restart();
string sql = "select UserID, Name, Pwd, RegistedDate from Tb_User1"; string tableName = "";
User entity = new User();
IDataReader reader = db.ExecuteDataReader(sql);
List<User> list = new List<User>();
using (reader)
{
if (reader.Read())
{
int fcount = reader.FieldCount;
string[] names = new string[fcount]; for (int i = ; i < fcount; i++)
names[i] = reader.GetName(i);
User t0 = new User();
if (!string.IsNullOrEmpty(tableName))
t0.MapNewTableName(tableName);
//正式,下面放开
// t0.PropertyNames = names;
//
Action< int, object[]> readInt = ( i, o) => { if (reader.IsDBNull(i)) o[i] = DBNull.Value; else o[i] = reader.GetInt32(i); };
Action< int, object[]> readString = ( i, o) => { if (reader.IsDBNull(i)) o[i] = DBNull.Value; else o[i] = reader.GetString(i); };
Action< int, object[]> readDateTime = ( i, o) => { if (reader.IsDBNull(i)) o[i] = DBNull.Value; else o[i] = reader.GetDateTime(i); };
Action< int, object[]>[] readerActions = {
readInt,readString,readString,readDateTime
};
//
do
{
User item = (User)t0.Clone(false);
for (int i = ; i < readerActions.Length; i++)
{
readerActions[i]( i, item.PropertyValues);
} list.Add(item);
} while (reader.Read()); }
} //return list;
watch.Stop();
Console.WriteLine("EntityQuery2 List (10000 item) 耗时:(ms)" + watch.ElapsedMilliseconds);
return watch.ElapsedMilliseconds;
}

测试过程

以上4种测试方法准备完毕,下面准备测试数据,使用SQL Server Express LocalDB 创建一个数据库文件,在此文件数据库中创建一个User实体类对应的数据表,然后插入10万条数据,这个功能可以通过SOD框架下面的代码实现:

 private static void InitData(AdoHelper db, System.Diagnostics.Stopwatch watch)
{
//自动创建数据库和表
LocalDbContext context = new LocalDbContext();
Console.WriteLine("需要初始化数据吗?(Y/N) ");
string input= Console.ReadLine();
if (input.ToLower() != "y") return;
Console.WriteLine("正在初始化数据,请稍后。。。。");
context.TruncateTable<User>();
Console.WriteLine("...");
watch.Restart();
List<User> batchList = new List<User>();
for (int i = ; i < ; i++)
{
User zhang_yeye = new User() { ID = + i, Name = "zhang yeye" + i, Pwd = "pwd" + i ,RegistedDate =DateTime.Now };
//count += EntityQuery<User>.Instance.Insert(zhang_yeye);//采用泛型 EntityQuery 方式插入数据
batchList.Add(zhang_yeye);
}
watch.Stop();
Console.WriteLine("准备数据 耗时:(ms)" + watch.ElapsedMilliseconds); watch.Restart();
int count = EntityQuery<User>.Instance.QuickInsert(batchList);
watch.Stop();
Console.WriteLine("QuickInsert List (100000 item) 耗时:(ms)" + watch.ElapsedMilliseconds);
System.Threading.Thread.Sleep();
}

代码说明:

上面的方法中首先初始化数据库,通过DbContext对象自动创建数据表,并且通过TruncateTable 方法快速清除原来的测试数据。接着在内存中添加10万条数据,然后将它使用QuickInsert方法快速插入到数据库。

下面就可以给出完整的测试过程了,直接看代码:

static void Main(string[] args)
{
System.Diagnostics.Stopwatch watch = new System.Diagnostics.Stopwatch();
watch.Start();
AdoHelper db = MyDB.GetDBHelperByConnectionName("local");
InitData(db, watch); long[] useTime1 = new long[];
long[] useTime2 = new long[];
long[] useTime3 = new long[];
long[] useTime4 = new long[]; for (int i = ; i < ; i++)
{
useTime1[i]= HandQuery(db, watch);
System.Threading.Thread.Sleep(); //便于观察CPU、内存等资源变化 useTime2[i] = QueryPOCO(db, watch);
System.Threading.Thread.Sleep(); useTime3[i] = EntityQuery(db, watch);
System.Threading.Thread.Sleep(); useTime4[i] = EntityQuery2(db, watch);
System.Threading.Thread.Sleep(); Console.WriteLine("run test No.{0},sleep 1000 ms", i + );
Console.WriteLine();
}
//去掉热身的第一次
useTime1[] = ;
useTime2[] = ;
useTime3[] = ;
useTime4[] = ;
Console.WriteLine("Avg HandQuery={0} ms, \r\n Avg QueryPOCO={1} ms, \r\n Avg SOD EntityQuery={2} ms,\r\n Avg EntityQuery2={3} ms"
, useTime1.Average(),useTime2.Average(),useTime3.Average(), useTime4.Average()); Console.ReadLine();
}

测试过程去掉第一次循环测试的“热身”过程,计算剩余9次不同方式的平均执行时间,下面是在笔者笔记本电脑(Intel i7-4720HQ CPU 2.6GHz,12G RAM,普通硬盘)的测试结果:

需要初始化数据吗?(Y/N)
y
正在初始化数据,请稍后。。。。
...
准备数据 耗时:(ms)
QuickInsert List ( item) 耗时:(ms)
HandQuery List ( item) 耗时:(ms)
QueryPOCO List ( item) 耗时:(ms)
SOD QueryList List ( item) 耗时:(ms)
EntityQuery2 List ( item) 耗时:(ms)
run test No.,sleep ms HandQuery List ( item) 耗时:(ms)
QueryPOCO List ( item) 耗时:(ms)
SOD QueryList List ( item) 耗时:(ms)
EntityQuery2 List ( item) 耗时:(ms)
run test No.,sleep ms HandQuery List ( item) 耗时:(ms)
QueryPOCO List ( item) 耗时:(ms)
SOD QueryList List ( item) 耗时:(ms)
EntityQuery2 List ( item) 耗时:(ms)
run test No.,sleep ms HandQuery List ( item) 耗时:(ms)
QueryPOCO List ( item) 耗时:(ms)
SOD QueryList List ( item) 耗时:(ms)
EntityQuery2 List ( item) 耗时:(ms)
run test No.,sleep ms HandQuery List ( item) 耗时:(ms)
QueryPOCO List ( item) 耗时:(ms)
SOD QueryList List ( item) 耗时:(ms)
EntityQuery2 List ( item) 耗时:(ms)
run test No.,sleep ms HandQuery List ( item) 耗时:(ms)
QueryPOCO List ( item) 耗时:(ms)
SOD QueryList List ( item) 耗时:(ms)
EntityQuery2 List ( item) 耗时:(ms)
run test No.,sleep ms HandQuery List ( item) 耗时:(ms)
QueryPOCO List ( item) 耗时:(ms)
SOD QueryList List ( item) 耗时:(ms)
EntityQuery2 List ( item) 耗时:(ms)
run test No.,sleep ms HandQuery List ( item) 耗时:(ms)
QueryPOCO List ( item) 耗时:(ms)
SOD QueryList List ( item) 耗时:(ms)
EntityQuery2 List ( item) 耗时:(ms)
run test No.,sleep ms HandQuery List ( item) 耗时:(ms)
QueryPOCO List ( item) 耗时:(ms)
SOD QueryList List ( item) 耗时:(ms)
EntityQuery2 List ( item) 耗时:(ms)
run test No.,sleep ms HandQuery List ( item) 耗时:(ms)
QueryPOCO List ( item) 耗时:(ms)
SOD QueryList List ( item) 耗时:(ms)
EntityQuery2 List ( item) 耗时:(ms)
run test No.,sleep ms Avg HandQuery=155.9 ms,
Avg QueryPOCO=172.7 ms,
Avg SOD EntityQuery=186.8 ms,
Avg EntityQuery2=233.5 ms

测试结果说明,SOD框架的QueryPOCO“微型ORM”功能性能不错,虽然有数据装箱过程,但仍然接近手写代码数据映射的方式。SOD框架最常用的EntityQuery实体查询性能接近于QueryPOCO方式,而本次的测试方法4尝试将类型化数据读取到object数组对象也有装箱过程,性能却远低于EntityQuery实体查询方式。那么测试方法4的EntityQuery2方法中如果不装箱,直接采用读取指定位置数据为object类型能否性能明显提升呢?比如将方法中下面的代码:

Action< int, object[]> readInt = ( i, o) => { if (reader.IsDBNull(i)) o[i] = DBNull.Value; else o[i] = reader.GetInt32(i); };

修改为:

Action< int, object[]> readInt = ( i, o) => { if (reader.IsDBNull(i)) o[i] = DBNull.Value; else o[i] = reader.GetValue(i); };

经测试,修改前后性能没用明显的改善,两者性能基本相同。看来DataReader对象是否使用类型化数据读取对性能没用明显的影响,也就是读取的数据是否装箱对于ORM的数据映射性能没有明显影响,ORM查询过程中对性能影响最大的应该是数据库,而不是数据装箱。测试方法4还说明了,将DataReader的数据一次性读取到object[]对象数组中,性能要明显高于逐字段读取,不管是类型化读取还是非类型化读取。

这次测试也说明,SOD框架的ORM性能与手写代码查询映射的性能接近,没有明显的差距,SOD框架仍然是一个简单、高效、可靠的,值得使用的数据开发框架。本次测试的全部代码都在SOD项目解决方案的“SODTest”程序集项目中,源码仓库地址:https://github.com/znlgis/sod

------------------------------------------------------------------------------------------

最后值此元旦之际,向奋斗在一线的广大程序员朋友致敬!

为感谢广大SOD框架(原PDF.NET框架)用户朋友和所有支持、关心的朋友,让大家把“增删改查”的项目做的更快、更好,笔者花了一年多时间写了一本有关数据开发与架构实战的书:《SOD框架“企业级”应用数据架构实战》,目前出版社已经在校对阶段,预计年后将跟读者朋友见面,欢迎大家关注!

SOD框架高级用户QQ群:18215717

DataReader类型化数据读取与装箱性能研究的更多相关文章

  1. XML数据读取方式性能比较(一)

    原文:XML数据读取方式性能比较(一) 几个月来,疑被SOA,一直在和XML操作打交道,SQL差不多又忘光了.现在已经知道,至少有四种常用人XML数据操作方式(好像Java差不多),不过还没有实际比较 ...

  2. 如何在ADO中使用数据读取器(DataReader)读取数据

    DbDataReader类型(实现IDataReader接口)是从数据源获取信息最简单也最快速的方法. 数据读取器是只读向前的效据流.井且一次返回一条记录.因此.只有当你向数据源提交 Select 查 ...

  3. GPS数据读取与处理

    GPS数据读取与处理 GPS模块简介 SiRF芯片在2004年发布的最新的第三代芯片SiRFstar III(GSW 3.0/3.1),使得民用GPS芯片在性能方面登上了一个顶峰,灵敏度比以前的产品大 ...

  4. oracle中的数据读取与查找

    数据读取 首先数据块读入到Buffer Cache中,并将其放在LRU(Last Recently Used)链表的MRU(Most Recently Used)端,当需要再次访问该块时可以直接从bu ...

  5. java Scanner与BufferedReader读取键盘输入性能比较

    java  Scanner与BufferedReader读取键盘输入性能比较            1.Scanner和BufferedReader 性能比较 在java中常见的从键盘获取输入的方式有 ...

  6. Spark数据本地化-->如何达到性能调优的目的

    Spark数据本地化-->如何达到性能调优的目的 1.Spark数据的本地化:移动计算,而不是移动数据 2.Spark中的数据本地化级别: TaskSetManager 的 Locality L ...

  7. TensorFlow走过的坑之---数据读取和tf中batch的使用方法

    首先介绍数据读取问题,现在TensorFlow官方推荐的数据读取方法是使用tf.data.Dataset,具体的细节不在这里赘述,看官方文档更清楚,这里主要记录一下官方文档没有提到的坑,以示" ...

  8. tensorflow之数据读取探究(1)

    Tensorflow中之前主要用的数据读取方式主要有: 建立placeholder,然后使用feed_dict将数据feed进placeholder进行使用.使用这种方法十分灵活,可以一下子将所有数据 ...

  9. (转)在.NET程序运行过程中,什么是堆,什么是栈?什么情况下会在堆(栈)上分配数据?它们有性能上的区别吗?“结构”对象可能分配在堆上吗?什么情况下会发生,有什么需要注意的吗?

    转自:http://www.cnblogs.com/xiaoyao2011/archive/2011/09/09/2172427.html 在.NET程序运行过程中,什么是堆,什么是栈? 堆也就是托管 ...

随机推荐

  1. PS-蒙版的深入解析

    ps四大核心技术是什么?通道.蒙版.路径.选区 通道: 通道的详解,不过比较早的文章了. http://www.blueidea.com/tech/graph/2004/2056.asp PS通道快速 ...

  2. https搭建(自签名证书)

    博客搬家: https搭建(自签名证书) 上一篇博客探究了https(ssl)的原理,为了贯彻理论落实于实践的宗旨,本文将记录我搭建https的实操流程,使用Apache2+ubuntu+openss ...

  3. Codeforces_844

    A.统计字母个数. #include<bits/stdc++.h> using namespace std; string s; int n; map<char,int> mp ...

  4. apache 访问日志access_log 配置和解析 rotatelogs分割日志

    一.解析访问日志        apache 的访问日志记载着大量的信息,学会高效快捷的读出其中关键信息对我们的工作有极大帮助.       如果Apache的安装方式是默认安装,服务器一运行就会有两 ...

  5. 常用命令 find chmod

    find   path   -option   [   -print ]   [ -exec   -ok   command ]   {} \; find  [指定查找目录]  [查找规则]  [查找 ...

  6. php页面传递参数值几种方法总结

    2013-06-06 18:02 (分类:) 又搞了一个学期的php,就这样吧. php是一种服务器的脚本语言,他也是现在最为流行的WEB开发语言,下面我们来讲述一下几种上在php开发应用中常用的四种 ...

  7. 计算机网络 From Mr.Liu

    引言 本博客摘自Mr.Liu,原帖请点击这里. 感谢Mr.Liu,这个文章很充分的描述了计算机网络的核心知识点. 我还在学习中,所以没有进行自己的转述.图片因为是copy代码而没有获得,想看更详尽的, ...

  8. Android事件分发与责任链模式

    一.责任链模式 责任链模式是一种行为模式,为请求创建一个接收者的对象链.这样就避免,一个请求链接多个接收者的情况.进行外部解耦.类似于单向链表结构. 优点: 1. 降低耦合度.它将请求的发送者和接收者 ...

  9. Leetcode:235. 二叉搜索树的最近公共祖先

    Leetcode:235. 二叉搜索树的最近公共祖先 Leetcode:235. 二叉搜索树的最近公共祖先 Talk is cheap . Show me the code . /** * Defin ...

  10. 修改PR Cs6,PS Cs6,AU Cs6的启动界面

    转载来源:https://jingyan.baidu.com/article/09ea3ede00aeedc0aede39ca.html 百度了很多,只见PS Cs6的启动界面修改教程,PR,AU C ...