Mini ORM——PetaPoco笔记

记录一下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 而不是IEnumerable 。在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();

注意这有一些限制,不过我以为这是值得的。

一、这个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使用@ 做为名称参数可是可能会和某些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);

一些说明:

一、SQL查询从两个表返回列。

二、Query方法的前两个泛型参数指定了拥有每行数据的POCO的类型。

三、第三个泛型参数是返回集合的类型-通常是第一个表的对象类型,但也能够是其余的。

四、Query方法须要它的第一个参数做为回调委托,能够用来链接两个对象以前的关系。

因此在这个例子中,咱们返回一个IEnumerable

,每一个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);

两点须要注意的:

一、第三个返回参数类型不是必需的。返回的结果集合永远是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 ,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
        ");

注意上面作了两件事:

一、在泛型参数中有一个额外的<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-(

相关文章
相关标签/搜索