Mini ORM——PetaPoco笔记
记录一下petapoco官网博客的一些要点。这些博客记录了PetaPoco是如何一步步改进的。
目录:
PetaPoco-Named Columns,Result Columns and int/long conversion
PetaPoco-Value Conversions and UTC Times
PetaPoco-T4 Template support for SQL Server
Some Minor PetaPoco Improvements
PetaPoco-Transaction Bug and Named Parameter Improvements
PetaPoco-Custom mapping with the Mapper interface
PetaPoco-Smart Consecutive Clause Handling in SQL Builder
PetaPoco-Performance Improvements using DynamicMethods
Benchmarking SubSonic's Slow Performance
PetaPoco - Support for SQL Server Compact Edition
PetaPoco - PostgreSQL Support and More Improvements
PetaPoco - A couple of little tweaks
PetaPoco - Oracle Support and more
PetaPoco - Not So Poco!(or, adding support for dynamic)
PetaPoco - Incorporating Feedback
PetaPoco - Single Column Value Requests
PetaPoco - Experimental Multi Poco Queries
PetaPoco - Mapping One-to-Many and Many-to-One Relationships
PetaPoco - Partial Record Updates
Long Time No Post and PetaPoco v5
Announcing PetaPoco
http://www.toptensoftware.com/Articles/68/Announcing-PetaPoco
第一个版本。
PetaPoco-Improvements
http://www.toptensoftware.com/Articles/69/PetaPoco-Improvements
如果运行查询时不以“select”开头,则petapoco会自动加上:
// 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<T>而不是IEnumerable<T>。在PetaPoco里这是一个Fetch而不是一个Query。添加一个IEnumerable版本也很简单,但考虑到结果集的大小是分页决定的,因此没必要添加。
背后的故事:
我总是觉得构建分页查询语句很乏味,这一般涉及到两个不同但很类似的SQL语句:
1.分页查询本身
2.查询所有记录数量。
接下来讲到如何处理MySQL和SQL Server分页查询的异同,为了支持不同的数据库,使用了不同的查询语句。略过。
PetaPoco-Named Columns,Result Columns and int/long conversion
http://www.toptensoftware.com/Articles/75/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();
注意这有一些限制,不过我觉得这是值得的。
1、这个mapper是被所有Database的实例共享的。PetaPoco在全局缓存这些列映射,所以不能为不同的数据库实例提供不同的映射。
2、只能安装一个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);
}
有一些注意事项,但很容易处理。
1、where子句必须是Sql片段的第一个部分,所以下面的不会工作:
sql.Append("WHERE condition1 WHERE condition2");
但这样的可以:
sql.Append("WHERE condition1").Append("WHERE condition2");
2、Sql片段必须相邻,所以这样的不会工作:
sql.Append("WHERE condition1").Append("OR condition2").Append("WHERE condition3");
3、你也许需要给个别条件加上括号来确保得到正确的优先级:
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) { }
你在两种情况下也许会用到它:
1、日志-你可以抓取SQL语句和参数值并记录任何你需要的。
2、调整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支持:
1、打开项目属性
2、切换到“生成”选项卡
3、在“条件编译符号”添加PETAPOCO_NO_DYNAMIC
包含空格的列(和其他非标识符字符的)
以前版本的PetaPoco假设你的数据库的任何列名都是有效的C#标识符名称。如果列名中包含空格,当然会出错,现在已经通过两种方式来纠正这个错误:
1、PetaPoco可以正确转义SQL数据库的分隔符,如[column], `column` or "column"
2、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使用@<name>作为名称参数但是可能会和某些Provider冲突。以前你可以为MySQL转义,现在可以支持所有的Provider了。在这个例子中,@@id将作为@id传递到数据库中而@name将被用作在传递的参数中查找属性名。(怎么翻译?)
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);
一些说明:
1、SQL查询从两个表返回列。
2、Query方法的前两个泛型参数指定了拥有每行数据的POCO的类型。
3、第三个泛型参数是返回集合的类型-一般是第一个表的对象类型,但也可以是其他的。
4、Query方法需要它的第一个参数作为回调委托,可以用来连接两个对象之前的关系。
所以在这个例子中,我们返回一个IEnumerable<article>,每个article对象都通过它的user属性拥有一个相关user的引用。
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);
两点需要注意的:
1、第三个返回参数类型不是必需的。返回的结果集合永远是T1类型。
2、设置对象关系的回调方法不是必需的。
很明显的,做这项工作PetaPoco有一点小猜测,但是这是一个常见的情况,我认为这是值得的。要实现这个目的,T2到T5必需有一个属性是和它左边的类型是相同的类型。换句话说:
1、T1必需有一个T2的属性
2、T1或T2必需有一个T3的属性
3、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<post>,post对象有一个author类型的属性,PetaPoco将它连接到创建的author对象。
写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
");
注意上面做了两件事:
1、在泛型参数中有一个额外的<post,author,post>。最后一个参数表明了返回集合的类型。使用自定义的relator你可以决定使用不同的类代表Join的行。
2、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<string>指定应该被更新的列的名称(不是属性).
这是有用的除非跟踪哪些列需要更新非常痛苦。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();
}
解释一下:
1、OnLoaded是一个新方法,PetaPoco在从数据库填充任何POCO实现后都将立即调用它。
2、MarkColumnsModified-简单的记录OnLoaded被调用后有值被更改的列名。
3、执行Update时Update和Save已经更新为传递一个修改列的list给PetaPoco。
有一点需要注意的,set访问器,它们标志了列被修改 实际上值并没有改变。这是故意的,有两个原因:
1、它确保值无论如何确实被发送到数据库,帮助保持数据一致性。
2、这意味着查询数据库不依赖于用户输入的数据。例如:如果两个用户使用同样的表单来改变他们的资料,一个改变了他们的邮件地址,另一个改变了他们的显示名称,均会导致数据库相同的update查询-数据库只能优化一次。
Long Time No Post and PetaPoco v5
http://www.toptensoftware.com/Articles/137/Long-Time-No-Post-and-PetaPoco-v5
本文主要是V5版本的一些更新……实在没有力气翻译了。8-(
转自:http://www.cnblogs.com/yanxiaodi/archive/2013/03/25/2978606.html#Announcing-PetaPoco