博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
Mini ORM PetaPoco
阅读量:5322 次
发布时间:2019-06-14

本文共 32687 字,大约阅读时间需要 108 分钟。

记录一下petapoco官网博客的一些要点。这些博客记录了PetaPoco是如何一步步改进的。

目录:

Announcing PetaPoco

http://www.toptensoftware.com/Articles/68/Announcing-PetaPoco

第一个版本。

PetaPoco-Improvements

http://www.toptensoftware.com/Articles/69/PetaPoco-Improvements

如果运行查询时不以“select”开头,则petapoco会自动加上:

// Get a recordvar a=db.SingleOrDefault
("SELECT * FROM articles WHERE article_id=@0", 123);// can be shortened to this: Get a recordvar a=db.SingleOrDefault
("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 recorddb.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
("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
FetchPage
(long page, long itemsPerPage, string sql, params object[] args) where T : new()

注意一点page参数是从0开始。返回值是一个PagedFetch对象:

// Results from paged requestpublic class PagedFetch
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
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
("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
(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
m = user.FetchPage(page - 1, 30, "ORDER BY display_name");

现在这样用:

Page
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
("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
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
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
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次迭代载入一个帖子实体手工编码 65msPetaPoco(Fast) 67msPetaPoco(Normal)  78msLinq 2 SQL 841msEF 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
("WHERE article_id=@0", id);

-EnableNamedParams,是否处理Database类的参数,禁用时,以下无法运行:

var a=db.SingleOrDefault
("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
("WHERE id=@0", 123);

转换为:

var a = db.SingleOrDefault
("SELECT col1, col2, col3 FROM articles WHERE id=@0", 123);

现在它也会处理这个问题:

var a = db.SingleOrDefault
("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_countFROM articlesLEFT JOIN comments ON comments.article_id = articles.article_idGROUP BY articles.article_id;

定义一个C#类来保存结果(注意属性名称匹配SQL的列名)

public class ArticleWithCommentCount{    public string title     {         get;         set;     }    public long comment_count    {        get;        set;    }}

使用新类型来查询:

var articles = db.Fetch
(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
(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
("WHERE article_id=@0", some_id);

当然最常见的情况是根据主键取记录,所以现在对Single和SingleOrDefault有一个新的重载:

var a = db.SingleOrDefault
(some_id);

作为边注,不要忘了如果你使用T4模板的话,上面的可以更简化:

// Normal versionvar a = article.SingleOrDefault("WHERE title=@0", "My Article");// New simpler PK versionvar 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 executionpublic 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
("SELECT * FROM articles")){ Console.WriteLine("{0} - {1}", a.article_id, a.title);}

注意此时不支持自动添加SELECT子句,因为PetaPoco不知道表名。

你也可以做Update、Insert、Delete操作但是你需要指定表名和要更新的主键。

// Create a new recorddynamic a = new ExpandoObject();a.title = "My New Article";// Insert itdb.Insert("articles", "article_id", a);// New record ID returned with a new property matching the primary key nameConsole.WriteLine("New record @0 inserted", a.article_id")

更新:

// Updatevar 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
( "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
( "SELECT id, [col with spaces] FROM whatever WHERE id=@0", 23); Console.WriteLine((a as IDictionary
)["col with spaces"]);

Ansi String支持

DBA专家Rob Sullivan昨天指出,SQL Server在尝试使用Unicode字符串的参数来查询数据类型为varchar的列的索引的时候,会导致严重的性能开销。为了解决这个问题需要把参数约束为DbType.AnsiString。现在可以使用新的AnsiString类的字符串参数:

var a = db.SingleOrDefault
("WHERE title=@0", new PetaPoco.AnsiString("blah"));

Exists(PrimaryKey) and Delete(PrimaryKey)

可以检查是否存在一个主键的记录。

if (db.Exists
(23)) db.Delete
(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 tabletables["tablename"].Ignore = true;// To change the class name of a tabletables["tablename"].ClassName = "newname";                  // To ignore a columntables["tablename"]["columnname"].Ignore = true;            // To change the property name of a columntables["tablename"]["columnname"].PropertyName = "newname"; // To change the property type of a columntables["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
("CALL storedproc") // MySQL stored procdb.Query
("EXECUTE stmt") // MySQL prepared statementdb.Query
("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
("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 twhere    t.Name = @namefor 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
( (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
( ... )

感兴趣的是user_id。当映射这个结果集的时候,第一个user_id列将被映射到article,当看到第二个user_id的时候PetaPoco将意识到它已经被映射到article了,于是将其映射到user。

最后一种确定分割点的方法是当一个列不存在于当前的POCO类型但是存在于下个POCO。注意如果一个列不存在于当前POCO也不存在与下个POCO,它将被忽略。

自动连接POCO

PetaPoco可以在返回对象上自动猜测关系属性并自动分配对象引用。

这种写法:

var result = db.Query
( (a,u)=>{a.user=u; return a }, sql);

可以写成:

var result = db.Query
(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
MultiPocoQuery
(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
(";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
(@" 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
( (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
();var posts = db.Fetch
( (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
authors = new Dictionary
(); 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
( 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
(); current.posts.Add(p); // Return the now populated previous author (or null if first time through) return prev; }}

上面的注释很清楚的表明发生了什么-我们只是简单的保存author直到我们检测到一个新的然后添加文章列表到当前的author对象,这样来用:

var authors = db.Fetch
( 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
(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
ModifiedColumns;private void OnLoaded(){ ModifiedColumns = new Dictionary
();}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

 

转载于:https://www.cnblogs.com/wishese/p/3247373.html

你可能感兴趣的文章
Java 实践:生产者与消费者
查看>>
[转]IOCP--Socket IO模型终结篇
查看>>
js 获取视频的第一帧
查看>>
各种正则验证
查看>>
观察者模式(Observer)
查看>>
python中numpy.r_和numpy.c_
查看>>
egret3D与2D混合开发,画布尺寸不一致的问题
查看>>
freebsd 实现 tab 命令 补全 命令 提示
查看>>
struts1和struts2的区别
查看>>
函数之匿名函数
查看>>
shell习题第16题:查用户
查看>>
实验4 [bx]和loop的使用
查看>>
Redis常用命令
查看>>
2018.11.06 bzoj1040: [ZJOI2008]骑士(树形dp)
查看>>
2019.02.15 bzoj5210: 最大连通子块和(链分治+ddp)
查看>>
redis cluster 集群资料
查看>>
微软职位内部推荐-Sr. SE - Office incubation
查看>>
微软职位内部推荐-SOFTWARE ENGINEER II
查看>>
centos系统python2.7更新到3.5
查看>>
C#类与结构体究竟谁快——各种函数调用模式速度评测
查看>>