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

SQL Server 2005 中的 XML 数据类型技巧

2006年11月9日

CPOL

10分钟阅读

viewsIcon

262668

downloadIcon

2957

使用 XML 数据类型存储数据的技术。

引言

本文概述了在创建 VS2005 应用程序时,使用 SQL 2005 中提供的新 XML 数据类型的一些技术。这里的示例是用 VB.NET 编写的,但有 C# 版本的源代码。

背景

如果您使用面向对象 (OO) 语言(如 VB.NET 或 C#)进行开发,并将数据存储在关系数据库系统(如 SQL Server)上,经常出现的一个问题是如何处理 OO 的灵活性与数据库更严格的层次结构之间的差异。

例如,假设您的应用程序是一个在线电脑硬件商店,您需要存储一组“产品”的详细信息。通用字段,如 ProductID、StockCode、ProductName 等,相对容易映射到数据库结构。

然而,当字段要求根据产品性质而差异很大时,会发生什么?数码相机(像素数量、最大变焦、存储类型)的字段与硬盘(容量、接口类型、速度、外形尺寸)的字段会非常不同。您不能真正将这些作为列添加到表中,因为很快表就会变得非常大,并且有很多冗余字段。

一种传统方法是创建一个子表,例如“ProductAttributes”,并以不同的行存储属性。然后问题就变成了如何以通用方式存储不同的数据类型,例如字符串、日期、货币值、整数等。每次我们需要检查单个产品时,它还需要一个 SQL JOIN,以及更多的代码来处理、排序和显示这些信息。

使用 XML 以更“灵活”的方式存储此类数据是一种将值更接近对象而无需诉诸子表的方法。我们可以不使用子表,而只添加一个 XML 列 ProductAttributes 并存储一些带有属性的 XML。

起源

我在为数据库应用程序创建作业处理系统时开发了这项技术。作业处理“引擎”将从数据库表中加载待处理作业,并执行它们。“作业”本质上是 VS2005 中的一个基类,具有 JobID、JobName、RunAfter 等通用属性。问题是实际的作业实例类,如 ExportAccountsDataJob 或 SendCustomerEmailAlertsJob,都需要存储和检索非常不同类型的数据。

SQL 2005 的新功能之一是支持 XML 作为数据类型。我意识到,如果我将每个实例的数据存储在一个可以序列化和反序列化为 XML 的类中,我就可以使用 XML 字段将这些数据存储在 Job 表中。这使得 Job 表结构非常简单,并且每个实现的复杂性都将处理哪些数据进入 XML 字段。

如果您没有 SQL 2005,可以使用标准数据库文本字段,尽管您将无法使用 SQL 2005 的功能来查询 XML 数据。本文无意展示此数据类型的所有强大功能和灵活性(仅 XQuery 就需要一本书),而是展示其一种用法。

工作原理

  • 首先,我们在表中创建一个 XML 字段来存储我们的数据
  • 接下来,我们在 .NET 中定义一个可以序列化为/从 XML 的类,以便我们可以将 XML 转换为对象实例并返回
  • 然后,为了使其更易于访问,我们在数据库对象中创建一个属性,这将在下面详细解释
  • 然后您可以添加任何您需要的,例如该数据类型的可重用用户控件

地址示例

本文随附的代码演示了使用 XML 将客户地址存储在 SQL 2005 数据库字段中。传统上,您可能会将 Line1、Line2、Town 等作为 Customers 表中的数据库列。这适用于一个地址实例,但重用性不强。

例如,想象一下,您可能还需要在下订单时存储一个 DeliveryAddress,或者为客户添加一个可选的 InvoiceAddress。这会导致列名为 InvoiceAddressLine1、InvoiceAddressLine2 或 DeliveryAddressLine 1 等——您知道您经历过。然后,供应商、业务合作伙伴、股东、送货公司等地址也随之而来。

每次您都必须在每个新的数据库表中定义所有相同的字段(使用不同的名称),然后构建和绑定不同的控件来显示和验证它们。重用性不高!

数据库表设计

因此,我们不定义带有单独地址字段(Line1、Line2 等)的客户表,而是使用 XML 数据类型简单地创建一个单个字段。我将它命名为 `AddressXML`。这是创建我们的 Customers 表的 SQL 代码

/* SQL to create simple customers table (with XML datatype field) */
CREATE TABLE [dbo].[Customers](
    [CustomerID] [int] IDENTITY(1,1) NOT NULL,
    [CustomerName] [varchar](64) COLLATE Latin1_General_CI_AS NOT NULL,
    [AddressXML] [xml] NULL,
 CONSTRAINT [PK_Customers] PRIMARY KEY CLUSTERED 
(
    [CustomerID] ASC
)WITH (PAD_INDEX  = OFF, IGNORE_DUP_KEY = OFF) ON [PRIMARY]
) ON [PRIMARY]

在示例源代码中,您可以在 `App_LocalResources` 目录中找到 `CreateDatabase.SQL`,它为您设置示例数据库并创建表和登录名。

请注意,SQL 2005 XML 数据类型可以支持将 XML 架构 (XSD) 链接到特定 XML 字段以提供数据完整性检查和索引支持,但为了简化本示例,我们只使用非类型化 XML 列。

代码

首先,我们在代码中创建一个包含我们希望存储的公共属性(Line1、Line2、Line3 等)的 `Address` 类。请注意,由于我们使用 XML 序列化,我们可以存储任何类型的属性,包括其他类,前提是它们支持序列化。

App_Code 目录包含一个 `Customer` 类,它以这种方式将主表列映射到属性,我编写了一些简单的 ObjectDataSource CRUD 函数用于数据库访问。

注意:ObjectDataSource 函数在创建参数时不使用 `SqlClient.SqlDataTypes.XML` 类型,它们使用 VarcharText。这是因为 SQL 2005 XML 数据类型以 UTF16 编码存储,而我们的类使用的字符串是 UTF8。如果您尝试使用 XML 数据类型,当 SQL 尝试转换时会收到错误。如果您传递带有 UTF8 的 varchar/text,SQL 2005 会自动为您转换。

为了使地址值在客户对象中更易于使用,我们有一个 `CustomerAddress` 属性,它将通过反序列化 `AddressXML` 字段的内容来加载地址。

    Public ReadOnly Property CustomerAddress() As Address
        Get
            GetAddress()
            Return _address
        End Get
    End Property
    Private _address As Address = Nothing

    'ensure _address has a valid value
    Private Sub GetAddress()
        If _address Is Nothing Then
            'check for blank
            If Me.AddressXML Is Nothing Then
                'create new instance
                _address = New Address()
            Else
                'deserialize existing XML field
                _address = Address.Deserialize(Me.AddressXML)
            End If
        End If
    End Sub

    Public Sub SaveAddress()
        If _address IsNot Nothing Then
            'write to field
            Me.AddressXML = Address.Serialize(_address)
        End If
    End Sub

这意味着我们不必写

myCustomer.Line1 = "123 High Street"

我们有一个更优雅和逻辑的方法

myCustomer.CustomerAddress.Line1 = "123 High Street"

我这样做是为了对 `CustomerAddress` 的重复引用只反序列化 `AddressXML` 一次(按需),然后保留此值。这种技术的缺点是,如果您以这种方式更新地址,则必须明确告诉 Customer 类将数据保存回 `AddressXML`(请参阅 `SaveAddress` 方法)。

Address 类还有一个示例属性 `AddressAsHTML`,它不会被序列化,因为它是只读的。这用于 FindCustomers 页面中,将地址以 HTML 形式显示在标签中。

由于我将序列化技术用于几个不同的类,我创建了一个泛型 `XMLserializer(Of T)` 类(C# 中的 `XMLSerializer`),它可以重复用于序列化/反序列化任何对象类型。这意味着序列化和反序列化 Address 类的代码现在相当简单

    Private Shared _serializer As New XMLserializer(Of Address)

    Public Shared Function Serialize(ByVal myobject As Address) As String
        Return _serializer.Serialize(myobject)
    End Function

    Public Shared Function Deserialize(ByVal xml As String) As Address
        Return _serializer.Deserialize(xml)
    End Function

在 .NET 中创建 XML 序列化器需要在运行时创建和编译 .DLL,所以我们**真的**希望尽量减少执行此操作的次数。使用共享/静态变量是实现此目的的一种方法,因为它只为每个应用程序实例创建一次。

示例网页

为了演示这一点,应用程序有两个页面,`AllCustomers.aspx` 和 `FindCustomers.aspx`。第一个页面在 `GridView` 中显示所有客户,并允许您选择、插入、更新和删除它们。

显示客户的 `DetailsView` 控件非常简单,因为我们使用 `AddressControl.ascx` 来显示和编辑 `AddressXML` 值。

AddressControl.ascx

`AddressControl.ascx` 对数据库或字段名一无所知,它只与 Address 类交互。它有一个可绑定的属性 `AddressXML`,我们将其绑定到数据库字段。在我的示例中,它绑定到 `Customer.AddressXML`,但也可以轻松地以相同的方式绑定到 `Order.DeliveryAddressXML`。

当控件显示时,XML 会被传入(`AddressXML` 被设置)。这会反序列化 XML 并将值复制到控件。当请求 `AddressXML` 值时(例如,用于插入或更新),会读取控件值,创建 Address 对象,序列化并作为字符串返回。

XML 数据上的 SQL 查询

在 SQL 2005 之前,反对在数据库中使用 XML 的一个论点是,文本字段中的 XML 数据不易查询。SQL 查询

SELECT * FROM Customers WHERE Town = 'London'
在传统方法中非常简单高效。如果 XML 数据在文本字段中,要查找城镇为“London”的客户,则需要读取所有客户,反序列化地址并检查值——这显然效率会低得多。

SQL 2005 支持查询 XML 数据(无论是类型化还是非类型化),因此这将转换为

SELECT * FROM Customers 
    WHERE AddressXML.value('(/Address/Town)[1]', 'nvarchar(1000)') = 'London'

“查找客户”页面展示了这种搜索方式的一个示例。您可以输入一个城镇并搜索具有该值的客户。数据源 `Customers.vb` 代码使用 XML 字段上的 XQuery 搜索。

请注意,我要求 Address 类提供实际的 XPath `Address/Town`,以防我们以后将“Town”更改为其他内容。这避免了将地址逻辑编码到 `Customer` 类中。

使用 XML 的好处

重用

将大部分地址结构和功能放在 Address 类中,我们现在可以实现重用。您的客户详细信息中需要发票地址吗?只需在数据库表中添加一个 `InvoiceAddressXML` 列,并在 Customer 类中添加支持代码以将其作为 `Customer.InvoiceAddress` 访问。

控件绑定等。

一个很酷的技巧是,我们可以将 `AddressXML` 视为一个单一属性,并将其传递给 `AddressControl` Web 用户控件。如果采用单独列的方法,我们将需要在地址控件中创建多个属性,并在数据绑定表单中使用控件时传递每个属性的值。

这是一个主要的好处,因为我以前不得不以这种方式创建一些非常复杂的用户控件,绑定 Line1、Line2 等真的非常耗时。

另一点是,Web 控件总是将值作为字符串传递。字符串的格式不正确(尤其是日期!)在区域设置可能导致问题时可能会成为一个真正的问题。XML 允许字符串数据通过并重新组装成安全的 .NET 数据类型。

灵活性

在这个例子中,我们只使用 `AddressXML` 来存储单一类型的数据。然而,理论上我们几乎可以将任何内容存储在 XML 字段中。回到我们之前的产品示例,我们需要为不同类型的产品存储不同的属性。假设我们的 `Products` 表可能包含 `ProductID`、`ProductName`、`ProductCategory` 和 `ProductAttributesXML`。

然后,我们将使用 `ProductCategory` 来决定在 `ProductAttributesXML` 中存储哪种类型的类。因此,对于 `ProductCategory`='数码相机',我们可以定义一个 `DigitalCameraAttributes` 类,其中包含 `Pixels`、`MaximumZoom`、`StorageType` 等字段,以及一个特殊的自定义控件来显示这些详细信息。同样,对于硬盘类别,可以定义一个名为 `HardDiskAttributes` 的类。

结论

我希望您觉得这种方法很有用。它提供了一种在使用关系数据库(如 SQL 2005)时处理面向对象代码的复杂性和灵活性的新方法。

© . All rights reserved.