记录一下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-(