記錄一下petapoco官網博客的一些要點。這些博客記錄了PetaPoco是如何一步步改進的。mysql
第一個版本。git
PetaPoco-Improvementsgithub
http://www.toptensoftware.com/Articles/69/PetaPoco-Improvementsweb
若是運行查詢時不以「select」開頭,則petapoco會自動加上:sql
// Get a record var a=db.SingleOrDefault<article>("SELECT * FROM articles WHERE article_id=@0", 123); // can be shortened to this: Get a record var a=db.SingleOrDefault<article>("WHERE article_id=@0", 123);
增長了IsNew和Save方法:數據庫
若是如今有一個poco對象,要確認它是否在數據庫中仍是一個新記錄,能夠經過檢查它的主鍵是否被設置了默認值之外的值來判斷:數組
// Is this a new record if (db.IsNew(a)) { // Yes it is... }
相關的,有一個Save方法,來自動決定是插入記錄仍是更新記錄:緩存
// Save a new or existing record db.Save(a);
PetaPoco-Improvements II多線程
http://www.toptensoftware.com/Articles/70/PetaPoco-Improvements-II架構
改進列映射:PetaPoco支持[Ignore]屬性來指定某個屬性被忽略,如今添加了兩個新屬性:
[ExplicitColumns]:添加到POCO類來表示只有明確標出的列才進行映射
[Column]:添加到全部須要映射的列
緩存POCO類型信息
跟蹤最後一個SQL語句:公開了三個參數:
LastSQL
LastArgs:一個Object[]數組傳遞的全部參數
LastCommand:一個字符串,顯示SQL和參數
能夠在調試監視窗口監視LastCommand屬性。
異常處理:經過OnException方法來找出錯誤
一些錯誤:
Fetch方法返回一個List,而不是一個IEnumerable。
有些方法使用默認參數,沒法與舊版本C#兼容。
自動Select沒法檢測到以空白開始的select語句。
MySQL參數管理和用戶自定義鏈接字符串不能正常工做。
PetaPoco-T4 Template
http://www.toptensoftware.com/Articles/71/PetaPoco-T4-Template
增長了T4模板支持:
自動生成PetaPoco對象:
[TableName("articles")] [PrimaryKey("article_id")] [ExplicitColumns] public partial class article { [Column] public long article_id { get; set; } [Column] public long site_id { get; set; } [Column] public long user_id { get; set; } [Column] public DateTime? date_created { get; set; } [Column] public string title { get; set; } [Column] public string content { get; set; } [Column] public bool draft { get; set; } [Column] public long local_article_id { get; set; } [Column] public long? wip_article_id { get; set; } }
還能夠生成一些經常使用方法,好比Save(),IsNew(),Update(),SingleOrDefault()...能夠這樣使用:
var a = article.SingleOrDefault("WHERE article_id=@0", id); a.Save();
T4模板從PetaPoco.Database推導出一個類來描述數據庫自己,這個類有一個靜態方法GetInstance(),能夠用這個方法來獲得數據庫的實例,這樣來使用:
var records=jabDB.GetInstance().ExecuteScalar<long>("SELECT COUNT(*) FROM articles");
T4模板包括三個文件:
PetaPoco.Core.ttinclude:包括全部讀取數據庫架構的常規方法
PetaPoco.Generator.ttinclude:定義生成內容的實際模板
Records.tt:模板自己,包括一些設置,包括其餘兩個模板文件
一個Records.tt文件看起來是這樣的:
<#@ include file="PetaPoco.Core.ttinclude" #> <# // Settings ConnectionStringName = "jab"; Namespace = ConnectionStringName; DatabaseName = ConnectionStringName; string RepoName = DatabaseName + "DB"; bool GenerateOperations = true; // Load tables var tables = LoadTables(); #> <#@ include file="PetaPoco.Generator.ttinclude" #>
使用模板:
添加這三個文件到C#項目。
確認在app.config或web.config裏定義了數據庫鏈接字符串connection string and provider name
編輯Records.tt裏的ConnectionStringName爲實際的ConnectionStringName
保存Records.tt文件。T4模板會自動生成Records.cs文件,從數據庫中全部的表來生成POCO對象。
PetaPoco-NuGet Package
http://www.toptensoftware.com/Articles/73/PetaPoco-NuGet-Package
如今能夠從NuGet來安裝了。
PetaPoco-Paged Queries
http://www.toptensoftware.com/Articles/74/PetaPoco-Paged-Queries
支持分頁查詢,經過FetchPage方法:
public PagedFetch<T> FetchPage<T>(long page, long itemsPerPage, string sql, params object[] args) where T : new()
注意一點page參數是從0開始。返回值是一個PagedFetch對象:
// Results from paged request public class PagedFetch<T> where T:new() { public long CurrentPage { get; set; } public long ItemsPerPage { get; set; } public long TotalPages { get; set; } public long TotalItems { get; set; } public List<T> Items { get; set; } }
CurrentPage和ItemsPerPage只是反映傳遞過來的page和itemsPerPage參數。由於在構造分頁控件的時候須要用到。
注意返回的是一個List
背後的故事:
我老是以爲構建分頁查詢語句很乏味,這通常涉及到兩個不一樣但很相似的SQL語句:
1.分頁查詢自己
2.查詢全部記錄數量。
接下來說到如何處理MySQL和SQL Server分頁查詢的異同,爲了支持不一樣的數據庫,使用了不一樣的查詢語句。略過。
PetaPoco-Named Columns,Result Columns and int/long conversion
命名列:
如今能夠修改映射的列名稱,經過給[Column]屬性一個參數:
[PetaPoco.Column("article_id")] long id { get; set; }
注意,表的[PrimaryKey]屬性和其餘PetaPoco.Database 方法的primaryKeyName參數指的是列名,不是映射的屬性名。
結果列:
有時候運行查詢不只返回表中的列,還會有計算或鏈接的列。咱們須要查詢結果可以填充這些列,但在Update和Insert操做的時候忽略它們。
爲此目的增長了一個新的[ResultColumn]屬性。
假設你有一個categories表,你想可以檢索每一個類別的文章數量。
[TableName("categories")] [PrimaryKey("category_id")] [ExplicitColumns] public class category { [Column] public long category_id { get; set; } [Column] public string name { get; set; } [ResultColumn] public long article_count { get; set; } }
你仍然能夠像之前同樣執行Update和Insert方法,aritical_count屬性將被忽略。
var c = db.SingleOrDefault<category>("WHERE name=@0", "somecat"); c.name="newname"; db.Save(c);
可是你也能夠用它來捕獲join的結果:
var sql = new PetaPoco.Sql() .Append("SELECT categories.*, COUNT(article_id) as article_count") .Append("FROM categories") .Append("JOIN article_categories ON article_categories.category_id = categories.category_id") .Append("GROUP BY article_categories.category_id") .Append("ORDER BY categories.name"); foreach (var c in db.Fetch<category>(sql)) { Console.WriteLine("{0}\t{1}\t{2}", c.category_id, c.article_count, c.name); }
注意,填充一個[ResultColumn]你必須在你的select字句中顯式引用它。PetaPoco從自動生成的select語句中生成的列中不會包括它們(好比在上一個例子中的SingleOrDefault命令)。
自動long/int轉換
MySQL返回的count(*)是一個long,可是有時候把這個屬性聲明爲int更好些。這將在試圖定義這個屬性的時候致使異常。
如今PetaPoco能夠自動作這個轉換。當long轉換爲int的時候若是值超出範圍則拋出一個異常。
IDataReaders銷燬
上一個版本有個bug,Fetch或Query方法後data readers沒有被銷燬。如今已經修復了這個錯誤。
PetaPoco-NUnit Test Cases
http://www.toptensoftware.com/Articles/76/PetaPoco-NUnit-Test-Cases
若是要在生產環境中使用PetaPoco,很須要可以在一個更可控的方式中進行測試。
爲了可以用相同的一組測試來測試SQL Server和MySQL,設置了兩個鏈接字符串:"mysql"和"sqlserver",而後把這些字符串當作參數來運行測試。
[TestFixture("sqlserver")] [TestFixture("mysql")] public class Tests : AssertionHelper {
數據庫不須要任何特殊的方式配置測試用例好比建立一個名爲petapoco的表而後進行清理工做。有一個嵌入式的SQL資源腳原本進行初始化和清理.
測試用例自己簡單直接的使用每一個特性。對SQL builder功能來講也有測試用例。
主要測試均可以經過,沒有任何問題。有幾個預期的SQL Server的bug已經被修正(好比FetchPage方法有一些SQL語法錯誤和一些類型轉換問題)。
測試用例包含在github庫,但NuGet包中沒有。
PetaPoco-Value Conversions and UTC Times
http://www.toptensoftware.com/Articles/84/PetaPoco-Value-Conversions-and-UTC-Times
這篇文章已通過時了。
PetaPoco-T4 Template support for SQL Server
http://www.toptensoftware.com/Articles/78/PetaPoco-T4-Template-support-for-SQL-Server
增長了支持SQL Server的T4模板。
Some Minor PetaPoco Improvements
http://www.toptensoftware.com/Articles/89/Some-Minor-PetaPoco-Improvements
一些小的改進,讓分頁請求更加容易。
在使用時老是忘掉FetchPage仍是PagedFetch的返回類型。如今統一分頁方法和返回類型,如今都叫作Page。
接受Adam Schroder的建議,page number從1開始比從0開始更有意義。
之前的用法是這樣:
PagedFetch<user> m = user.FetchPage(page - 1, 30, "ORDER BY display_name");
如今這樣用:
Page<user> m = user.Page(page, 30, "ORDER BY display_name");
同時接受Adam Schroder的建議,如今有一個構造函數,接受一個connection string name和provider name做爲參數。
PetaPoco-Transaction Bug and Named Parameter Improvements
http://www.toptensoftware.com/Articles/90/PetaPoco-Transaction-Bug-and-Named-Parameter-Improvements
我剛注意到(已經修復)PetaPoco的支持事務中的bug,並對Database類增長了命名參數的支持。
PetaPoco的Sql builder一直支持命名參數的參數屬性:
sql.Append("WHERE name=@name", new { name="petapoco" } );
如今Database類也支持這個功能了:
var a=db.SingleOrDefault<person>("WHERE name=@name", new { name="petapoco" } ); PetaPoco-Custom mapping with the Mapper interface
http://www.toptensoftware.com/Articles/92/PetaPoco-Custom-mapping-with-the-Mapper-interface
使用Mapper接口自定義映射
最簡單的使用PetaPoco的方法是用聲明哪些屬性應該被映射到哪些列的屬性裝飾你的POCO對象。有時候,這不太實際或有些人以爲這太有侵入性了。因此我添加了一個聲明這些綁定的方法。
PetaPoco.Database類如今支持一個叫作Mapper的靜態屬性,經過它你可使用本身的列和表的映射信息。
首先,你須要提供一個PetaPoco.IMapper接口的實現:
public interface IMapper { void GetTableInfo(Type t, ref string tableName, ref string primaryKey); bool MapPropertyToColumn(PropertyInfo pi, ref string columnName, ref bool resultColumn); }
當GetTableInfo方法被調用時,tableName和primaryKey將被設置爲PetaPoco定義的默認值,若是你須要其餘的,修改便可,記得首先檢查類型。
相似的,MapPropertyToColumn方法-修改columnName和resultColumn的值來適應你的須要。容許映射返回true,或忽略它返回false。
一旦你實現了IMapper接口,你只須要設置PetaPoco的靜態屬性Mapper:
PetaPoco.Database.Mapper = new MyMapper();
注意這有一些限制,不過我以爲這是值得的。
一、這個mapper是被全部Database的實例共享的。PetaPoco在全局緩存這些列映射,因此不能爲不一樣的數據庫實例提供不一樣的映射。
二、只能安裝一個mapper。
PetaPoco-Smart Consecutive Clause Handling in SQL Builder
http://www.toptensoftware.com/Articles/91/PetaPoco-Smart-Consecutive-Clause-Handling-in-SQL-Builder
有時須要添加多個可選的Where字句。PetaPoco的連續子句處理能夠自動正確加入它們。
想象一下,你正在查詢一個數據庫,有兩個可選條件,一個開始日期,一個結束日期:
List<article> GetArticles(DateTime? start, DateTime? end) { var sql=new Sql(); if (start.HasValue) sql.Append("WHERE start_date>=@0", start.Value); if (end.HasValue) { if (start.HasValue) sql.Append("AND end_date<=@0", end.value); else sql.Append("WHERE end_data<@0", end.Value); } return article.Fetch(sql); }
計算第二個條件是where仍是and子句很乏味。如今PetaPoco能夠自動檢測連續的where子句並自動轉換後續的爲and子句。
List<article> GetArticles(DateTime? start, DateTime? end) { var sql=new Sql(); if (start.HasValue) sql.Append("WHERE start_date>=@0", start.Value); if (end.HasValue) sql.Append("WHERE end_data<@0", end.Value); return article.Fetch(sql); }
有一些注意事項,但很容易處理。
一、where子句必須是Sql片斷的第一個部分,因此下面的不會工做:
sql.Append("WHERE condition1 WHERE condition2");
但這樣的能夠:
sql.Append("WHERE condition1").Append("WHERE condition2");
二、Sql片斷必須相鄰,因此這樣的不會工做:
sql.Append("WHERE condition1").Append("OR condition2").Append("WHERE condition3");
三、你也許須要給個別條件加上括號來確保獲得正確的優先級:
sql.Append("WHERE x"); sql.Append("WHERE y OR Z");
應該寫成:
sql.Append("WHERE x"); sql.Append("WHERE (y OR z)");
這個功能也適用於Order By子句:
var sql=new Sql(); sql.Append("ORDER BY date"); sql.Append("ORDER BY name");
將生成:
ORDER BY date, name
PetaPoco-Performance Improvements using DynamicMethods
http://www.toptensoftware.com/Articles/93/PetaPoco-Performance-Improvements-using-DynamicMethods
使用動態方法的性能改進
PetaPoco已經比典型的Linq實現快。經過消除反射用動態生成的方法取代它,如今甚至更快-約20%。
在原始版本,PetaPoco一直使用反射來設置它建立的從數據庫中讀取的POCO對象的屬性。反射的優點是很容易使用,不足之處是有一點點慢。
在.NET裏可使用DynamicMethod和ILGenerator動態生成一段代碼。這是至關複雜的實現,須要瞭解MSIL。但它的速度更快,約20%。事實上我但願性能可以更好,因此也許不應給反射扣上速度慢的壞名聲。
因此這裏的想法很簡單-使用一個IDataReader並動態生成一個函數,它知道如何從data reader中讀取列值,並直接將它們分配給POCO相應的屬性。
爲了實現此目的,我作了一個公開可見的變化-即virtual Database。ConvertValue方法已廢棄,在IMapper接口使用一個新的GetValueConverter方法替代之。
public interface IMapper { void GetTableInfo(Type t, ref string tableName, ref string primaryKey); bool MapPropertyToColumn(PropertyInfo pi, ref string columnName, ref bool resultColumn); Func<object, object> GetValueConverter(PropertyInfo pi, Type SourceType); }
這麼作的主要目的是,提供了一個能夠在生成MSIL的時候採用的決策點。若是一個converter是調用它的MSIL須要的,則生成。若是不則省略。
添加動態方法生成增長了一些成本,.cs文件大小增長了120行左右。但我認爲值得。
PetaPoco-More Speed
http://www.toptensoftware.com/Articles/94/PetaPoco-More-Speed
我注意到Sam Saffron的Dapper項目……(Dapper也是一個很好的微型ORM框架)
首先我忘了昨天的帖子中提到的DynamicMethod支持的想法來自Sam Saffron在Stack Overflow上發表的帖子,如何壓榨更多的性能。
其次,Sam Saffron開源了Dapper項目,包括一個比較各類ORM的基準測試程序。固然我忍不住更新它來支持PetaPoco,很快就突破了幾個小瓶頸。一點點小調整,這是一些典型結果(就是說PetaPoco很快,確定比EF快了):
運行500次迭代載入一個帖子實體
手工編碼 65ms
PetaPoco(Fast) 67ms
PetaPoco(Normal) 78ms
Linq 2 SQL 841ms
EF 1286ms
我運行了PetaPoco的兩個測試模式,Normal和Fast
Normal - 全部的默認選項和啓用smarts
Fast - 全部的smarts,好比自動select子句,強制DateTime到UTC轉換,命名參數等都禁用
Normal模式是我很指望一般使用PetaPoco的方式,但禁用這些額外功能一直是可選的,當你真的試圖壓榨全部的性能的時候。
共享鏈接支持
主要的修復是能夠重用一個單一數據庫鏈接。在以前的版本中每一個查詢都會致使一個新的鏈接。經過公開OpenSharedConnection方法,你能夠預先調用它,全部隨後的查詢都將重用相同的鏈接。調用OpenSharedConnection和CloseSharedConnection是引用計數(reference counted),能夠嵌套。
爲一個HTTP請求的持續時間打開共享鏈接可能會是一個好主意。我尚未嘗試,but I going to try hooking this in with StructureMap's HttpContextScoped .
最後關於共享鏈接要注意的。PetaPoco一旦被銷燬會自動關閉共享鏈接。這意味着,若是調用OpenSharedConnection一次,and the Database is disposed everything should be cleaned up properly。
其餘可選行爲:
其餘功能是禁止一些PetaPoco行爲的能力:
-EnableAutoSelect,是否自動添加select子句,禁用時,如下沒法運行:
var a=db.SingleOrDefault<article>("WHERE article_id=@0", id);
-EnableNamedParams,是否處理Database類的參數,禁用時,如下沒法運行:
var a=db.SingleOrDefault<article>("WHERE article_id=@id", new { id=123 } );
-ForceDateTimesToUtc,禁用時,日期時間會返回數據庫提供的徹底相同的數據。啓用時,PetaPoco返回DateTimeKind.Utc類型。
禁用這些特性能夠帶來一點小的性能提高。若是他們形成了一些不良影響也提供了可能性來繞過這些特性。
其餘優化
還作了一些其餘的優化:
First(), FirstOrDefault(), Single() and SingleOrDefault()
這些方法基準測試程序並不使用,單我仍是提供了優化版本,返回一個記錄,保存創建一個列表,或單條記錄的enumerable。
用try/finally來代理using子句,PetaPoco內部使用一個可清理對象和using語句來確保鏈接被關閉。我已經更換了這些,用一個直接調用和finally塊來保存實例化一個額外的對象。
Benchmarking SubSonic's Slow Performance
http://www.toptensoftware.com/Articles/95/Benchmarking-SubSonic-s-Slow-Performance
本文主要是說Subsonic性能如何慢,以SingleOrDefault方法爲例。
PetaPoco - Support for SQL Server Compact Edition
http://www.toptensoftware.com/Articles/96/PetaPoco-Support-for-SQL-Server-Compact-Edition
支持SQL Server Compact版本。略過。
PetaPoco - PostgreSQL Support and More Improvements
http://www.toptensoftware.com/Articles/98/PetaPoco-PostgreSQL-Support-and-More-Improvements
支持PostgreSQL數據庫。略過。
PetaPoco - A couple of little tweaks
http://www.toptensoftware.com/Articles/99/PetaPoco-A-couple-of-little-tweaks
幾個小調整。
Sql.Builder
爲了使Sql.Builder更流暢,添加了一個新的靜態屬性返回一個Sql實例。之前寫法:
new Sql().Append("SELECT * FROM whatever");
如今寫法:
Sql.Builder.Append("SELECT * FROM _whatever");
這是一個微不足道的變換,但可讀性更好。
自動Select子句改進
此前,PetaPoco能夠自動轉換:
var a = db.SingleOrDefault<article>("WHERE id=@0", 123);
轉換爲:
var a = db.SingleOrDefault<article>("SELECT col1, col2, col3 FROM articles WHERE id=@0", 123);
如今它也會處理這個問題:
var a = db.SingleOrDefault<article>("FROM whatever WHERE id=@0", 123);
換言之,若是它看到一個語句以FROM開始,它只添加SELECT語句和列名列表,而不添加FROM子句。
T4模板改進
因爲NuGet的奇怪的特性,安裝文件順序爲字母倒序排列。致使T4模板在必須的文件以前安裝,隨之而來一堆錯誤。根據David Ebbo的建議Record.tt更名爲Database.tt。
PetaPoco - Working with Joins
http://www.toptensoftware.com/Articles/101/PetaPoco-Working-with-Joins
一般狀況下,使用PetaPoco有一個至關直接的映射,從C#類映射到數據庫。大部分時間這工做的很好,可是當你須要一個JOIN時-你須要比C#類的屬性更多的列來保存。
方法1-手動定義一個新的POCO類
第一個方法是簡單的建立一個新類來保存全部的JOIN後的列。好比須要一個文章標題列表和每篇文章的評論計數,SQL看起來像這樣:
SELECT articles.title, COUNT(comments.comment_id) as comment_count FROM articles LEFT JOIN comments ON comments.article_id = articles.article_id GROUP BY articles.article_id;
定義一個C#類來保存結果(注意屬性名稱匹配SQL的列名)
public class ArticleWithCommentCount { public string title { get; set; } public long comment_count { get; set; } }
使用新類型來查詢:
var articles = db.Fetch<ArticleWithCommentCount>(sql);
方法2-擴展示有的POCO對象
更有可能的是你已經有了一個POCO對象,包括了JOIN結果的大部分列,你只是想加入額外的幾個列。
與上個例子相同,你已經有了一個article對象,你只是想加入一個comment_count屬性,這就須要[ResultColumn]屬性了,添加一個新屬性到現有的類:
[ResultColumn] public long comment_count { get; set; }
經過聲明一個屬性爲[ResultColumn],若是結果集的列名匹配它將被自動填充,可是Update和Insert的時候會被忽略。
方法3-擴展T4模板中的POCO對象
上面的方法很好,若是你手動編寫本身的POCO類。可是若是你用T4模板來生成呢?你如何擴展這些類的屬性,從新運行模板不會覆蓋新屬性?答案在於T4模板生成的是partial class。
若是你不知道什麼是partial class……
仍是上面這個例子,添加一個新類,使用與T4模板生成的類相同的名字(確保名稱空間也要匹配)。聲明爲partial class,給任何JOIN的列添加[ResultColumn]屬性:
public partial class article { [ResultColumn] public long comment_count { get; set; } }
這是最後生成的查詢(使用PetaPoco的SQL builder)
var articles = db.Fetch<article>(PetaPoco.Sql.Builder .Append("SELECT articles.title, COUNT(comments.comment_id) as comment_count") .Append("FROM articles") .Append("LEFT JOIN comments ON comments.article_id = articles.article_id") .Append("GROUP BY articles.article_id") );
方法4-對象引用其餘POCO類
固然若是PetaPoco可以使用屬性對象引用來映射JOIN的表會很好-像一個徹底成熟的ORM同樣。但PetaPoco不能這樣作,也許永遠不會-這是不值得的複雜性,這也不是PetaPoco設計用來解決的問題。
更新 方法5-使用C#4.0的dynamic
從最初發布這篇文章以來,PetaPoco已經更新支持C#的dynamic expando objects。這提供了一個偉大的方法來處理JOIN,GROUP BY和其餘計算的查詢。
PetaPoco - Oracle Support and more...
http://www.toptensoftware.com/Articles/103/PetaPoco-Oracle-Support-and-more
Oracle支持。略過
Single和SingleOrDefault的主鍵版本
取一個單一記錄是很簡單的:
var a = db.SingleOrDefault<article>("WHERE article_id=@0", some_id);
固然最多見的狀況是根據主鍵取記錄,因此如今對Single和SingleOrDefault有一個新的重載:
var a = db.SingleOrDefault<article>(some_id);
做爲邊注,不要忘了若是你使用T4模板的話,上面的能夠更簡化:
// Normal version var a = article.SingleOrDefault("WHERE title=@0", "My Article"); // New simpler PK version var a = article.SingleOrDefault(some_id);
對於Joins的SQL builder方法
如今增長了兩個新的方法:InnerJoin和LeftJoin:
var sql = Sql.Builder .Select("*") .From("articles") .LeftJoin("comments").On("articles.article_id=comments.article_id");
枚舉屬性類型
此前若是你試圖使用有枚舉屬性的POCO對象會拋出一個異常。如今PetaPoco會正確的轉換整數列到枚舉屬性。
新OnExecutingCommand虛擬方法
OnExecutingCommand是一個新的虛擬方法,在PetaPoco命中數據庫以前被調用。
// Override this to log commands, or modify command before execution
public virtual void OnExecutingCommand(IDbCommand cmd) { }
你在兩種狀況下也許會用到它:
一、日誌-你能夠抓取SQL語句和參數值並記錄任何你須要的。
二、調整SQL-你能夠在返回以前調整SQL語句-也許爲某個特殊平臺的數據庫。
PetaPoco - Not So Poco!(or, adding support for dynamic)
http://www.toptensoftware.com/Articles/104/PetaPoco-Not-So-Poco-or-adding-support-for-dynamic
PetaPoco最初的靈感來自Massive-經過dynamic Expando objects返回一切。對於大多數狀況我以爲這比較麻煩,更喜歡強類型的類。可是有些時候支持dynamic也是有用的-特別是用於Join、Group By和其餘計算查詢時。
構造一個dynamic查詢只需使用現有的查詢方法,只是傳遞一個dynamic的泛型參數。返回的對象將爲每一個查詢返回的列對應一個屬性:
foreach (var a in db.Fetch<dynamic>("SELECT * FROM articles")) { Console.WriteLine("{0} - {1}", a.article_id, a.title); }
注意此時不支持自動添加SELECT子句,由於PetaPoco不知道表名。
你也能夠作Update、Insert、Delete操做可是你須要指定表名和要更新的主鍵。
// Create a new record dynamic a = new ExpandoObject(); a.title = "My New Article"; // Insert it db.Insert("articles", "article_id", a); // New record ID returned with a new property matching the primary key name Console.WriteLine("New record @0 inserted", a.article_id")
更新:
// Update var a = db.Single("SELECT * FROM articles WHERE article_id=@0", id); a.title="New Title"; db.Update("articles", "article_id", a); Delete()、Save()和IsNew()方法都相似。
有一個編譯指令,能夠禁用dynamic支持。由於.NET3.5不支持。
PetaPoco - Version 2.1.0
http://www.toptensoftware.com/Articles/105/PetaPoco-Version-2-1-0
支持dynamic
收集了是否應該包括支持dynamic的反饋意見後,我決定採用它。可是若是你運行較早版本的.NET也能夠禁用它。
關閉dynamic支持:
一、打開項目屬性
二、切換到「生成」選項卡
三、在「條件編譯符號」添加PETAPOCO_NO_DYNAMIC
包含空格的列(和其餘非標識符字符的)
之前版本的PetaPoco假設你的數據庫的任何列名都是有效的C#標識符名稱。若是列名中包含空格,固然會出錯,如今已經經過兩種方式來糾正這個錯誤:
一、PetaPoco能夠正確轉義SQL數據庫的分隔符,如[column], column
or "column"
二、T4模板清除列名稱以與C#兼容,使用Column屬性來設置列的DB name。
須要注意的是,若是你使用了dynamic的不兼容的列名,PetaPoco在這種狀況下並不試圖糾正它們。你仍將須要修改SQL來返回一個可用的列名。
var a=db.SingleOrDefault<dynamic>( "SELECT id, [col with spaces] as col_with_spaces FROM whatever WHERE id=@0", 23); Console.WriteLine(a.col_with_spaces);
或者,把返回的Expandos轉換爲dictionary:
var a=db.SingleOrDefault<dynamic>( "SELECT id, [col with spaces] FROM whatever WHERE id=@0", 23); Console.WriteLine((a as IDictionary<string, object>)["col with spaces"]);
Ansi String支持
DBA專家Rob Sullivan昨天指出,SQL Server在嘗試使用Unicode字符串的參數來查詢數據類型爲varchar的列的索引的時候,會致使嚴重的性能開銷。爲了解決這個問題須要把參數約束爲DbType.AnsiString。如今可使用新的AnsiString類的字符串參數:
var a = db.SingleOrDefault<article>("WHERE title=@0", new PetaPoco.AnsiString("blah")); Exists(PrimaryKey) and Delete(PrimaryKey)
能夠檢查是否存在一個主鍵的記錄。
if (db.Exists<article>(23)) db.Delete <article>(23);
PetaPoco - Incorporating Feedback
http://www.toptensoftware.com/Articles/106/PetaPoco-Incorporating-Feedback
支持無標識的主鍵列
在之前的版本中,PetaPoco假設主鍵列的值老是有數據庫生成和填充。但狀況並不是老是如此。如今PetaPoco的PrimaryKey屬性有了一個新的property - autoIncrement。
[TableName("subscribers")] [PrimaryKey("email", autoIncrement=false)] [ExplicitColumns] public partial class subscribers { [Column] public string email { get; set; } [Column] public string name { get; set; } }
autoIncrement默認設置爲true,你只須要指定不是自動生成主鍵的表便可。當autoIncrement設置爲false的時候PetaPoco能夠正確的插入記錄-忽略主鍵的值而不是試圖取回主鍵。
若是你沒有用這個屬性裝飾,Insert方法還有一個新的重載,可讓你指定是否自動生成主鍵。
public object Insert(string tableName, string primaryKeyName, bool autoIncrement, object poco)
你還能夠經過IMapper來指定這個屬性。由於GetTableInfo方法由於它的ref參數變得有點失控了,我把它改爲這樣:
void GetTableInfo(Type t, TableInfo ti); public class TableInfo { public string TableName { get; set; } public string PrimaryKey { get; set; } public bool AutoIncrement { get; set; } public string SequenceName { get; set; } }
不幸的是這是一個不向後兼容的改變。
有一個警告,對全部沒有自增主鍵列的表來講,IsNew()和Save()方法沒法工做,由於沒有辦法知道記錄是否來自數據庫。這種狀況下你應該知道是調用Insert()仍是Update()。
最後,T4模板已經更新爲自動生成autoIncrement屬性。這適用於SQL Server、SQL Server CE、MySQL和PostgreSQL,但不適用於Oracle。
架構調整
PetaPoco的T4模板能夠支持調整在生成最後一個POCO類以前導入的架構信息。這能夠用來重命名或忽略某些表和某些列。
// To ignore a table tables["tablename"].Ignore = true; // To change the class name of a table tables["tablename"].ClassName = "newname"; // To ignore a column tables["tablename"]["columnname"].Ignore = true; // To change the property name of a column tables["tablename"]["columnname"].PropertyName = "newname"; // To change the property type of a column tables["tablename"]["columnname"].PropertyType = "bool"; // To adjust autoincrement tables["tablename"]["columnname"].AutoIncrement = false;
調用LoadTables方法後在Database.tt中使用這個方法。能夠查看最新的Database.tt。
改善存儲過程支持
PetaPoco已經支持存儲過程-你必須關閉EnableAutoSelect讓它在查詢的時候起做用。我已經小小的修正了一下,以便PetaPoco不會在以Execute或Call開頭的語句前自動插入Select子句,這意味着你能夠調用存儲過程:
db.Query<type>("CALL storedproc") // MySQL stored proc db.Query<type>("EXECUTE stmt") // MySQL prepared statement db.Query<type>("EXECUTE storedproc") // SQL Server
這只是一個很小的改進,不支持out參數。
T4 Support for SQL Server Geography and Geometry
你能夠添加一個Microsoft.SqlServer.Types.dll引用。
PetaPoco - Single Column Value Requests
http://www.toptensoftware.com/Articles/107/PetaPoco-Single-Column-Value-Requests
單列值查詢
以前的版本只支持返回POCO對象,如今支持這樣的查詢:
foreach (var x in db.Query<long>("SELECT article_id FROM articles")) { Console.WriteLine("Article ID: {0}", x); }
這能夠支持全部的Type.IsValueType,字符串和byte數組
@字符轉義
PetaPoco使用@
select t.Id as '@@id' from dbo.MyTable as t where t.Name = @name for xml path('Item'), root ('Root'), type
Where子句的自動括號
SQL builder能夠自動附加連續的Where子句,好比:
sql.Where("cond1"); sql.Where("cond2");
會變成:
WHERE cond1 AND cond2
這挺好的,可是很容易致使不注意的操做法優先級錯誤。好比:
sql.Where("cond1 OR cond2"); sql.Where("cond3");
會變成:
cond1 OR cond2 AND cond3
老實說我並不知道實際的And和Or的優先級-我也不關心,可是我知道使用SQL builder的Where()方法會致使很容易出現這種問題。因此如今Where()方法會自動給參數加括號,會生成下面的語句:
(cond1 OR cond2) AND (cond3)
注意,這隻適用於Where()方法,當使用Append("WHERE cond")時無效。
PetaPoco - Version 3.0.0
http://www.toptensoftware.com/Articles/108/PetaPoco-Version-3-0-0
本文主要介紹3.0版本的改進,都在前面介紹過了。略過。
PetaPoco-Experimental-Multi-Poco-Queries
http://www.toptensoftware.com/Articles/111/PetaPoco-Experimental-Multi-Poco-Queries
首先這歸功於Sam Saffron的Dapper項目。PetaPoco的多POCO查詢支持與Dapper的很相似但PetaPoco的實現是至關不一樣的,列之間的分割點是不一樣的,它還能夠在POCO對象間自動猜想和分配對象的關係。
背景
多POCO查詢背後的想法是構造一個Join的SQL查詢,從每一個表返回的列能夠自動映射到POCO類。換句話說,不是第一個N列映射到第一個POCO,接下來的N列映射到另外一個……
用法
var sql = PetaPoco.Sql.Builder .Append("SELECT articles.*, authors.*") .Append("FROM articles") .Append("LEFT JOIN users ON articles.user_id = users.user_id"); var result = db.Query<article, user, article>( (a,u)=>{a.user=u; return a }, sql);
一些說明:
一、SQL查詢從兩個表返回列。
二、Query方法的前兩個泛型參數指定了擁有每行數據的POCO的類型。
三、第三個泛型參數是返回集合的類型-通常是第一個表的對象類型,但也能夠是其餘的。
四、Query方法須要它的第一個參數做爲回調委託,能夠用來鏈接兩個對象以前的關係。
因此在這個例子中,咱們返回一個IEnumerable
PetaPoco支持最多5個POCO類型,Fetch和Query方法也有變化。
選擇分割點
返回的列必須和Query()方法中的泛型參數的順序相同。好比第一個N列映射到T1,接下來N列映射到T2……
若是一個列名已經被映射到當前POCO類型它就被假定是一個分割點。想象一下這組列:
article_id, title, content, user_id, user_id, name
這些POCO:
class article { long article_id { get; set; } string title { get; set; } string content { get; set; } long user_id { get; set; } } class user { long user_id { get; set; } string name { get; set; } }
查詢相似這樣:
db.Query<article, user, article>( ... )
感興趣的是user_id。當映射這個結果集的時候,第一個user_id列將被映射到article,當看到第二個user_id的時候PetaPoco將意識到它已經被映射到article了,因而將其映射到user。
最後一種肯定分割點的方法是當一個列不存在於當前的POCO類型可是存在於下個POCO。注意若是一個列不存在於當前POCO也不存在與下個POCO,它將被忽略。
自動鏈接POCO
PetaPoco能夠在返回對象上自動猜想關係屬性並自動分配對象引用。
這種寫法:
var result = db.Query<article, user, article>( (a,u)=>{a.user=u; return a }, sql);
能夠寫成:
var result = db.Query<article, user>(sql);
兩點須要注意的:
一、第三個返回參數類型不是必需的。返回的結果集合永遠是T1類型。
二、設置對象關係的回調方法不是必需的。
很明顯的,作這項工做PetaPoco有一點小猜想,可是這是一個常見的狀況,我認爲這是值得的。要實現這個目的,T2到T5必需有一個屬性是和它左邊的類型是相同的類型。換句話說:
一、T1必需有一個T2的屬性
二、T1或T2必需有一個T3的屬性
三、T1或T2或T3必需有一個T4的屬性
……
同時,屬性是從右往左搜索的。因此若是T2和T3都有一個T4的屬性,那將使用T3的屬性。
結論和可用性
你可能須要多閱讀這篇文章幾回來理解這個新特性,可是一旦你習慣了我相信你會發現這是一個頗有用的補充。
PetaPoco - What's new in v4.0
http://www.toptensoftware.com/Articles/114/PetaPoco-What-s-new-in-v4-0
使用一個方法代替Transaction屬性
using(var scope = db.Transaction)
改爲這樣:
using(var scope = db.GetTransaction())
多POCO查詢
上一篇文章已經介紹過了。
另外能夠直接調用MultiPocoQuery方法:
IEnumerable<TRet> MultiPocoQuery<TRet>(Type[] types, object cb, string sql, params object[] args)
這個方法接受一個POCO數組做爲參數,而不是泛型參數。
支持IDbParameters做爲SQL arguments
PetaPoco如今支持直接傳遞IDbParameters對象到查詢中。若是PetaPoco沒有正確映射一個屬性的時候這很方便。
例如SQL Server不會將DbNull分配給VarBinary列觸發參數配置了正確的類型。如今能夠這樣作:
databaseQuery.Execute("insert into temp1 (t) values (@0)", new SqlParameter() { SqlDbType = SqlDbType.VarBinary, Value = DbNull.Value });
一個有趣的反作用是你還能夠從PetaPoco返回一個IDbParameters。IMapper接口從全局覆蓋了PetaPoco的默認參數映射功能。
在每一個基礎查詢禁用自動select生成的功能
PetaPoco作了一個合理的工做,猜想什麼時候應該自動插入Select子句-可是這不太完美並且有各類運行不正確的狀況。之前的版本你須要關閉EnableAutoSelect屬性,運行你的查詢而後再改回來。
如今你能夠用一個分號開頭來代表Select子句不該被插入。PetaPoco在查詢以前會移除分號。
// Leading semicolon in query will be removed... db.Query<mytype>(";WITH R as....");
T4模板改進-自定義插件清理功能
如今能夠替換標準的T4模板中用來清理表和列名的方法。在T4模板中,在調用LoadTables方法以前設置全局CleanUp屬性爲一個委託:
CleanUp = (x) => { return MyCleanUpFunction(x); }
T4模板改進-包括架構視圖和過濾的功能
T4模板如今能夠爲數據庫中的全部架構生成類,或者僅爲一個架構。若是隻包括一個特定架構的表,在調用LoadTables方法以前設置全局的SchemaName屬性。你也能夠用一個前綴來生成類:
SchemaName = "MySchema"; ClassPrefix = "myschema_";
若是你想要一個特定的主架構或其餘架構或多架構,設置多個不一樣SchemaName的Database.ttj便可。
你還能夠用T4模板生成類視圖:
IncludeViews = true;
ReaderWriterLockSlim多線程支持改進
PetaPoco使用ReaderWriterLockSlim來保護訪問共享數據來提升多線程性能-好比在web程序中。
支持protected構造函數和屬性
PetaPoco如今能夠訪問POCO的private和protected成員-包括private構造函數和屬性訪問器。
新的Page<>.Context屬性
在一些狀況下,我一直爲MVC視圖使用PetaPoco的強類型的Model對象,但須要一些額外的數據。不想使用ViewData或新建一個新的類,所以爲Page類添加了一個Context屬性。這能夠用來傳遞一些額外的上下文數據。
好比有一個頁面須要一個partial view來顯示網站頁的縮略圖。當有頁面顯示的時候是正常的,單若是列表是空的我想顯示一個根據上下文來顯示的「blank slate」信息。這多是「你尚未收藏」或「沒有更多網站了」或「你尚未喜歡任何網站」……
爲了處理這個問題,在controller中我設置了Context屬性能夠代表若是沒有數據的時候該如何顯示空白信息。
bug修復
PetaPoco - Mapping One-to-Many and Many-to-One Relationships :http://www.toptensoftware.com/Articles/115/PetaPoco-Mapping-One-to-Many-and-Many-to-One-Relationships
如今PetaPoco支持多POCO查詢。不少人問我PetaPoco如何或是否可以映射一對多和多對一的關係。
簡單的回答是,不會。但你能夠本身作,若是你想的話。
這就是說,請肯定你是否真的須要它。若是你只是作通常的Join查詢返回POCO那是不必的。多POCO查詢的重點是在捕獲Join結果的時候避免定義新的或擴展示有的POCO對象-不是真的要提供Instance Identity。
實例標識和廢棄POCO
那麼究竟當我說"Instance Identity"的時候是什麼意思呢?我意思是,若是從兩個或更多地方的查詢返回一個特定的記錄,則全部的狀況下都返回相同的POCO實例,或該POCO實例有惟一的標識。例如,若是你正在作一個articles和authors的Join查詢,若是兩個article有相同的author,那麼將引用相同的author的對象實例。
PetaPoco的多POCO查詢老是爲每一個行建立一個新的實例。所以在上面的例子中,每一行都將建立一個新的author對象。要得到正確的Instance Identity,咱們將最終丟棄重複的-因此不要把一對多和多對一做爲提升效率的辦法-只有在更準確的對象圖對你有用的時候再使用它。
Relator Callbacks
自動映射和簡單關係
當咱們寫一個relator callback時,咱們看看簡單的自動映射多POCO查詢看起來像這樣:
var posts = db.Fetch<post, author>(@" SELECT * FROM posts LEFT JOIN authors ON posts.author = authors.id ORDER BY posts.id ");
使用自動映射,第一個泛型參數是返回類型。所以這個例子將返回一個List
寫relator callback,看起來像這樣:
var posts = db.Fetch<post, author, post>( (p,a)=> { p.author_obj = a; return p; }, @"SELECT * FROM posts LEFT JOIN authors ON posts.author = authors.id ORDER BY posts.id ");
注意上面作了兩件事:
一、在泛型參數中有一個額外的<post,author,post>。最後一個參數代表了返回集合的類型。使用自定義的relator你能夠決定使用不一樣的類表明Join的行。
二、lambda表達式鏈接了post和author。
測試用例地址:https://github.com/toptensoftware/PetaPoco/blob/master/PetaPoco.Tests/MultiPocoTests.cs
多對一的關係
爲了實現多對一的關係,咱們須要作的是保持一個映射的RHS對象,並每次都重用相同的一個。
var authors = new Dictionary<long, author>(); var posts = db.Fetch<post, author, post>( (p, a) => { // Get existing author object author aExisting; if (authors.TryGetValue(a.id, out aExisting)) a = aExisting; else authors.Add(a.id, a); // Wire up objects p.author_obj = a; return p; }, "SELECT * FROM posts LEFT JOIN authors ON posts.author = authors.id ORDER BY posts.id" );
實現是很簡單的:尋找之前的相同author實例,若是找到了就使用它的引用。若是沒有找到就提供一個並存儲起來供之後使用。
固然若是你須要在不少地方這樣作很快就會乏味。因此包裝一個helper:
class PostAuthorRelator { // A dictionary of known authors Dictionary<long, author> authors = new Dictionary<long, author>(); public post MapIt(post p, author a) { // Get existing author object, or if not found store this one author aExisting; if (authors.TryGetValue(a.id, out aExisting)) a = aExisting; else authors.Add(a.id, a); // Wire up objects p.author_obj = a; return p; } }
如今能夠這樣運行查詢:
var posts = db.Fetch<post, author, post>( new PostAuthorRelator().MapIt, "SELECT * FROM posts LEFT JOIN authors ON posts.author = authors.id ORDER BY posts.id" );
好多了,繼續……
一對多關係
在一對多關係,咱們想從RHS獲得的對象集合來填充每一個LHS對象。好比上面的例子,咱們想要一個author列表,每一個都有一個做者的文章集合。
SELECT * FROM authors LEFT JOIN posts ON posts.author = authors.id ORDER BY posts.id
使用這個查詢咱們會獲得LHS結果集中的重複的author信息,文章信息在右面。左邊的author須要去重獲得單一的POCO,文章須要爲每一個author收集成一個list。
返回的集合事實上會比數據庫返回的行有更少的項,因此relator callback須要可以hold back當前的author直到檢測到一個新的author。
爲了支持這點,PetaPoco容許一個relator callback來返回null表示還沒爲當前記錄準備好。爲了清空最後的記錄PetaPoco將在結果集末尾最後調用一次relator,爲全部的參數傳遞null(但它只能作這個,若是relator在結果集中至少返回一次-relator不用檢查null參數更簡單了)
看一下一對多的relator:
class AuthorPostRelator { public author current; public author MapIt(author a, post p) { // Terminating call. Since we can return null from this function // we need to be ready for PetaPoco to callback later with null // parameters if (a == null) return current; // Is this the same author as the current one we're processing if (current != null && current.id == a.id) { // Yes, just add this post to the current author's collection of posts current.posts.Add(p); // Return null to indicate we're not done with this author yet return null; } // This is a different author to the current one, or this is the // first time through and we don't have an author yet // Save the current author var prev = current; // Setup the new current author current = a; current.posts = new List<post>(); current.posts.Add(p); // Return the now populated previous author (or null if first time through) return prev; } }
上面的註釋很清楚的代表發生了什麼-咱們只是簡單的保存author直到咱們檢測到一個新的而後添加文章列表到當前的author對象,這樣來用:
var authors = db.Fetch<author, post, author>( new AuthorPostRelator().MapIt, "SELECT * FROM authors LEFT JOIN posts ON posts.author = authors.id ORDER BY posts.id" );
雙向映射,映射兩個以上的對象
在上面的例子中,我要麼把author映射到post要麼添加post到author列表。relator沒有理由作不到同時使用這兩種方式建立的引用。我沒有包括這個例子只是爲了證實這是可行的可是你懂得。
最後,上面的例子只是展現瞭如何聯繫兩個對象。若是你鏈接更多的表你須要作更多複雜的工做,單只是上面例子的擴展。
PetaPoco-Partial Record Updates
http://www.toptensoftware.com/Articles/116/PetaPoco-Partial-Record-Updates
默認狀況下,PetaPoco更新記錄的時候會更新全部的被映射到POCO屬性的列。根據不一樣的使用狀況,一般是能夠的但也許無心中覆蓋了已經被其餘事務更新過的字段。
例如:
var u = user.SingleOrDefault("WHERE name=@0", username); u.last_login = DateTime.UtcNow; u.Update();
問題是全部的字段都被更新了-用戶名、郵件地址、密碼,全部的都重寫到數據庫。若是隻是更新last_login字段會更好一些。咱們能夠這樣寫:
u.Update(new string[] { "last_login" });
或相似的:
db.Update<user>(u, new string[] { "last_login" });
全部的Update方法如今都有一個新的重載,接受一個新參數,定義爲IEnumerable
這是有用的除非跟蹤哪些列須要更新很是痛苦。T4模板生成的POCO類如今能夠自動跟蹤修改的屬性。爲了啓用它,Database.tt中有一個設置選項:
TrackModifiedColumns = true;
當設置爲false的時候,POCO屬性以舊方式實現:
[Column] string title { get; set; }
當爲true時,它生成跟蹤修改列的訪問器方法;
[Column] public string title { get { return _title; } set { _title = value; MarkColumnModified("title"); } } string _title;
基本的Record類有一些新方法:
private Dictionary<string,bool> ModifiedColumns; private void OnLoaded() { ModifiedColumns = new Dictionary<string,bool>(); } protected void MarkColumnModified(string column_name) { if (ModifiedColumns!=null) ModifiedColumns[column_name]=true; } public int Update() { if (ModifiedColumns==null) return repo.Update(this); int retv = repo.Update(this, ModifiedColumns.Keys); ModifiedColumns.Clear(); return retv; } public void Save() { if (repo.IsNew(this)) repo.Insert(this); else Update(); }
解釋一下:
一、OnLoaded是一個新方法,PetaPoco在從數據庫填充任何POCO實現後都將當即調用它。
二、MarkColumnsModified-簡單的記錄OnLoaded被調用後有值被更改的列名。
三、執行Update時Update和Save已經更新爲傳遞一個修改列的list給PetaPoco。
有一點須要注意的,set訪問器,它們標誌了列被修改 實際上值並無改變。這是故意的,有兩個緣由:
一、它確保值不管如何確實被髮送到數據庫,幫助保持數據一致性。
二、這意味着查詢數據庫不依賴於用戶輸入的數據。例如:若是兩個用戶使用一樣的表單來改變他們的資料,一個改變了他們的郵件地址,另外一個改變了他們的顯示名稱,均會致使數據庫相同的update查詢-數據庫只能優化一次。
Long Time No Post and PetaPoco v5
http://www.toptensoftware.com/Articles/137/Long-Time-No-Post-and-PetaPoco-v5
本文主要是V5版本的一些更新……實在沒有力氣翻譯了。8-(