65.9K
CodeProject 正在变化。 阅读更多。
Home

Postulate ORM 入门

2017 年 6 月 12 日

CPOL

9分钟阅读

viewsIcon

12614

downloadIcon

220

Postulate 简介,一个使用 Dapper 为 SQL Server 构建的代码优先 ORM

引言

在这篇文章中,我想谈谈我正在开发的一个名为“Postulate”的 ORM 库。(GitHub)我长期以来一直在使用 ORM——既作为开发者也作为使用者。多年来,我从未真正找到过强大功能和高生产力之间的完美平衡——直到现在,也许吧。

为什么我不放弃使用 Entity Framework?

有几个原因:

  1. 我讨厌迁移。它们复杂且要求苛刻。
  2. 我不喜欢 EF 广泛的关注点和覆盖范围。我希望我的 ORM 层更轻巧、更简单。
  3. 我不同意 EF 在主键、外键和继承方面的约定。
  4. 我并不完全信任 Linq 作为 SQL 的替代品。Linq to Entities 很出色,但你必须接受整个 EF 堆栈,或者开发自己的 Linq 提供程序。

Postulate 的亮点是什么?

  1. 它的 CRUD 操作非常简单——语法上的易用性和简洁性可能是我主要的目标。
  2. 它有一个很棒的方法,可以通过一个我在这里会讲到的特殊应用程序来合并模型更改到数据库。我还有一个演示视频:https://vimeo.com/219400011。所有这些都是开源的,并且合并 API 可以有不同的应用方式。
  3. 它具有诸如更改跟踪删除跟踪之类的功能,我认为这些功能在许多应用程序中都很有用。还有一些很棒的方法可以标准化审计跟踪,包括用户名和日期戳。我来自多租户系统背景,有一个功能可以帮助防止数据在租户之间漂移,通过 [ColumnAccess] 属性。

Postulate 可能引起争议的地方有哪些?

  1. 模型类必须继承自Record<TKey>,所以它不是纯 POCO。我这样做是为了强制执行关于主键的约定,并为验证、事件和权限提供扩展点。
  2. 在构建用于 WHERE 子句的 SQL 时,在某些地方会使用内联 SQL。我想提供 Lambda 表达式的通用解析,但我不知道如何做到。我非常欢迎任何帮助!
  3. Postulate 没有导航属性。一方面,我认为它们有点被高估了,而且 IMO 实现起来非常复杂且有风险。

本介绍将重点介绍在控制台应用程序中入门和执行 CRUD 操作,以及使用 Schema Merge 应用程序。我不会讨论查询的通用用法,也不会讨论在 MVC 应用程序等更实际场景中的用法。不久前,我将查询功能重构到了一个名为Postulate.Sql 的不同项目。同样,我有一个名为Postulate.Mvc 的项目,专门用于 ASP.NET MVC。

演练

开始

  1. 创建一个控制台应用程序,将 .NET 版本设置为 4.6.1,然后安装 nuget 包 Postulate.Orm
  2. 从本文或 GitHub 发布页面下载/安装 Schema Merge 应用程序。编辑控制台应用程序项目的生成后事件,使其包含此命令:
    "C:\Users\Adam\AppData\Local\Adam O'Neil Software\Postulate Schema Merge\PostulateMergeUI.exe" 
    "$(TargetPath)"

    你可以从 Postulate Schema Merge 桌面图标获取命令的 EXE 部分

    ($TargetPath) 宏由 Visual Studio 定义,并将生成输出文件名传递给 Postulate 合并应用程序。

  3. app.config 文件中添加一个 SQL Server 连接字符串。本教程将假定连接名为“demo”。数据库不必存在——但要确保字符串在其他方面有效,并且在数据库不存在时具有创建数据库的权限。

我的连接字符串看起来像这样。请注意,我使用的是 localhost,因为我有一个 SQL Server 实例。你也可以使用 Azure 或你环境中任何其他有效的连接。请注意,我的数据库名称是 CodeProjectDemo

现在你可以开始编写代码了。

根对象:SqlServerDb<TKey>

首先,创建一个继承自SqlServerDb<TKey>的类。TKey 参数是数据库中所有表的**主键类型**。Postulate 支持三种键类型:intlongGuid。仅此构造函数就足以开始使用了。

public class DemoDb : SqlServerDb<int>
{
    public DemoDb() : base("demo")
    {
    }
}

这没什么令人惊讶的,但为了 Schema Merge 应用程序的方便,我们需要添加另一个构造函数。Schema Merge 需要一个显式的 configuration 对象,因为 Schema Merge 应用程序有自己的配置,与你的控制台应用程序不同。

public DemoDb(Configuration configuration) : base(configuration, "demo")
{
}

默认构造函数接受一个可选的 userName 参数。这在应用记录上的用户和时间戳时发挥作用。对于控制台应用程序,我们可以使用 Environment.UserName。最终的类如下所示:

namespace PostulateIntro
{
    public class DemoDb : SqlServerDb<int>
    {
        public DemoDb(Configuration configuration) : base(configuration, "demo")
        {
        }

        public DemoDb() : base("demo", Environment.UserName)
        {
        }
    }
}

模型类:Record<TKey>

现在我们可以开始创建模型类了。让我们做一个简单的例子。

public class Employee : Record<int>
{
    [Required]
    [MaxLength(50)]
    public string FirstName { get; set; }

    [Required]
    [MaxLength(50)]
    public string LastName { get; set; }

    [Required]
    [MaxLength(9)]
    public string SSN { get; set; }

    public DateTime DateOfBirth { get; set; }

    [MaxLength(50)]
    public string Phone { get; set; }

    [MaxLength(50)]
    public string Email { get; set; }
}

当你构建项目时,Visual Studio 会有一些处理。几秒钟后,你应该会看到此消息:

接着是这个:

每次构建项目并且模型类发生更改时,Schema Merge 应用程序都会出现,并在左侧的树状视图中显示操作和受影响的对象。相应的 SQL 将显示在右侧。点击右上角的 Execute 按钮执行 SQL。

如果你在不执行的情况下关闭 Schema Merge 应用程序,除非你进行更多模型更改或**重新生成**项目,否则它不会再次出现。

让我们继续执行我们已有的代码。你应该会看到这条消息:

现在我们可以回到编码,并进行更多模型更改。我们的第一个表已经创建好了。

让我们在 employee 类中添加一个新的属性,在这里是一个外键:

[ForeignKey(typeof(Employee))]
public int? ManagerId { get; set; }

Postulate 有一个像 EF 一样的 ForeignKey 属性,但它是一个不同的属性——不要混淆它们。Postulate 需要主表的类型。(你可以选择启用级联删除和索引——请参阅ForeignKeyAttribute.cs。)

当我构建项目时,我看到了这个。我的 employee 表被删除并重新创建以添加新列。如果表中有数据,则不会有 DROP 语句。Postulate 永远不会删除有数据的表。在这种情况下,列会简单地添加到表的末尾。另请注意,还会创建一个外键。

我希望这能让你对 Postulate Schema Merge 的功能有所了解。它在检测结构性更改和生成正确的 SQL 来执行它们方面做得相当好。这无疑是 Postulate 中最难的部分。如果你有建议或遇到 bug,请通过在 GitHub 仓库中创建一个 Issue 来告知我。

上面我链接的演示视频更详细地展示了 Schema Merge 的用途。现在,让我们创建并处理一些数据。

CRUD 操作

让我们先创建一个并保存一个 Employee 记录。我想到什么写什么,写了这段代码:

class Program
{
    private static DemoDb _db = new DemoDb();

    static void Main(string[] args)
    {
        var emp = new Employee() 
        { LastName = "Whoever", FirstName = "Hegel" };
        _db.Save(emp);
        Console.WriteLine($"emp id = {emp.Id}");
        Console.ReadLine();
    }
}

你不需要想太多!这是我的主要设计目标之一。请注意,我有一个 static 变量 _db。我本可以在 Main 方法中将其声明为局部变量,但出于习惯(来自 MVC 控制器开发),我将其设为类级别的 static

Save 方法根据记录的状态执行 insertupdate。没有显式的 insertupdate 方法。嗯,有一个 Update 方法,但它用于一种略有不同的情况。通常,在 MVC 操作中,以及在典型情况下,你会使用 Save 方法。

当我运行这个时,我收到了这个错误:

它告诉我缺少几个必需字段。所以让我们修复它并重试。我重写了它,然后运行了它:

这下对了!

查找现有记录有几种方法:FindFindWhereFind 接受一个 TKey 参数,而 FindWhere 接受一个 string WHERE 子句。让我们都试一下——首先是 Find 方法。我知道 employee ID 为 1 的记录是在不久前创建的,所以我将其作为 key 参数传递。你可以在我截取的控制台窗口片段中看到,“Hegel Whoever”被找到了。

在我没有 TKey 的情况下,我可以使用 string WHERE 子句。注意 lastName 是如何使用 Dapper 风格的语法作为参数传递的。

删除记录并不难。有三种删除方法——你大概可以从方法名称中看出它们的作用。它们都接受一个 TRecord 泛型参数来指定要删除的模型类型,后面跟着一个 WHERE 子句和参数,或者一个 TKey

当我们向模型类添加 [TrackDeletions] 属性时,删除记录会变得更有趣。这使我们能够跟踪谁在何时删除记录,以及在不违反外键的情况下恢复记录的能力。还记得我们在 DemoDb 构造函数中将用户名设置为 Environment.UserName 吗?该用户名会被传递给后续可能需要一种标准方式来了解当前用户的方法——而无需依赖特定的身份提供程序。

[TrackDeletions] 生效时,删除记录会导致 Postulate 将对象序列化为 XML。如果不存在,会创建一个 deleted 表,并将记录数据保存在那里。(请注意,你最终使用的生产凭据可能没有权限在运行时创建模式和表,因此在生产环境中,你可能需要提前创建 deleted 模式和删除跟踪表,然后再进行普遍使用。)

_db.DeleteOne<Employee>(1);

要恢复记录:

_db.RestoreOne<Employee>(1);

你可以在 Postulate wiki 上阅读更多关于 delete 跟踪的信息。既然谈到了跟踪,让我简单介绍一下更改跟踪。为模型类添加 [TrackChanges] 属性,以便在进行 Save 或 Update 操作时捕获记录的修改前后状态。例如:

现在,我将查找、更新和保存一条记录。请注意,TKey 值为 2,因为我正在处理之前恢复的那条记录,并且它已经获得了新的 ID。

var emp = _db.Find<Employee>(2);
emp.LastName = "Whomever";
_db.Save(emp);

现在我可以查询该记录的更改历史记录:

你可以在 Postulate wiki 上阅读更多关于更改跟踪的信息。

结论

我希望这些内容足以引起你的兴趣并让你尝试一下 Postulate!我欢迎在 GitHub 仓库 上贡献代码和任何反馈。

Schema Merge 应用程序有单独的仓库:Postulate.MergeUI

如果有兴趣,我将讨论 Postulate.Sql 和 Postulate.Mvc,它们展示了这个 ORM 在更实际场景中的应用。

另外,请查看视频,它比上面的教程更深入。

致谢

Schema Merge 应用程序的 SQL 语法感知代码视图得益于 Pavel Torgashov 的出色作品Fast Colored Text Box

© . All rights reserved.