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

将 DataSet 转换为泛型列表

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.57/5 (6投票s)

2009年7月18日

CPOL

7分钟阅读

viewsIcon

79748

downloadIcon

1017

提供了一个简单的实用框架,有助于将 DataSets 转换为通用 Lists。

引言

在本文中,我将介绍一组类,可用于从指定的 DataSet 对象创建通用的 List<T>。当我们将某个指定实体的加载数据放入 DataSet 中,并需要轻松构建该实体类型的通用 List<> 时,这可能非常有用。

为了演示用法,我们将考虑以下场景:我们需要运行一个通讯录应用程序。我们使用数据库进行存储,并创建了以下表:

AddressBook.JPG

  • Contact - 包含联系人的详细信息
  • Address - 包含地址的详细信息
  • ContactAddress - 将联系人映射到多个地址

有了这个数据模型,我们将编写两个存储过程,一个用于获取系统中的所有联系人,另一个用于根据联系人 ID 获取联系人详细信息。在列出所有联系人时,我们只需要联系人 ID 和联系人姓名即可显示。在获取联系人详细信息时,我们需要所有联系人详细信息以及该联系人拥有的地址列表。

  • GetContactById - 获取联系人详细信息。同时获取属于该联系人的所有地址。
  • CREATE PROCEDURE dbo.GetContacts
    AS
        SET NOCOUNT ON
        -- Select all contacts
        SELECT     ContactId, ContactName
        FROM         Contact 
        RETURN
  • GetContactById - 获取联系人详细信息。同时获取属于该联系人的所有地址。
  • CREATE PROCEDURE dbo.GetContactById
    (
        @ContactId UNIQUEIDENTIFIER
    )
    AS
        SET NOCOUNT ON
        -- The first result set contains the contact details
        SELECT     ContactId, ContactEmail, ContactName
        FROM         Contact
        WHERE     (ContactId = @ContactId)
        
        --The second result set contains the address details    
        SELECT     Address.AddressId, Address.AddressLine2, 
                   Address.AddressLine1, Address.AddressLine3, Address.AddressType
        FROM         Address INNER JOIN
                             ContactAddress ON Address.AddressId = ContactAddress.AddressId
        WHERE     (ContactAddress.ContactId = @ContactId)

我们为 ContactAddressContactInfo 定义了简单的实体类型。Citrus.Data.Core 命名空间包含帮助类,这些类将使我们能够轻松地将这些实体类型与这些存储过程的结果进行映射。

在我们的上下文中,实体是代表业务对象的任何东西。ContactContactInfoAddress 是实体类型。实体类型可以包含其他实体类型(无论是单个实例还是列表)。Contact 实体包含一个 Address 实体列表。

伪逻辑

核心类负责处理 DataSetDataTable 的所有迭代细节,并将 DataColumn 映射到实体属性。它提供了函数,这些函数随后可以从指定的 DataSet 加载任何给定的实体对象。

虽然核心类可以直接与您现有的实体类一起使用,但如果您需要,也可以通过在实体属性上标记 Data* 属性来更改它们的行为。这将在下一节中详细介绍。例如,您可以使用该属性上的 DataColumn 属性让 EntityLoader 知道某个特定属性映射到特定的数据列。

EntityLoader 类的一个关键方法是 **Load<T>(DataSet dataSet)**。此方法需要包含必要数据的 DataSet 以及我们需要创建实体列表的类型 T。我们从数据集的第一个数据表中开始加载过程。

  1. 检查提供的实体类型并获取其所有属性的列表。
  2. 对于每个属性,检查我们是否可以将该属性映射到当前数据表中的指定列。这称为列属性映射。
  3. 创建该实体类型的新列表。
  4. 对于当前数据表中的每个数据行
    • 创建一个类型为 T 的实体。
    • 使用我们创建的映射,根据当前行的数据,加载新实体所有简单的属性(stringintbooldoubleGuid 等)。
    • 对于当前实体包含的所有复杂属性(另一个实体或实体列表)
      • 如果该属性是一个实体,则要加载的数据将存在于当前数据行本身,因此获取包含实体的映射并对其进行填充。
      • 如果该属性是一个实体列表,则当前数据表将不包含该数据。如果指定了可以从中加载此包含实体列表的数据表,则对该数据表执行步骤 1-4。
    • 一旦实体所有属性都加载完毕,就将其添加到实体列表中。
  5. 返回实体列表。

完整示例及支持的功能

下载的示例包含一个示例网站应用程序,演示了 EntityLoader 类的设置和用法。

注意:下载示例包含核心程序集、示例 Web 应用程序以及独立的通讯录数据库。它还包括 Microsoft Enterprise Library 4.1(用于数据访问)的二进制文件。该解决方案是在 VS 2008 Express Edition 中创建的。

在通讯录的示例应用程序中,我们有以下实体:

/// <summary>
/// Entity to get information about all contacts
/// </summary>
public class ContactInfo
{
    [DataColumn("ContactId")]
    public Guid Id { get; set; }

    [DataColumn("ContactName")]
    public string Name { get; set; }
}

/// <summary>
/// A simple contact entity
/// </summary>
public class Contact
{
    [DataColumn("ContactId")]
    public Guid Id { get; set; }

    [DataColumn("ContactName")]
    public string Name { get; set; }

    /// <summary>
    /// Note that email address has a private set
    /// EntityLoader will not be able to load the
    /// email address in this case
    /// </summary>
    [DataColumn("ContactEmail")]
    public string EmailAddress { get; private set; }

    /// <summary>
    /// The list of addresses if from the next table
    /// </summary>
    [DataTable(DataTableSource.Next)]
    public List<Address> Addresses { get; set; }
}

/// <summary>
/// In this entity we use implicit column mapping
/// EntityLoader will map the column names from the data
/// set against the property names
/// </summary>
public class Address
{
    public string AddressLine1 { get; set; }
    public string AddressLine2 { get; set; }
    public string AddressLine3 { get; set; }
    public string AddressLine4 { get; set; }

    /// <summary>
    /// This is an example of custom data conversion
    /// The address type is stored in the database as
    /// an int, but the entity uses a string form
    /// </summary>
    [DataConverter(typeof(AddressTypeConverter))]
    public string AddressType { get; set; }
}

这三个类展示了允许属性的各种组合用法。在 ContactInfo 类中,我们使用 DataColumn 属性来显式指定属性应映射到哪个数据列。在 Contact 类中,我们使用 DataTable 属性让 EntityLoader 知道 Address 数据列表存在于另一个数据表中(在本例中是下一个)。在 Address 实体类中,我们看到了基于属性名称的隐式列映射示例。我们还使用 DataConverter 属性来指定某个属性需要额外工作才能填充 - AddressType 在数据库中存储为 int,但 Address 实体为此有一个 string 值。下面显示了 AddressTypeConverter 类的实现。

/// <summary>
/// This is a converter class that allows the address type
/// property of the address entity to be loaded
/// </summary>
public class AddressTypeConverter : DataConverterBase<string, int>
{
    public override string GetEntity(int entityData)
    {
        switch (entityData)
        {
            case 1: return "Home";
            case 2: return "Office";
            default: return "Unknown";
        }
    }

    public override int GetEntityData(string entity)
    {
        switch (entity)
        {
            case "Home": return 1;
            case "Office": return 2;
            default: return -1;
        }
    }
}

这些属性有助于建立数据列与实体属性之间的映射关系。

数据访问层

示例应用程序具有一个基于 Microsoft Enterprise Library 4.1 Data Access Application Block 构建的简单数据访问层。

数据访问层定义如下:

/// <summary>
/// The simple data access layer
/// </summary>
public static class AddressBook

并具有静态构造函数如下:

/// <summary>
/// The static contructor for our data access object
/// initializes the entity definition provider and adds
/// all the entity types that make up our data model
/// </summary>
static AddressBook()
{
    CachedEntityDefinitionProvider addressBookEntityProvider = 
                            new CachedEntityDefinitionProvider();

    addressBookEntityProvider.AddEntityType(typeof(ContactInfo));
    addressBookEntityProvider.AddEntityType(typeof(Contact));
    addressBookEntityProvider.AddEntityType(typeof(Address));
    
    // Set our custom provider to the inspector
    EntityInspector.EntityProvider = addressBookEntityProvider;
}

CachedEntityDefinitionProvider 是一个简单的扩展类,用于告知实体加载系统系统中哪些类型是实体类型。在我们的示例中,我们指定 ContactContactInfoAddress 类型是实体类型。您可以创建自己的实体定义提供程序版本,并通过实现 IEntityDefinitionProvider 接口将其挂接到 EntityInspectorEntityProvider 属性。

利用 EntityLoader 的核心方法实现为 Database 类型上的扩展方法,如下所示:

/// <summary>
/// Simple extension for Database type to call EntityLoader Load method
/// </summary>
static List<T> LoadEntity<T>(this Database db, 
       string commandName, params object[] commandArguments)
{
    return EntityLoader.Load<T>(db.ExecuteDataSet(commandName, commandArguments));
}

如果您没有 EL4.1,可以在示例中更改此设置。

最后是数据访问方法:

public static List<ContactInfo> GetContacts()
{
    return DbContext.LoadEntity<ContactInfo>(StoredProcedures.GetContacts);
}

public static List<Contact> GetContactById(Guid id)
{
    return DbContext.LoadEntity<Contact>(StoredProcedures.GetContactsById, id);
}

请注意,所有方法都返回指定实体的列表。

数据属性

本节列出了所有可用的属性:

  • DataColumn - 应用于属性。指定数据表中哪个数据列映射到此属性。
  • DataTable - 应用于 list<> 属性。指定应从中填充列表的源数据表。使用 DataTable(DataTableSource.Next)DataTable(2) 来指定相对表位置。
  • DataConverter - 应用于属性。告知 EntityLoader 系统需要使用特定的数据转换器类来加载属性数据。
  • EntityLoader - 应用于实体类。这控制了检查属性以从 DataSet 加载数据的方式。默认情况下,将使用给定实体的所有属性进行列映射和数据加载。您可以通过为 EntityLoader 属性指定 InspectionPolicy 参数来更改此行为。可能的使用方式是 EntityLoader(InspectionPolicy.OptIn)EntityLoader(InspectionPolicy.OptOut)。如果指定了 OptIn,则需要使用 DataInclude 属性显式标记属性,以确保 EntityLoader 将使用该属性。如果指定了 OptOut,则会检查所有属性,除非标记了 DataExcludeOptOut 是默认行为。

改进

当然,这只是一个非常早期的实现。在参数验证、更好的异常处理等方面还有很多工作要做。

Citrus.Data.Core 程序集还包含一个实用类,它可以执行与我们在此看到的相反的操作,即将 List<T> 转换为 DataSet。它尚未完成,但对于简单实体(不包含实体列表属性的实体)有效。

如果您能提出一些建议,请随时提出。

参考文献

在可能的情况下,我在代码本身中包含了引用。其中一些值得一看:

© . All rights reserved.