[本文篇幅較長,能夠經過目錄查看您感興趣的內容,或者下載格式良好的PDF版本文件查看] html
目錄 sql
1、ORM的"三國志" 2 數據庫
2、一決高下 4session
2.1.2,DataReader沒有DataSet快? 5app
記得我很早之前i(大概05年之前),剛聽到ORM這個詞的時候,就聽到有人在說ORM性能不高,要求性能的地方都是直接SQL的,後來談論ORM的人愈來愈多的時候,我也去關注了下,偶然間發現,尼瑪,一個文章表的實體類,竟然查詢的時候把Content(內容)字段也查詢出來了,這要是我查詢個文章列表,這些內容字段不只多餘,並且嚴重影響性能,爲啥不能只查詢我須要的字段到ORM?自此對ORM沒有好感,潛心研究SQL去了,將SQL封裝到一個XML文件程序再來調用,還能夠在運行時修改,別提多爽了,ORM,一邊去吧:)
到了06年,隨着這種寫SQL的方式,我發現一個項目裏面CRUD的SQL實在是太多了,特別是分頁,也得手寫SQL去處理,爲了高效率,分頁還要3種方式,第一頁直接用Top,最後一頁也用Top倒序處理,中間就得使用雙OrderBy處理了。這些SQL寫多了越寫越煩,因而再度去圍觀ORM,發現它的確大大減輕了我寫SQL的負擔,除了那個令我心煩的Content內容字段也被查詢出來的問題,不過我也學會了,單獨創建一個實體類,影射文章表的時候,不映射Content內容字段便可。很快發現,煩心的不止這個Content內容字段,若是要作到SQL那麼靈活,要讓系統更加高效,有不少地方實體類都不須要完整映射一個表的,一個表被影射出3-4個實體類是常見的事情,這讓系統的實體類數量迅速膨脹... 看來我不能忍受ORM的這個毛病了,必須爲ORM搞一個查詢的API,讓ORM能夠查詢指定的屬性,而不是從數據庫查詢所有的屬性數據出來,這就是OQL的雛形:
User u=new User();
u.Age=20;
OQL oql=new OQL(u);
oql.Select(u.UserID,u.Name,u.Sex).Where(u.Age);
List<User> list=EntityQuery<User>.QueryList(q);
上面是查詢年齡等於20的用戶的ID,Name,Sex 信息,固然User 實體類還有其它屬性,當前只須要這幾個屬性。
當時這個ORM查詢API--OQL很簡單,只能處理相等條件的查詢,可是可以只選取實體類的部分屬性,已經很好了,複雜點的查詢,結合在XML中寫SQL語句的方式解決,其它一些地方,經過數據控件,直接生成SQL語句去執行了,好比用數據控件來更新表單數據到數據庫。
小結一下我作CRUD的歷史,首先是對寫SQL樂此不彼,還發明瞭在XML文件中配置SQL而後映射到程序的功能:SQL-MAP,而後以爲這樣寫SQL儘管方便管理編寫查詢且能夠自動生成DAL代碼,可是項目裏面大量的SQL仍是致使工做量很大,因而拿起ORM併發明瞭查詢部分實體類屬性的查詢API:OQL;最後,以爲有些地方用ORM仍是麻煩,好比處理一個表單的CRUD,若是用ORM也得收集或者填充數據到實體類上,還不如直接發出SQL,因而又有了"數據控件"。
這樣,按照出現的順序,在2006年11月,一個具備SQL-MAP、ORM、Data Control功能的數據開發框架:PDF.NET Ver 1.0 誕生了!如今改名爲SOD框架,就是根據這個順序過程來的。
2008年,隨着.NET 3.5和VS2008發佈,MS的官方ORM框架Linq2Sql也一同發佈了,它採用Linq語法來查詢數據庫,也就是說Linq是MS的ORM查詢API。因爲Linq語法跟SQL語法有較大的區別,特別是Linq版本的左、又鏈接查詢語法,跟SQL的Join鏈接查詢,差別巨大,所以,學習Linq須要必定的成本。可是,LINQ to SQL是一個再也不更新的技術。其有不少不足之處,如,不能靈活的定義對象模型與數據表之間的映射、沒法擴展提供程序只能支持SQL Server等。 MS在同年,推出了Entity Framework,你們習慣的簡稱它爲EF,它能夠支持更多的數據庫。因而在2008年12月,我原來所在公司的項目經理急切的準備去嘗試它,用EF去開發一個用Oracle的系統。到了2009年8月,坊間已經處處流傳,Linq2Sql將死,EF是將來之星,咱們當時有一個客戶端項目,準備用EF來訪問SQLite。當時我任該項目的項目經理,因爲同事都不怎麼會Linq,更別提EF了,因而部分模塊用傳統的DataSet,部分用了EF for SQLite。結果項目作完,兩部分模塊進行對比,發現用EF的模塊,訪問速度很是的慢,查詢複雜一下直接要5秒以上纔出結果,對這些複雜的查詢不得不直接用SQL去重寫,而自此之後,咱們公司再也沒有人在項目中使用EF了,包括我也對EF比較失望,因而從新撿起個人PDF.NET,並在公司後來的諸多項目中大量推廣使用。
最近一兩年,坊間流行DDD開發,提倡Code First了,談論EF的人愈來愈多了,畢竟EF的查詢API--LINQ,是.NET的親生兒子,你們都愛上了它,那麼愛EF也是天然的。在EF 5.0的時候,它已經徹底支持Code First了,有人說如今的EF速度很快了,而我對此,仍是半信半疑,全力發展PDF.NET,如今它也支持Code First 開發模式了。
也是最近兩年,談論微型ORM的人也愈來愈多了,它們主打"靈活"、"高性能"兩張牌,查詢不用Linq,而是直接使用SQL或者變體的SQL語句,將結果直接映射成POCO實體類。因爲它們大都採用了Emit的方式根據DataReader動態生成實體類的映射代碼,因此這類微型ORM框架的速度接近手寫映射了。這類框架的表明就是Dapper、PetaPOCO.
這個問題由來已久,自ORM誕生那一天起就有很多人在疑問,甚至有人說,複雜查詢,就不應用ORM(見《爲何不推崇複雜的ORM 》,不只查詢語法不靈活,性能也底下。對此問題,我認爲無論是Linq,仍是OQL,或者是別的什麼ORM的查詢API,要作到SQL那麼靈活的確不可能,因此Hibernate還有HQL,EF還有ESQL,基於字符串的實體查詢語句,但我以爲既然都字符串了還不如直接SQL來的好;而對於複雜查詢效率低的問題,這個跟ORM沒有太大關係,複雜查詢哪怕用SQL寫,DB執行起來也低的,ORM只不過自動生成SQL讓DB去執行而已,問題可能出在某些ORM框架輸出的SQL並非開發人員預期的,也很難對它輸出的SQL語句進行優化,從而致使效率較低,但這種狀況並很少見,不是全部的查詢ORM輸出的SQL都很爛,某些SQL仍是優化的很好的,只不過優化權不開在發人員手中。另外,有的ORM語言能夠作到查詢透明化的,即按照你用ORM的預期去生成對應的SQL,不會多此一舉,PDF.NET的ORM查詢語言OQL就是這樣的。
那麼,對於通常的查詢,ORM有沒有DataSet快?
不少開發人員本身造的ORM輪子可能會有這個問題,依靠反射,將DataReader的數據讀取到實體類上,這種方式效率很低,確定比DataSet慢,如今,大部分紅熟的ORM框架,對此都改進了,一般的作法是使用委託、表達式樹、Emit來解決這個問題,Emit效率最高,表達式樹的解析會消耗很多資源,早期的EF,不知道是否是這個問題,也是慢得出奇;而採用委託方式,有所改進,但效率不是很高,若是結合緩存,那麼效率提高就較爲明顯了。
因爲大部分ORM框架都是採用DataReader來讀取數據的,而DataSet依賴於DataAdapter,自己DataReader就是比DataSet快的,因此只要解決了DataReader閱讀器賦值給實體類的效率問題,那麼這樣的ORM就有可能比DataSet要快的,何況,弱類型的DataSet,在查詢的時候會有2次查詢,第一次是查詢架構,第二次纔是加載數據,因此效率比較慢,所以,採用強類型的DataSet,可以改善這個問題,但要使用自定義的Sql查詢來填充強類型的DataSet的話,又很是慢,比DataSet慢了3倍多。
要讓ORM具備實用價值,那麼必須解決性能問題,通過前面的分析,咱們知道問題不在於DataReader自己是否比DataSet慢,而在於DataReader合適的數據讀取方式與讀取的值賦值給實體類的效率問題。前者已經有不少文章分析過,使用索引定位DataReader並進行類型化的讀取是效率的關鍵,在本文"3.3.3,手寫代碼"裏面作了最佳實踐的示例;後者的問題,咱們必須找到"三個火槍手",來看他們如何避開直接反射,最快的實現給對象賦值?就是咱們今天要討論的問題。
OK,咱們就以3個流行的框架,採用3種不一樣的方式實現的ORM ,來比較下看誰的效率最高。在比賽前,咱們先分別看看3種ORM的實現方式。
咱們首先得知,對一個屬性進行讀寫,能夠經過反射實現,以下面的代碼:
PropertyInfo.GetValue(source,null);
PropertyInfo.SetValue(target,Value ,null);
PropertyInfo 是對象的屬性信息對象,能夠經過反射拿到對象的每一個屬性的屬性信息對象,咱們能夠給它定義一個委託來分別對應屬性的讀寫:
public Func<object, object[], object> Getter { get; privateset; }
public Action<object, object, object[]> Setter { get; privateset; }
咱們將Getter委託綁定到PropertyInfo.GetValue 方法上,將Setter委託綁定到PropertyInfo.SetValue 方法上,那麼在使用的時候能夠像下面這個樣子:
CastProperty cp = mProperties[i];
if (cp.SourceProperty.Getter != null)
{
object Value = cp.SourceProperty.Getter(source, null); //PropertyInfo.GetValue(source,null);
if (cp.TargetProperty.Setter != null)
cp.TargetProperty.Setter(target, Value, null);// PropertyInfo.SetValue(target,Value ,null);
}
這段代碼來自我之前的文章《使用反射+緩存+委託,實現一個不一樣對象之間同名同類型屬性值的快速拷貝》,類型的全部屬性都已經事先緩存到了mProperties 數組中,這樣能夠在必定程度上改善反射的缺陷,加快屬性讀寫的速度。
可是,上面的方式不是最好的,緣由就在於PropertyInfo.GetValue、PropertyInfo.SetValue 很慢,由於它的參數和返回值都是 object 類型,會有類型檢查和類型轉換,所以,採用泛型委託纔是正道。
private MyFunc<T, P> GetValueDelegate;
private MyAction<T, P> SetValueDelegate;
public PropertyAccessor(Type type, string propertyName)
{
var propertyInfo = type.GetProperty(propertyName);
if (propertyInfo != null)
{
GetValueDelegate = (MyFunc<T, P>)Delegate.CreateDelegate(typeof(MyFunc<T, P>), propertyInfo.GetGetMethod());
SetValueDelegate = (MyAction<T, P>)Delegate.CreateDelegate(typeof(MyAction<T, P>), propertyInfo.GetSetMethod());
}
}
上面的代碼定義了GetValueDelegate 委託,指向屬性的 GetGetMethod()方法,定義SetValueDelegate,指向屬性的GetSetMethod()方法。有了這兩個泛型委託,咱們訪問一個屬性,就相似於下面這個樣子了:
string GetUserNameValue<User>(User instance)
{
return GetValueDelegate<User,string>(instance);
}
void SetUserNameValue<User,string>(User instance,string newValue)
{
SetValueDelegate<User,string>(instance,newValue);
}
但爲了讓咱們的方法更通用,再定義點參數和返回值是object類型的屬性讀寫方法:
publicobject GetValue(object instance)
{
return GetValueDelegate((T)instance);
}
publicvoid SetValue(object instance, object newValue)
{
SetValueDelegate((T)instance, (P)newValue);
}
實驗證實,儘管使用了這種方式對參數和返回值進行了類型轉換,但仍是要比前面的GetValue、SetValue方法要快得多。如今,將這段代碼封裝在泛型類 PropertyAccessor<T,P> 中,而後再將屬性的每一個GetValueDelegate、SetValueDelegate 緩存起來,那麼使用起來效率就很高了:
private INamedMemberAccessor FindAccessor(Type type, string memberName)
{
var key = type.FullName + memberName;
INamedMemberAccessor accessor;
accessorCache.TryGetValue(key, out accessor);
if (accessor == null)
{
var propertyInfo = type.GetProperty(memberName);
if (propertyInfo == null)
thrownew ArgumentException("實體類中沒有屬性名爲" + memberName + " 的屬性!");
accessor = Activator.CreateInstance(
typeof(PropertyAccessor<,>).MakeGenericType(type, propertyInfo.PropertyType)
, type
, memberName
) as INamedMemberAccessor;
accessorCache.Add(key, accessor);
}
return accessor;
}
有了這個方法,看起來讀寫一個屬性很快了,但將它直接放到"百萬級別"的數據查詢場景下,它仍是不那麼快,以前老趙有篇文章曾經說過,這個問題有"字典查詢開銷",不是說用了字典就必定快,所以,咱們真正用的時候還得作下處理,把它"暫存"起來,看下面的代碼:
publicstatic 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;
INamedMemberAccessor[] accessors = new INamedMemberAccessor[fcount];
DelegatedReflectionMemberAccessor drm = new DelegatedReflectionMemberAccessor();
for (int i = 0; i < fcount; i++)
{
accessors[i] = drm.FindAccessor<T>(reader.GetName(i));
}
do
{
T t = new T();
for (int i = 0; i < fcount; i++)
{
if (!reader.IsDBNull(i))
accessors[i].SetValue(t, reader.GetValue(i));
}
list.Add(t);
} while (reader.Read());
}
}
return list;
}
上面的代碼,每次查找到屬性訪問器以後,drm.FindAccessor<T>(reader.GetName(i)),把它按照順序位置存入一個數組中,在每次讀取DataReader的時候,按照數組索引拿到當前位置的屬性訪問器進行操做:
accessors[i].SetValue(t, reader.GetValue(i));
無疑,數組按照索引訪問,速度比字典要來得快的,字典每次得計算Key的哈希值而後再根據索引定位的。
就這樣,咱們採用泛型委託+反射+緩存的方式,終於實現了一個快速的ORM,PDF.NET Ver 5.0.3 加入了該特性,使得框架支持POCO實體類的效果更好了。
有關表達式樹的問題,我摘引下別人文章中的段落,原文在《表達式即編譯器》:
微軟在.NET 3.5中引入了LINQ。LINQ的關鍵部分之一(尤爲是在訪問數據庫等外部資源的時候)是將代碼表現爲表達式樹的概念。這種方法的可用領域很是普遍,例如咱們能夠這樣篩選數據:
var query = fromcustin customers
where cust.Region == "North"
select cust;
雖然從代碼上看不太出彩,可是它和下面使用Lambda表達式的代碼是徹底一致的:
var query = customers.Where(cust => cust.Region == "North");
LINQ 以及Where方法細節的關鍵之處,即是Lambda表達式。在LINQ to Object中,Where方法接受一個Func<T, bool>類型的參數——它是一個根據某個對象(T)返回true(表示包含該對象)或false(表示排除該對象)的委託。然而,對於數據庫這樣的數據源來講,Where方法接受的是Expression<Func<T, bool>>參數。它是一個表示測試規則的表達式樹,而不是一個委託。
這裏的關鍵點,在於咱們能夠構造本身的表達式樹來應對各類不一樣場景的需求——表達式樹還帶有編譯爲一個強類型委託的功能。這讓咱們可以在運行時輕鬆編寫IL。
-------引用完------------
不用說,根正苗紅的Linq2Sql,EntityFramework,都是基於表達式樹打造的ORM。如今,EF也開源了,感興趣的朋友能夠去看下它在DataReader讀取數據的時候是怎麼MAP到實體類的。
如今不少聲稱速度接近手寫的ORM框架,都利用了Emit技術,好比前面說的微型ORM表明Dapper。下面,咱們看看Dapper是怎麼具體使用Emit來讀寫實體類的。
///<summary>
/// Read the next grid of results
///</summary>
#if CSHARP30
public IEnumerable<T> Read<T>(bool buffered)
#else
public IEnumerable<T> Read<T>(bool buffered = true)
#endif
{
if (reader == null) thrownew ObjectDisposedException(GetType().FullName, "The reader has been disposed; this can happen after all data has been consumed");
if (consumed) thrownew InvalidOperationException("Query results must be consumed in the correct order, and each result can only be consumed once");
var typedIdentity = identity.ForGrid(typeof(T), gridIndex);
CacheInfo cache = GetCacheInfo(typedIdentity);
var deserializer = cache.Deserializer;
int hash = GetColumnHash(reader);
if (deserializer.Func == null || deserializer.Hash != hash)
{
deserializer = new DeserializerState(hash, GetDeserializer(typeof(T), reader, 0, -1, false));
cache.Deserializer = deserializer;
}
consumed = true;
var result = ReadDeferred<T>(gridIndex, deserializer.Func, typedIdentity);
return buffered ? result.ToList() : result;
}
在上面的方法中,引用了另一個方法 GetDeserializer(typeof(T), reader, 0, -1, false) ,再跟蹤下去,這個方法裏面大量使用Emit方式,根據實體類類型T和當前的DataReader,構造合適的代碼來快速讀取數據並賦值給實體類,代碼很是多,難讀難懂,感興趣的朋友本身慢慢去分析了。
聽說,泛型委託的效率低於表達式樹,表達式樹的效率接近Emit,那麼,使用了Emit,Dapper是否是最快的ORM呢?不能人云亦云,實踐是檢驗真理的惟一標準!
前面,有關ORM的實現原理說得差很少了,如今咱們來比試非ORM,ORM它們到底誰纔是"武林高手"。首先,對今天參賽選手分門別類:
MS派:
西部牛仔派:
草根派:
獨孤派:
首先,在比賽開始前,會由EF的Code First 功能自動建立一個Users表,而後由PDF.NET 插入100W行隨機的數據。最後,比賽分爲2個時段,
第一時段,串行比賽,各選手依次進入賽場比賽,總共比賽10次;
比賽內容爲,各選手從這100W行數據中查找身高大於1.6米的80後,對應的SQL以下:
SELECT UID,Sex,Height,Birthday,Name FROM Users Where Height >=1.6And Birthday>'1980-1-1
各選手根據這個比賽題目,盡情發揮,只要查詢到這些指定的數據便可。
第二時段,並行比賽,每次有3位選手一塊兒進行比賽,總共比賽100次,以平均成績論勝負;
比賽內容爲,查早身高在1.6-1.8之間的80後男性,對應的SQL以下:
SELECT UID,Sex,Height,Birthday,Name FROM Users
Where Height between1.6and1.8and sex=1And Birthday>'1980-1-1'
比賽場館由SqlServer 2008 贊助。
下面,咱們來看看各派系的招式:
不用解釋,你們都看得懂
int count = 0;
using (var dbef = new LocalDBContex())
{
var userQ = from user in dbef.Users
where user.Height >= 1.6 && user.Birthday>new DateTime(1980,1,1)
selectnew
{
UID = user.UID,
Sex = user.Sex,
Height = user.Height,
Birthday = user.Birthday,
Name = user.Name
};
var users = userQ.ToList();
count = users.Count;
}
這裏分爲2部分,前面是弱類型的DataSet,後面是強類型的DataSet
privatestaticvoid TestDataSet(string sql, AdoHelper db, System.Diagnostics.Stopwatch sw)
{
//System.Threading.Thread.Sleep(1000);
//DataSet
sw.Reset();
Console.Write("use DataSet,begin...");
sw.Start();
DataSet ds = db.ExecuteDataSet(sql,
CommandType.Text, new IDataParameter[] {
db.GetParameter("@height", 1.6),
db.GetParameter("@birthday", new DateTime(1980, 1, 1))
});
sw.Stop();
Console.WriteLine("end,row count:{0},used time(ms){1}", ds.Tables[0].Rows.Count, sw.ElapsedMilliseconds);
//System.Threading.Thread.Sleep(100);
//使用強類型的DataSet
sw.Reset();
Console.Write("use Typed DataSet,begin...");
sw.Start();
//
DataSet1 ds1 = new DataSet1();
SqlServer sqlServer = db as SqlServer;
sqlServer.ExecuteTypedDataSet(sql,
CommandType.Text, new IDataParameter[] {
db.GetParameter("@height", 1.6),
db.GetParameter("@birthday", new DateTime(1980, 1, 1))
}
,ds1
,"Users");
sw.Stop();
//下面的方式使用強類型DataSet,可是沒有制定查詢條件,可能數據量會很大,不通用
//DataSet1.UsersDataTable udt = new DataSet1.UsersDataTable();
//DataSet1TableAdapters.UsersTableAdapter uta = new DataSet1TableAdapters.UsersTableAdapter();
//uta.Fill(udt);
Console.WriteLine("end,row count:{0},used time(ms){1}", ds.Tables[0].Rows.Count, sw.ElapsedMilliseconds);
}
根據具體的SQL,手工寫DataReader的數據讀取代碼,賦值給實體類
//AdoHelper 格式化查詢
IList<UserPoco> list4 = db.GetList<UserPoco>(reader =>
{
returnnew UserPoco()
{
UID = reader.GetInt32(0),
Sex = reader.GetBoolean(1),//安全的作法應該判斷reader.IsDBNull(i)
Height = reader.GetFloat(2),
Birthday = reader.GetDateTime(3),
Name = reader.IsDBNull(0) ? null : reader.GetString(4)
};
},
"SELECT UID,Sex,Height,Birthday,Name FROM Users Where Height >={0} And Birthday>{1}",
1.6f,new DateTime(1980,1,1)
);
直接使用SQL查詢獲得DataReader,在實體類MAP的時候,此用泛型委託的方式處理,即文章開頭說明的原理
privatestaticvoid TestAdoHelperPOCO(string sql, AdoHelper db, System.Diagnostics.Stopwatch sw)
{
//System.Threading.Thread.Sleep(1000);
sw.Reset();
Console.Write("use PDF.NET AdoHelper POCO,begin...");
sw.Start();
List<UserPoco> list = AdoHelper.QueryList<UserPoco>(
db.ExecuteDataReader(sql, CommandType.Text,
new IDataParameter[] {
db.GetParameter("@height", 1.6),
db.GetParameter("@birthday", new DateTime(1980, 1, 1))
})
);
sw.Stop();
Console.WriteLine("end,row count:{0},used time(ms){1}", list.Count, sw.ElapsedMilliseconds);
}
3.3.5,
直接使用SQL,但將結果映射到PDF.NET的實體類
List<Table_User> list3 = EntityQuery<Table_User>.QueryList(
db.ExecuteDataReader(sql, CommandType.Text,new IDataParameter[] {
db.GetParameter("@height", 1.6),
db.GetParameter("@birthday", new DateTime(1980, 1, 1))
})
);
3.3.6,IDataRead實體類:在POCO實體類的基礎上,實現IDataRead接口,自定義DataReaer的讀取方式
privatestaticvoid TestEntityQueryByIDataRead(string sql, AdoHelper db, System.Diagnostics.Stopwatch sw)
{
//System.Threading.Thread.Sleep(1000);
sw.Reset();
Console.Write("use PDF.NET EntityQuery, with IDataRead class begin...");
sw.Start();
List<UserIDataRead> list3 = EntityQuery.QueryList<UserIDataRead>(
db.ExecuteDataReader(sql, CommandType.Text, new IDataParameter[] {
db.GetParameter("@height", 1.6),
db.GetParameter("@birthday", new DateTime(1980, 1, 1))
})
);
sw.Stop();
Console.WriteLine("end,row count:{0},used time(ms){1}", list3.Count, sw.ElapsedMilliseconds);
}
其中用到的實體類的定義以下:
publicclass UserIDataRead : ITable_User, PWMIS.Common.IReadData
{
//實現接口的屬性成員代碼略
publicvoid ReadData(System.Data.IDataReader reader, int fieldCount, string[] fieldNames)
{
for (int i = 0; i < fieldCount; i++)
{
if (reader.IsDBNull(i))
continue;
switch (fieldNames[i])
{
case"UID": this.UID = reader.GetInt32(i); break;
case"Sex": this.Sex = reader.GetBoolean(i); break;
case"Height": this.Height = reader.GetFloat(i); break;
case"Birthday": this.Birthday = reader.GetDateTime(i); break;
case"Name": this.Name = reader.GetString(i); break;
}
}
}
}
使用框架的ORM查詢API--OQL進行查詢
privatestaticvoid TestEntityQueryByOQL(AdoHelper db, System.Diagnostics.Stopwatch sw)
{
//System.Threading.Thread.Sleep(1000);
sw.Reset();
Console.Write("use PDF.NET OQL,begin...");
sw.Start();
Table_User u=new Table_User ();
OQL q = OQL.From(u)
.Select(u.UID, u.Sex, u.Birthday, u.Height, u.Name)
.Where(cmp => cmp.Property(u.Height) >= 1.6 & cmp.Comparer(u.Birthday,">",new DateTime(1980,1,1)))
.END;
List<Table_User> list3 = EntityQuery<Table_User>.QueryList(q, db);
sw.Stop();
Console.WriteLine("end,row count:{0},used time(ms){1}", list3.Count, sw.ElapsedMilliseconds);
}
使用OQL構造查詢表達式,可是將結果映射到一個POCO實體類中,使用了泛型委託
privatestaticvoid TestEntityQueryByPOCO_OQL(AdoHelper db, System.Diagnostics.Stopwatch sw)
{
//System.Threading.Thread.Sleep(1000);
sw.Reset();
Console.Write("use PDF.NET OQL with POCO,begin...");
sw.Start();
Table_User u = new Table_User();
OQL q = OQL.From(u)
.Select(u.UID, u.Sex, u.Birthday, u.Height, u.Name)
.Where(cmp => cmp.Property(u.Height) >= 1.6 & cmp.Comparer(u.Birthday, ">", new DateTime(1980, 1, 1)))
.END;
List<UserPoco> list3 = EntityQuery.QueryList<UserPoco>(q, db);
sw.Stop();
Console.WriteLine("end,row count:{0},used time(ms){1}", list3.Count, sw.ElapsedMilliseconds);
}
將SQL寫在XML配置文件中,並自動生成DAL代碼
首先看調用代碼:
privatestaticvoid TestSqlMap(System.Diagnostics.Stopwatch sw)
{
//System.Threading.Thread.Sleep(1000);
sw.Reset();
Console.Write("use PDF.NET SQL-MAP,begin...");
sw.Start();
DBQueryTest.SqlMapDAL.TestClassSqlServer tcs = new SqlMapDAL.TestClassSqlServer();
List<Table_User> list10 = tcs.QueryUser(1.6f,new DateTime(1980,1,1));
sw.Stop();
Console.WriteLine("end,row count:{0},used time(ms){1}", list10.Count, sw.ElapsedMilliseconds);
}
而後看看對應的DAL代碼:
//使用該程序前請先引用程序集:PWMIS.Core,而且下面定義的名稱空間前綴不要使用PWMIS,更多信息,請查看 http://www.pwmis.com/sqlmap
// ========================================================================
// Copyright(c) 2008-2010 公司名稱, All Rights Reserved.
// ========================================================================
using System;
using System.Data;
using System.Collections.Generic;
using PWMIS.DataMap.SqlMap;
using PWMIS.DataMap.Entity;
using PWMIS.Common;
namespace DBQueryTest.SqlMapDAL
{
///<summary>
///文件名:TestClassSqlServer.cs
///類 名:TestClassSqlServer
///版 本:1.0
///建立時間:2013/10/3 17:19:07
///用途描述:測試SQL-MAP
///其它信息:該文件由 PDF.NET Code Maker 自動生成,修改前請先備份!
///</summary>
publicpartialclass TestClassSqlServer
: DBMapper
{
///<summary>
///默認構造函數
///</summary>
public TestClassSqlServer()
{
Mapper.CommandClassName = "TestSqlServer";
//CurrentDataBase.DataBaseType=DataBase.enumDataBaseType.SqlServer;
Mapper.EmbedAssemblySource="DBQueryTest,DBQueryTest.SqlMap.config";//SQL-MAP文件嵌入的程序集名稱和資源名稱,若是有多個SQL-MAP文件建議在此指明。
}
///<summary>
///查詢指定身高的用戶
///</summary>
///<param name="height"></param>
///<returns></returns>
public List<LocalDB.Table_User> QueryUser(Single height, DateTime birthday)
{
//獲取命令信息
CommandInfo cmdInfo=Mapper.GetCommandInfo("QueryUser");
//參數賦值,推薦使用該種方式;
cmdInfo.DataParameters[0].Value = height;
cmdInfo.DataParameters[1].Value = birthday;
//參數賦值,使用命名方式;
//cmdInfo.SetParameterValue("@height", height);
//cmdInfo.SetParameterValue("@birthday", birthday);
//執行查詢
return EntityQuery<LocalDB.Table_User>.QueryList( CurrentDataBase.ExecuteReader(CurrentDataBase.ConnectionString, cmdInfo.CommandType, cmdInfo.CommandText , cmdInfo.DataParameters));
//
}//End Function
}//End Class
}//End NameSpace
SQL-MAP DAL
最後,看看對應的SQL的XML配置文件:
<?xml version="1.0" encoding="utf-8"?>
<!--
PWMIS SqlMap Ver 1.1.2 ,2006-11-22,http://www.pwmis.com/SqlMap/
Config by SqlMap Builder,Date:2013/10/3
-->
<SqlMap xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="SqlMap.xsd"
EmbedAssemblySource="DBQueryTest,DBQueryTest.SqlMap.config">
<Script Type="Access" Version="2000,2002,2003">
<CommandClass Name="TestAccess" Class="TestClassAccess" Description="測試SQL-MAP" Interface="">
<Select CommandName="QueryUser" CommandType="Text" Method="" Description="查詢指定身高的用戶" ResultClass="EntityList" ResultMap="LocalDB.Table_User">
<![CDATA[
SELECT UID,Sex,Height,Birthday,Name FROM Users Where Height >=#height:Single,Single# And Birthday>#birthday:DateTime#
]]>
</Select>
</CommandClass>
</Script>
<Script Type="SqlServer" Version="2008" ConnectionString="">
<CommandClass Name="TestSqlServer" Class="TestClassSqlServer" Description="測試SQL-MAP" Interface="">
<Select CommandName="QueryUser" CommandType="Text" Method="" Description="查詢指定身高的用戶" ResultClass="EntityList" ResultMap="LocalDB.Table_User">
<![CDATA[
SELECT UID,Sex,Height,Birthday,Name FROM Users Where Height >=#height:Single,Single# And Birthday>#birthday:DateTime#
]]>
</Select>
</CommandClass>
</Script>
</SqlMap>
使用Dapper 格式的SQL參數語法,將查詢結果映射到POCO實體類中
privatestaticvoid TestDapperORM(string sql, System.Diagnostics.Stopwatch sw)
{
//System.Threading.Thread.Sleep(1000);
sw.Reset();
Console.Write("use Dapper ORM,begin...");
sw.Start();
SqlConnection connection = new SqlConnection(MyDB.Instance.ConnectionString);
List<UserPoco> list6 = connection.Query<UserPoco>(sql, new { height = 1.6, birthday=new DateTime(1980,1,1) })
.ToList<UserPoco>();
sw.Stop();
Console.WriteLine("end,row count:{0},used time(ms){1}", list6.Count, sw.ElapsedMilliseconds);
}
由EF,PDF.NET OQL,Dapper ORM參加,使用Task開啓任務。下面是完整的並行測試代碼
class ParalleTest
{
/* query sql:
* SELECT UID,Sex,Height,Birthday,Name FROM Users
Where Height between 1.6 and 1.8 and sex=1 And Birthday>'1980-1-1'
*/
privatelong efTime = 0;
privatelong pdfTime = 0;
privatelong dapperTime = 0;
privateint batch = 100;
publicvoid StartTest()
{
Console.WriteLine("Paraller Test ,begin....");
for (int i = 0; i < batch; i++)
{
var task1 = Task.Factory.StartNew(() => TestEF());
var task2 = Task.Factory.StartNew(() => TestPDFNetOQL());
var task3 = Task.Factory.StartNew(() => TestDapperORM());
Task.WaitAll(task1, task2, task3);
Console.WriteLine("----tested No.{0}----------",i+1);
}
Console.WriteLine("EF used all time:{0}ms,avg time:{1}", efTime, efTime / batch);
Console.WriteLine("PDFNet OQL used all time:{0}ms,avg time:{1}", pdfTime, pdfTime/batch);
Console.WriteLine("Dapper ORM used all time:{0}ms,avg time:{1}", dapperTime, dapperTime/batch);
Console.WriteLine("Paraller Test OK!");
}
publicvoid TestEF()
{
System.Diagnostics.Stopwatch sw = new System.Diagnostics.Stopwatch();
sw.Start();
using (var dbef = new LocalDBContex())
{
var userQ = from user in dbef.Users
where user.Height >= 1.6 && user.Height <= 1.8//EF 沒有 Between?
&& user.Sex==true && user.Birthday > new DateTime(1980, 1, 1)
selectnew
{
UID = user.UID,
Sex = user.Sex,
Height = user.Height,
Birthday = user.Birthday,
Name = user.Name
};
var users = userQ.ToList();
}
sw.Stop();
Console.WriteLine("EF used time:{0}ms.",sw.ElapsedMilliseconds);
efTime += sw.ElapsedMilliseconds;
}
publicvoid TestPDFNetOQL()
{
System.Diagnostics.Stopwatch sw = new System.Diagnostics.Stopwatch();
sw.Start();
Table_User u = new Table_User() { Sex=true };
OQL q = OQL.From(u)
.Select(u.UID, u.Sex, u.Birthday, u.Height, u.Name)
.Where(cmp => cmp.Between(u.Height,1.6,1.8)
& cmp.EqualValue(u.Sex)
& cmp.Comparer(u.Birthday, ">", new DateTime(1980, 1, 1))
)
.END;
List<Table_User> list3 = EntityQuery<Table_User>.QueryList(q);
sw.Stop();
Console.WriteLine("PDFNet ORM(OQL) used time:{0}ms.", sw.ElapsedMilliseconds);
pdfTime += sw.ElapsedMilliseconds;
}
publicvoid TestDapperORM()
{
string sql = @"SELECT UID,Sex,Height,Birthday,Name FROM Users
Where Height between @P1 and @P2 and sex=@P3 And Birthday>@P4";
System.Diagnostics.Stopwatch sw = new System.Diagnostics.Stopwatch();
sw.Start();
SqlConnection connection = new SqlConnection(MyDB.Instance.ConnectionString);
List<UserPoco> list6 = connection.Query<UserPoco>(sql,
new { P1 = 1.6,P2=1.8,P3=true,P4 = new DateTime(1980, 1, 1) })
.ToList<UserPoco>();
sw.Stop();
Console.WriteLine("DapperORM used time:{0}ms.", sw.ElapsedMilliseconds);
dapperTime += sw.ElapsedMilliseconds;
}
}
爲了更加有效地測試,本次測試準備100W行隨機的數據,每條數據的屬性值都是隨機模擬的,包括姓名、年齡、性別、身高等,下面是具體代碼:
privatestaticvoid InitDataBase()
{
//利用EF CodeFirst 自動建立表
int count = 0;
var dbef = new LocalDBContex();
var tempUser= dbef.Users.Take(1).FirstOrDefault();
count= dbef.Users.Count();
dbef.Dispose();
Console.WriteLine("check database table [Users] have record count:{0}",count);
//若是沒有100萬條記錄,插入該數量的記錄
if (count < 1000000)
{
Console.WriteLine("insert 1000000 rows data...");
System.Diagnostics.Stopwatch sw = new System.Diagnostics.Stopwatch();
sw.Start();
//下面的db 等同於 MyDB.Instance ,它默認取最後一個鏈接配置
AdoHelper db = MyDB.GetDBHelperByConnectionName("default");
using (var session = db.OpenSession())
{
List<Table_User> list = new List<Table_User>();
int innerCount = 0;
for (int i = count; i < 1000000; i++)
{
Table_User user = new Table_User();
user.Name = Util.CreateUserName();
user.Height = Util.CreatePersonHeight();
user.Sex = Util.CreatePersonSex();
user.Birthday =Util.CreateBirthday();
list.Add(user);
innerCount++;
if (innerCount > 10000)
{
DataTable dt = EntityQueryAnonymous.EntitysToDataTable<Table_User>(list);
SqlServer.BulkCopy(dt, db.ConnectionString, user.GetTableName(), 10000);
list.Clear();
innerCount = 0;
Console.WriteLine("{0}:inserted 10000 rows .",DateTime.Now);
}
}
if (list.Count > 0)
{
innerCount=list.Count;
DataTable dt = EntityQueryAnonymous.EntitysToDataTable<Table_User>(list);
SqlServer.BulkCopy(dt, db.ConnectionString, list[0].GetTableName(), innerCount);
list.Clear();
Console.WriteLine("{0}:inserted {1} rows .", DateTime.Now, innerCount);
innerCount = 0;
}
}
Console.WriteLine("Init data used time:{0}ms",sw.ElapsedMilliseconds);
}
Console.WriteLine("check database ok.");
}
要使用它,得先準備一下配置文件了,本測試程序使用EF CodeFirst 功能,因此配置文件內容有所增長:
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<configSections>
<!-- For more information on Entity Framework configuration, visit http://go.microsoft.com/fwlink/?LinkID=237468 -->
<section name="entityFramework" type="System.Data.Entity.Internal.ConfigFile.EntityFrameworkSection, EntityFramework, Version=4.4.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" requirePermission="false"/>
</configSections>
<connectionStrings>
<add name="LocalDBContex" connectionString="Data Source=.;Initial Catalog=LocalDB;Persist Security Info=True;Integrated Security=SSPI;"
providerName="System.Data.SqlClient"/>
<add name="default" connectionString="Data Source=.;Initial Catalog=LocalDB;Integrated Security=True"
providerName="SqlServer"/>
<add name="DBQueryTest.Properties.Settings.LocalDBConnectionString"
connectionString="Data Source=.;Initial Catalog=LocalDB;Integrated Security=True"
providerName="System.Data.SqlClient"/>
</connectionStrings>
<startup>
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.0"/>
</startup>
<entityFramework>
<defaultConnectionFactory type="System.Data.Entity.Infrastructure.SqlConnectionFactory, EntityFramework"/>
</entityFramework>
</configuration>
系統配置中,要求使用SqlServer數據庫,且實現建立一個數據庫 LocalDB,若是數據庫不在本地機器上,須要修改鏈接字符串。
通過上面的準備,你是否是已經很急切的想知道誰是絕頂高手了?
比賽開始,第一輪,串行比賽,下面是比賽結果:
Entityframework,PDF.NET,Dapper Test.
Please config connectionStrings in App.config,if OK then continue.
check database table [Users] have record count:1000000
check database ok.
SELECT UID,Sex,Height,Birthday,Name FROM Users Where Height >=1.6 And Birthday>'1980-1-1'
-------------Testt No.1----------------
use EF CodeFirst,begin...end,row count:300135,used time(ms)1098
use DataSet,begin...end,row count:300135,used time(ms)2472
use Typed DataSet,begin...end,row count:300135,used time(ms)3427
use PDF.NET AdoHelper (hand code),begin...end,row count:300135,used time(ms)438
use PDF.NET AdoHelper POCO,begin...end,row count:300135,used time(ms)568
use PDF.NET EntityQuery,begin...end,row count:300135,used time(ms)538
use PDF.NET EntityQuery, with IDataRead class begin...end,row count:300135,used time(ms)432
use PDF.NET OQL,begin...end,row count:300135,used time(ms)781
use PDF.NET OQL with POCO,begin...end,row count:300135,used time(ms)639
use PDF.NET SQL-MAP,begin...end,row count:300135,used time(ms)577
use Dapper ORM,begin...end,row count:300135,used time(ms)1088
-------------Testt No.2----------------
use EF CodeFirst,begin...end,row count:300135,used time(ms)364
use DataSet,begin...end,row count:300135,used time(ms)1017
use Typed DataSet,begin...end,row count:300135,used time(ms)3168
use PDF.NET AdoHelper (hand code),begin...end,row count:300135,used time(ms)330
use PDF.NET AdoHelper POCO,begin...end,row count:300135,used time(ms)596
use PDF.NET EntityQuery,begin...end,row count:300135,used time(ms)555
use PDF.NET EntityQuery, with IDataRead class begin...end,row count:300135,used time(ms)445
use PDF.NET OQL,begin...end,row count:300135,used time(ms)555
use PDF.NET OQL with POCO,begin...end,row count:300135,used time(ms)588
use PDF.NET SQL-MAP,begin...end,row count:300135,used time(ms)559
use Dapper ORM,begin...end,row count:300135,used time(ms)534
-------------Testt No.3----------------
use EF CodeFirst,begin...end,row count:300135,used time(ms)346
use DataSet,begin...end,row count:300135,used time(ms)1051
use Typed DataSet,begin...end,row count:300135,used time(ms)3195
use PDF.NET AdoHelper (hand code),begin...end,row count:300135,used time(ms)305
use PDF.NET AdoHelper POCO,begin...end,row count:300135,used time(ms)557
use PDF.NET EntityQuery,begin...end,row count:300135,used time(ms)549
use PDF.NET EntityQuery, with IDataRead class begin...end,row count:300135,used time(ms)456
use PDF.NET OQL,begin...end,row count:300135,used time(ms)664
use PDF.NET OQL with POCO,begin...end,row count:300135,used time(ms)583
use PDF.NET SQL-MAP,begin...end,row count:300135,used time(ms)520
use Dapper ORM,begin...end,row count:300135,used time(ms)543
因爲篇幅緣由,這裏只貼出前3輪的比賽成績,比賽結果,EF竟然是匹黑馬,一雪前恥,速度接近手寫代碼,可是EF,Dapper,第一輪比賽居然輸給了PDF.NET OQL,而Dapper後面只是略勝,比起PDF.NET POCO,也是略勝,看來泛型委託仍是輸給了Emit,而EF,Dapper,它們在第一運行的時候,須要緩存代碼,因此較慢。屢次運行發現,EF僅這一次較慢,之後數次都很快,看來EF的代碼緩存策略,跟Dapper仍是不同。
可是,Dapper竟然輸給了EF,這是怎麼回事?莫非表達式樹比Emit還快?仍是EF將結果緩存了?使用SqlServer事務探察器,發現EF的確每次發出了查詢,沒有緩存數據。看來EF5.0的表達式樹可能真是效率有了很大提高,而且EF作了很好的優化,對EF取得的成果,不得不歎服!
下面是10次串行測試的數據表:
數據訪問 |
EF CodeFirst |
DataSet |
Typed |
PDF.NET |
PDF.NET |
PDF.NET |
PDF.NET |
PDF.NET |
PDF.NET |
PDF.NET |
Dapper |
1 |
374 |
1083 |
3220 |
305 |
564 |
584 |
449 |
598 |
585 |
601 |
516 |
2 |
344 |
1005 |
3188 |
314 |
549 |
545 |
445 |
578 |
574 |
564 |
493 |
3 |
341 |
1001 |
3110 |
287 |
527 |
584 |
436 |
624 |
743 |
560 |
486 |
4 |
342 |
1007 |
3380 |
342 |
574 |
575 |
539 |
758 |
655 |
633 |
638 |
5 |
424 |
1176 |
3275 |
318 |
659 |
855 |
553 |
684 |
797 |
679 |
647 |
6 |
433 |
1137 |
3156 |
283 |
536 |
588 |
441 |
546 |
628 |
540 |
487 |
7 |
356 |
1044 |
3671 |
301 |
537 |
536 |
452 |
524 |
603 |
516 |
479 |
8 |
367 |
975 |
3123 |
340 |
514 |
552 |
506 |
599 |
583 |
598 |
480 |
9 |
385 |
993 |
3213 |
306 |
538 |
612 |
473 |
569 |
596 |
545 |
521 |
10 |
483 |
987 |
3125 |
295 |
606 |
597 |
475 |
545 |
601 |
509 |
523 |
平均 |
384.9 |
1040.8 |
3246.1 |
309.1 |
560.4 |
602.8 |
476.9 |
602.5 |
636.5 |
574.5 |
527 |
因爲Type DataSet差別過大,在下面的圖中去掉了:
下面是平均值圖形:
下面是並行測試結果,程序共運行100次,每次三種ORM框架同時運行。因爲篇幅緣由,這裏只貼出最後三次的測試數據和最後計算的每種框架的性能平均數。
----tested No.97----------
DapperORM used time:435ms.
EF used time:462ms.
PDFNet ORM(OQL) used time:466ms.
----tested No.98----------
PDFNet ORM(OQL) used time:383ms.
DapperORM used time:389ms.
EF used time:451ms.
----tested No.99----------
PDFNet ORM(OQL) used time:390ms.
DapperORM used time:445ms.
EF used time:456ms.
----tested No.100----------
EF used all time:44462ms,avg time:444
PDFNet OQL used all time:38850ms,avg time:388
Dapper ORM used all time:39127ms,avg time:391
Paraller Test OK!
通過數次測試發現,三種框架表現都很優異,差距很小,其中PDF.NET OQL略微勝出,Dapper次之,EF稍慢。
ORM框架 |
EF Code First |
PDF.Net OQL |
Dapper ORM |
並行測試耗時百分比 |
35.45 |
32.18 |
32.35 |
串行測試,EF5.0 勝出,並行測試,PDF.NET勝出。在實際運行環境中,並行測試可能更好的反映問題。可是兩種測試場景,它們各自之見的性能並無"量"的差距,所以咱們選擇具體的成熟的ORM的時候,性能並非一個主要考察依據,而應該綜合分析比較,好比如下問題:
框架是咱們多年開發經驗的總結,在衆多流行的開發框架下,相信你選擇PDF.NET沒錯!
中國的軟件開源事業須要更多的人的關心和支持,PDF.NET爲此在2011年開始加入開源行列,並在2012年國慶前對最新版本進行開源,但願國人在基礎開發框架方面有更多的選擇,促進中國軟件事業的發展。但PDF.NET出身草根,它的發展須要您的更多呵護。若是您以爲它的確爲你的軟件開發起到了幫助,而且願意更進一步的支持框架的發展,請捐助PDF.NET,咱們將使用這筆資金來進行框架的宣傳、推廣、培訓活動;支付框架所在網站、源代碼託管服務;組織開發活動,獎勵開發團隊的貢獻。
有關抗震救災和捐助的詳細信息,請參看框架官網http://www.pwmis.com/sqlmap介紹,請以官網公佈的信息爲準!感謝全部已經捐助過和關心PDF.NET的朋友,指望他們的愛心可以讓更多的人知曉並讚賞!
本文中使用的測試程序下載: https://pwmis.codeplex.com/releases/view/113289
時間:2013年10月20日星期日