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

DataObjects.Net - RAD的ORM框架 - 简介

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.85/5 (15投票s)

2010年6月3日

CPOL

11分钟阅读

viewsIcon

51640

downloadIcon

1123

x-tensive公司ORM框架DataObjects.Net简介

引言

持久化数据经历了漫长的发展历程,从最初的文件、带索引的数据(如Berkeley DB或DBase)、支持事务、存储过程和参照完整性的SQL服务器,到最后的纯面向对象数据库。XML文件在存储少量信息或进行信息交换方面变得非常流行。实际上,没有万能的解决方案或通用的持久化数据方案。但是,对于所谓的业务应用程序,SQL和关系数据库仍然是首选。

从开发人员的角度来看,纯面向对象数据库很诱人。代码看起来很棒,而且没有被数据库的东西所杂乱。但是对于利益相关者来说,使用OO数据库存在许多缺点。首先是(通常)高昂的许可成本,最后是IT部门完全不知道如何维护(备份、转换等)这样的数据库。他们认为成熟稳定的数据库系统已经存在。有些数据库(如PostgreSQL)是免费和开源的。有些数据库(如Microsoft Azure云服务)则不需要硬件或维护。

在这种冲突中,对象关系映射(ORM)框架应运而生。ORM框架使关系数据库看起来像面向对象(或对象关系)数据库。然而,数据仍然存储在关系数据库(甚至更简单的数据库)中。

为什么要使用ORM框架?

首先,关系数据库(因此也包括ORM)的理想项目是复杂的业务应用程序,它们有大量的逻辑(所谓的业务逻辑)。典型的业务应用程序领域包括会计和企业资源规划(ERP)。Web内容管理系统(CMS)、组织者和业务规划师也属于此类应用程序领域。任何具有复杂数据(称为实体)之间关系和逻辑的都适合这个概念。如果您需要存储非结构化或相互之间关系不大的数据(如日志文件),那么使用关系数据库或ORM可能不是最佳选择。

如果您熟悉SQL编程,您会知道在开发这类应用程序时,您总是要做两遍工作。首先,您通过创建大量表和设置它们之间的外键来定义数据库模型。然后,您编写代码来访问这些表,放置查询并更改值。您的逻辑实体分散在多个表中,您必须手动连接它们并(几乎)手动维护一致性。这是自20世纪90年代中期以来所谓的客户端/服务器应用程序的构建块。它在Microsoft .NET Framework中以ADO.NET的形式实现,并处理像DataSetDataTableDataRow DataView这样的类。

对象关系映射(ORM)的出现是为了通过对象访问数据库。它充当编程语言或环境与底层数据库之间的一个层。由于这种抽象,业务应用程序的编程速度更快,代码也更易于维护。ORM框架实现了多种方法来加速开发过程并提高可维护性。总的来说,它们允许以(几乎)自然的方式通过反映实体的对象来访问数据库。许多ORM允许针对数据库运行LINQ查询来获取实体。还有一些ORM可以完全将业务逻辑与底层数据库解耦。

两个最知名的ORM框架是NHibernate(.NET)和ADO.NET Entity Framework。它们都只实现了部分LINQ,并且在速度方面并非最快的。在这篇文章中,我想介绍x-tensive公司的DataObjects.Net。DataObjects.net是一个具有非常高抽象级别、近乎完整的LINQ实现和良好性能的ORM。

为什么DataObjects.Net值得一试?

首先,我尝试它是因为它具有自动的模式生成和维护功能。我编写SQL多年了,对这些表、视图和存储过程的声明感到厌倦。

在使用过程中,我意识到这个框架几乎解决了ORM带来的所有问题。我可以透明地使用会话和事务,我的实体看起来就像真正的C#/VB对象。X-tensive公司在最大程度地减少通信(与SQL服务器的往返)方面付出了很多努力。对于复杂的业务应用程序,DataObjects.Net甚至比直接使用SQL服务器或任何轻量级ORM框架都要快,这得益于他们的优化。

可以从以下链接找到与其他ORM框架的功能和性能比较:ormbattle.net

DataObjects.Net是如何工作的?

DataObjects.Net使用面向切面的编程(AOP)技术,将持久化和业务逻辑注入到普通的C#/VB类中。方面可以看作是在其他函数开始或结束时调用的某种函数或委托。它们不需要在源代码中调用,而是在编译程序集后在中间语言(IL)中注入。DataObjects.Net使用著名的PostSharp框架(一个面向切面的编程框架)来注入映射代码。模型以简单的类和一些属性的形式声明,如下面的示例所示。这使得DataObjects.Net非常易于使用,并将所有映射内容与用户隔离开来。

下载和安装DataObjects.Net

在您试用该框架之前,您必须从x-tensive主页下载并安装它。在这篇文章中,我使用的是Visual Studio 2010的4.3版本。

幕后:DataObjects.Net和Visual Studio

您可能会问,PostSharp和DataObjects.Net是如何集成到Visual Studio解决方案或C#项目中的。由于PostSharp需要操作已编译和链接的程序集,因此在编译后必须有一个特殊的构建步骤。如果您在Visual Studio解决方案中打开.csproj文件,您会发现一个额外的“import project”行

<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<Import Project="$(DataObjectsDotNetPath)\Common\DataObjects.Net.targets" />  

这个包含行在幕后完成了魔术。顺便说一句:如果您在项目资源管理器窗口中右键单击您的项目,会找到“卸载项目”。在卸载项目后,您会找到“编辑项目”来编辑您的项目文件。编辑后,您可以重新加载项目。

您可以在手册的“在您的项目和解决方案中使用DataObjects.Net”部分找到更多信息。

构建块

任何ORM框架都将一些类或对象映射到数据库中的某些数据。然而,每个ORM框架都有自己的实现方式。在接下来的章节中,我将解释DataObjects.Net应用程序的构建块,并概述其背后的思想。

域和数据存储

DataObjects.Net需要访问某种数据存储,通常是SQL服务器。数据库和C#对象之间的粘合剂是所谓的域(Domain)。域这个术语与互联网域无关,而是源自所谓的领域驱动设计(DDD)。在技术上,域是连接到数据库的连接器。一个域需要两样东西:首先,到数据库或数据存储的总连接字符串。所有数据将存储在那里。其次,定义了持久化类的程序集和命名空间。这些类被称为“模型”(Model)。所需的表、索引、主键和外键都是从这个“模型”生成的。DataObjects.Net域可以在App.config文件或代码中进行配置。以下是如何在代码中完成此操作的示例:

var config = new DomainConfiguration("sqlserver://\\SQLExpress/DO40-Tests");
config.UpgradeMode = DomainUpgradeMode.Recreate; // Drops all tables and recreates them. 
						// Database is empty after that
config.Types.Register(typeof(SimpleEntity).Assembly, typeof(SimpleEntity).Namespace);
var domain = Domain.Build(config);    

我们有一个到名为“DO40-Tests”的Microsoft SQL Server Express数据库的连接字符串。请记住,DataObjects.Net **可以生成表等**,但**它不能自己生成任何数据库**。您必须创建“DO40-Tests”数据库,并确保DataObjects.Net可以在给定的连接字符串下访问它。之后,DataObjects.Net将完成剩余的工作。

模型

持久化数据以C#类的形式声明和访问。持久化类型必须派生自Entity类或Entity的某个子类。持久化字段声明为带有[Field]属性的属性。在继承层次结构中,第一个或根类必须带有[HierarchyRoot]属性,并且至少有一个[Field]属性必须声明为[Key]属性才能成为主键。这里有一个简单的实体,带有一个int键和一个string数据(payload)字段:

[Serializable]
[HierarchyRoot]
public class SimpleEntity : Entity
{
    [Field, Key]
    public int Id { get; private set; }

    [Field(Length = 100)]
    public string Data { get; set; }
} 

这些SimpleEntity对象之间没有任何关系。HierarchyRoot属性将此类标识为层次结构的基础。此处,“层次结构”是指面向对象意义上的继承基类。下一个示例展示了类如何从这个基类派生:

[Serializable]
[HierarchyRoot]
public class BaseEntity : Entity
{
    [Field, Key]
    public int Id { get; private set; }

    [Field(Length = 100)]
    public string BaseEntityData { get; set; }
}

[Serializable]
public class DerivedEntity : BaseEntity
{
    [Field(Length = 100)]
    public string DerivedEntityData { get; set; }
}    

如上所示,派生类以常规的C#方式继承自基类。它继承了基类的所有属性和主键。您可以推测,所有的类型转换(base <-> derived)都可以以常规的C#方式进行。使用这些持久化对象几乎与使用普通对象一样。在下一节中,将模拟一个典型的父/子或有时称为主/明细关系:

[Serializable]
[HierarchyRoot]
public class ParentEntity : Entity
{
    [Field, Key]
    public int Id { get; private set; }

    [Field]
    public EntitySet<ChildEntity> Childs { get; private set; }
}

[Serializable]
[HierarchyRoot]
public class ChildEntity : Entity
{
    public ChildEntity( ParentEntity parent )
    {
        Parent = parent;
    }

    [Field, Key]
    public long Id { get; private set; }

    [Field]
    [Association(PairTo = "Childs", OnTargetRemove = OnRemoveAction.Cascade)]
    public ParentEntity Parent { get; private set; }
}    

ParentEntity类几乎是直观的。在ChildEntity中,我们看到一个ParentEntity的'Parent'字段,用于存储到父对象的引用。我们还看到了形式为Association属性的业务逻辑。在这种情况下,关联将字段'Parent'与EntitiySet'Childs'配对。任何具有特定父对象的ChildEntity都自动包含在Childs EntitySet中,反之亦然。通过“OnTargetRemove = OnRemoveAction.Cascade”操作,在删除父对象时会自动删除子对象。解释DataObjects.Net的所有业务规则特性远远超出了本介绍的范围。但请记住,这些功能都可以通过DataObjects.Net实现。这两个类都用HierarchyRoot属性标记,因为它们都是基类(继承层次结构的根)。

为了提高查询性能,还可以为某些字段指定索引。它们使用Index属性声明,如下面的示例所示。

[Serializable]
[HierarchyRoot]
[Index("Data")]
public class IndexedDataEntity : Entity
{
    [Field]
    [Key]
    public int Id { get; private set; }

    [Field]
    public int Data { get; set; }
} 

基本操作

设置好域和声明模型后,就可以执行一些操作了。尽管DataObjects.Net允许以非常自然的方式访问数据,但它完全支持会话和事务。

会话和事务,持久化对象的创建和销毁

下一个示例展示了如何在代码中以编程方式使用会话和事务。特别是对于事务,也可以使用属性以声明式的方式完成。

using (Session.Open(domain))
{
    using (var transactionScope = Transaction.Open())
    {
        new SimpleEntity { Data = "SimpleEntity 1" };
        new SimpleEntity { Data = "SimpleEntity 2" };
        transactionScope.Complete();
    }
}        

使用IDisposable模式(using)可以确保在发生异常时会话和事务被关闭。在这个示例中,SimpleEntity对象确实被持久化了,而没有为它们提供任何事务或会话。为了实现这一点,框架使用一个隐藏的(线程static)当前会话和当前事务。当前会话和当前事务由Open()方法自动设置。在下一个示例中,将创建ParentEntityChildEntity对象。由于ChildEntity类的构造函数,不可能在没有父对象的情况下创建ChildEntity对象。DataObjects.Net支持普通C#类的构造函数语义。

using (Session.Open(domain))
{
    using (TransactionScope transactionScope = Transaction.Open())
    {
        var parent = new ParentEntity();
        new ChildEntity(parent);
        new ChildEntity(parent);
        new ChildEntity(parent);
        transactionScope.Complete();
    }
}    

如上所示,使用DataObjects.Net创建持久化对象非常简单。删除或移除这样的对象也没有问题。只需在对象上调用Remove()成员函数即可。我们应该记住,这些新创建的对象将永久存在,或者至少直到调用Remove()方法。但是,当我们的局部变量超出范围或我们的应用程序终止时,我们如何再次获取这些对象呢?答案很简单:我们使用LINQ(语言集成查询)从数据库中获取这些对象。这将在下一节中展示。

使用LINQ查询持久化对象

每个DataObjects.Net查询的起点是Query.All<>。下一个查询以任意顺序返回所有DerivedEntity对象。

using (Session.Open(domain))
{
    var linqQuery = from entity in Query.All<DerivedEntity>() select entity;
    foreach (var entity in linqQuery)
    {
        // Do some work with the objects
    }
}   

一旦我们获得一个对象,我们就可以以常规的C#方式使用其中的引用和集合。在下一个示例中,我们遍历ParentEntity对象中的Child集合。

using (Session.Open(domain))
{
    var linqQuery = from entity in Query.All<ParentEntity>() select entity;
    foreach (var parentEntity in linqQuery)
    {
        foreach( var childEntity in parentEntity.Childs )
        {
            // Do some work with the childs and parents
        }
    }
}   

DataObjects.Net拥有近乎完整的LINQ支持。下一个示例中的查询更为复杂。它对查询施加了一个条件(where)并对结果进行排序(orderby)。

using (Session.Open(domain))
{
    var linqQuery = from entity in Query.All<IndexedDataEntity>() 
        where entity.Data < 1000 
        orderby entity.Data 
        select entity;
    foreach (var entity in linqQuery)
    {
        // Do some work with the selected objects
    }
}    

事实上,LINQ查询可以复杂得多。但这远远超出了本介绍的范围。

结论

我们在一个新项目中使用了DataObjects.Net 4.0和PostgreSQL,该项目包含约60个持久化类和目前1000万个实体。起初,我们在PostgreSQL服务器的性能方面遇到了一些问题。但在配置了足够的缓存大小(默认值非常低)和一些小的优化之后,它的速度非常快,几乎在所有情况下都与Microsoft SQL服务器相当。我们正在使用PostgreSQL 9.0 Beta4 64位版本。我对数据库应用程序开发的速度和简便性印象深刻。总而言之:DataObjects.net可以成为加快开发过程的好选择,尤其对于新项目而言。

© . All rights reserved.