• Mini ORM——PetaPoco笔记


    Mini ORM——PetaPoco笔记

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

    目录:

    Announcing PetaPoco

    PetaPoco-Improvements

    PetaPoco-Improvements II

    PetaPoco-T4 Template

    PetaPoco-NuGet Package

    PetaPoco-Paged Queries

    PetaPoco-Named Columns,Result Columns and int/long conversion

    PetaPoco-NUnit Test Cases

    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

    PetaPoco-More Speed

    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 - Working with Joins

    PetaPoco - Oracle Support and more

    PetaPoco - Not So Poco!(or, adding support for dynamic)

    PetaPoco - Version 2.1.0

    PetaPoco - Incorporating Feedback

    PetaPoco - Single Column Value Requests

    PetaPoco - Version 3.0.0

    PetaPoco - Experimental Multi Poco Queries

    PetaPoco - What's new in v4.0

    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}	{1}	{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

  • 相关阅读:
    洗礼灵魂,修炼python(48)--巩固篇—模块
    洗礼灵魂,修炼python(48)--巩固篇—模块
    洗礼灵魂,修炼python(48)--巩固篇—模块
    Excel中拆分列
    Excel中拆分列
    Excel中拆分列
    Excel中拆分列
    Eclipse新建类的时候如何自动添加注释(作者,时间,版本等信息)
    Eclipse新建类的时候如何自动添加注释(作者,时间,版本等信息)
    用golang实现DDOS攻击网站
  • 原文地址:https://www.cnblogs.com/james641/p/5984217.html
Copyright © 2020-2023  润新知