【问题标题】:How do I design a class containing properties from lookup tables?如何设计一个包含查找表属性的类?
【发布时间】:2012-04-03 02:07:25
【问题描述】:

一些背景:这与我萌芽的愿望有关,即构建多层 Web 应用程序:

  1. C# ASP.NET 网络表单
  2. C# POCO 业务对象
  3. 某种 DAL...SQL(或者如果我能弄明白的话可能是 EF4)

例如,我真的不想要让我的表示层直接与 EF 实体对话的响应。

我已经使用 C# ASP.NET 和 SQL 进行自己的 Web 开发已有 10 年了,但在正式的 OOAD 方面我仍然是一个新手。我最近一直在热情地追求这项技能,但我还是新手,有些东西我无法完全理解。我希望有人能以一种能带来顿悟的方式来解释它:

假设我创建了一个以某种方式管理 People 的 Web 应用程序,并且我的 Person 对象必须具有诸如 FirstName、LastName、HairColor、EyeColor、Ethnicity、StateOrProvince 等属性。我' m 使用 SQL Server 进行持久性...所以常识会规定 People 表中的各个字段都是外键:

FirstName varchar(50)
LastName varchar(50)
HairColor tinyint
EyeColor tinyint
Ethnicity tinyint
StateOrProvince tinyint

显然,这意味着我对每个字段都有相应的查找表(即 HairColors 表、EyeColors 表、Ethnicities 表等),并且每个查找表都有一个 ID 和一个名称。当然,只要我想显示有关人员的任何有用信息,这些查找表中的名称字段就会与我的人员数据连接。

该网站的一些主要特点是:

1.) 在 Gridview 中枚举人员(FirstName、LastName、HairColor、EyeColor、Ethnicity、StateOrProvince)

2.) 在只读页面上显示个人的详细信息(名字、姓氏、头发颜色、眼睛颜色、种族、州或省)

3.) 允许用户在更新页面上更新个人的个人数据(FirstName、LastName、HairColor、EyeColor、Ethnicity、StateOrProvince)

案例#1 如果我在 gridview 中枚举 Person 对象的集合...每个 Person 实例都需要将其 HairColor、EyeColor、Ethnicity、StateOrProvince 属性显示为作为字符串才有意义(即来自SQL 查找表,而不是其 ID)。显然,我的 SQL sproc 将有一些 JOIN 来为我提供在每个 Person 实例中填充这些文本属性所需的适当字符串数据。

案例#2 同样,我的 sproc 将有一个 JOIN 来带回人类可读的属性名称作为字符串,我会使用 myPerson.HairColor、myPerson.EyeColor 等将它们显示在只读标签控件中。 p>

案例#3 在这里,我将显示一个页面,其中包含每个属性的下拉列表(即 value=HairColorId,Text=HairColorName)。我的直觉是使用每个属性的 IDs(类似于 myPerson.HairColorId),以便循环访问 DDL 项并选择一个代表 People 表当前持有的头发颜色的值对于这个人。如果用户在任何属性 DDL 中选择了不同的内容,我需要将适当的 SelectedId 值传递给 UPDATE sproc,并修改 People 表中此特定 Person 的值。

这让我想到了一个终极问题:

如何最好地设计一个 Person 对象,使其同时包含 HairColor、EyeColor、Ethnicity、StateOrProvince 的 ID 和名称,以便随后在 显示 时使用 名称 > 信息,但 ID 用于初始化更新 DDL 控件...并最终处理 更新

我对此进行了反思...我得出的结论是,我需要创建类来表示 HairColor、EyeColor、Ethnicity、StateOrProvince 属性。

然后是我的 Person 类,而不是这样的:

public class Person
{
    string FirstName { get; set; }
    string LastName { get; set; }

    int HairColorId { get; set; }
    string HairColorName { get; set; }

    int EyeColorId { get; set; }
    string EyeColorName { get; set; }

    int StateOrProvinceId { get; set; }
    string StateOrProvinceName { get; set; }
    string StateOrProvinceCode { get; set; }
}

将改为扩展为这样的内容:

public class HairColor
{
    int Id { get; set; }
    string Name { get; set; }
}

public class EyeColor
{
    int Id { get; set; }
    string Name { get; set; }
}

public class StateOrProvince
{
    int Id { get; set; }
    string Name { get; set; }
    string Code { get; set; }
}

public class Person
{
    HairColor HairColor { get; set; }
    EyeColor EyeColor { get; set; }
    StateOrProvince StateOrProvince { get; set; }

    public Person()
    {
        // how do I initialize a Person from a SQL data row?

    }
}

但是,如果我的 Person 类确实像上面那样......我到底如何最好地从我从 SQL 查询返回的给定数据行中初始化它(无论是单独还是在集合中)?我似乎记得我不应该在构造函数中更新东西(即 this.HairColor = new HairColor(dr["HairColorId"), dr["HairColorName"];)...所以我想知道如何打电话给

public static IEnumerable<Person> GetPeople()
{
    ...
}

在我的 BLL 中,可能会在将每个用户的数据添加到集合之前将其填满?

真的希望有人可以在这里给我一个“哈哈”的时刻......

【问题讨论】:

    标签: c# oop object


    【解决方案1】:

    我认为你有正确的方法,为那些支持实体创建类(尽管我会将 StateOrProvince 放在单独的 Address 实体中,也许所有这些特征都放在单独的 PersonTraits 实体中)。

    有很多方法可以解决这个问题。如果没有 ORM,请查看 Data Mappers(也位于 Dependent Mapping),它可用于从数据库查询映射到 Person 实例。这是映射器代码的概要:

    var row = ... // query database
    var person = new Person(row["FirstName"], row["LastName"]);
    person.EyeColor = new EyeColor(row["EyeColorID"], row["EyeColorName"]);
    ...
    

    (您也可以使用某种单独的Object Builder。)

    每当您更新一个人时,您都会更新所有支持信息以及使用相关实体的 ID。

    更新:像 EF4 这样的 ORM 非常强大,可以帮助您完成许多重复性任务(例如我描述的映射)。重要的是保持你的架构灵活,并且作为一个可交换层具有持久性。查看here 以获得一些指导。另外,我发现“领域驱动设计”这本书对于理解这种分离以及如何为您的实体建模非常重要。

    【讨论】:

    • 谢谢。将 StateOrProvince 封装在 Address 类中是对的。我认为一旦我真正开始构建它,这将是我的下一个合乎逻辑的步骤。不过,我从来没有想过在“PersonTraits”方面更进一步,因为它离数据库模式已经迈出了一大步。我想我太习惯于用我的 SQL 模型来思考了,我无法解放我的思想来思考对象模型。
    • 你会建议我使用 ORM 吗?大约 6 个月前,我在以前的测试站点上将 EF4 作为 DAL 搞砸了……但我认为我并没有为此做好心理准备。我对 OOP 的理解几乎达到了一个点,现在它对我来说可能更有意义。
    • 当然,像 EF4 这样的 ORM 非常强大,可以帮助您完成许多重复性任务(例如我描述的映射)。重要的是保持你的架构灵活,并且作为一个可交换层具有持久性。查看here 以获得一些指导。此外,我发现“领域驱动设计”这本书对于理解这种分离以及如何为实体建模非常重要。
    • @Jordão:用您对 EF4 的建议更新您的答案。 ...+1 为我。
    【解决方案2】:

    您是否查看过 EF 中的“导航属性”?它允许您将 ID 保留在主类(即 Person)中并通过导航属性引用字符串属性。例如,您将拥有:

    Person p = [从 EF 数据上下文中获取记录]

    p.state_id 将引用状态的数字 ID,而 p.State.Name 将是状态的字符串名称。 EF 负责加载引用的状态记录。如果您使用数据库优先并定义了外键,它甚至可以自动为您创建它们(有些工具可以将数据库优先转换为代码优先)

    【讨论】:

    • 我很确定这会让我的表示层直接与 EF 实体对话,对吧?正如我在问题开头(上下文区域)中提到的那样,我更愿意避免这种情况。从我所做的研究中,我确定将业务层从持久层抽象出来是一种最佳实践。演示 -> 业务 -> 持久性。我认为我尝试使用 SQL ID 的行为打破了这个模型......但这并不像我的表示层直接与实体对话那样“糟糕”?我显然不是专家……但这就是我收集到的。
    • 是的,您可以让它们与 EF 实体对话,但是在代码优先的情况下,您的 EF 实体只是 POCO 类,与您自己编写它们没有什么不同。是的,您可以在表示层创建完整的类层次结构,但是当表示类与 POCO EF 实体完全相同时,为什么要这样做。您可以将 EF POCO 类放入一个单独的程序集中,并让它成为所有不同层交换数据的工具。
    • 看看这个从 EDMX 创建 POCO 的工具:visualstudiogallery.msdn.microsoft.com/…
    【解决方案3】:

    我会将您的查找表镜像为枚举。然后,您将 id 和 name 都放在一个值中。如果名称包含不能在标识符中使用的字符,那么您可以轻松创建一个属性来处理附加数据。

    附加信息(例如示例代码,修改以适应,我用 VB 编写,因此转换为 C# 将是必要的):

    Namespace Company.Data
      Public Enum EyeColor As Int16
        Unknown = 0
        Brown = 1
        Blue = 2
        Green = 3
      End Enum
    
      Public Enum HairColor As Int16
        Unknown = 0
        Brown = 1
        Blond = 2
        Red = 3
        Pink = 4
      End Enum
    End Namespace
    
    
    
    Public Class Person
    
      Public Property EyeColor As EyeColor = EyeColor.Unknown
    
      Public Property HairColor As HairColor = HairColor.Unknown
    
    End Class
    

    由于您使用的是枚举,因此各个枚举值映射到您的数据库查找表键。所以你可以用aPersonObject.HairColor.ToString()得到你的Display,你可以用aPersonObject.HairColor得到ID

    您可以非常花哨并使用一些代码生成(mabey T4 模板)从数据库中的值自动创建您的枚举。

    【讨论】:

    • 非常感谢您的意见,但我现在太迟钝了,无法理解。我使用过枚举...但主要用作 Case 语句决策的常量。有没有一种方法可以修改您的回复,以提供一些可能会翻转我的“哈哈”开关的示例代码?如果我有一个 Person.cs 文件和一个 PersonManager.cs 文件,其中包含像 GetPeople() 或 GetPerson() 这样的静态方法......这些枚举将存在于哪里......它们将如何与我的 Person 类集成?
    • 添加了一些额外的内容。如果您需要更多说明,请告诉我。
    • 外键的全部意义在于列表包含在数据存储中,而不是精装。
    • @BenVoigt:不,外键的全部意义在于数据完整性和一致性。特别是在这种情况下,查找表将预先加载所有可能的值。如果要经常更新外键表,我当然不推荐枚举。但在这种情况下,这对我来说似乎是最好的选择。
    • @Boo:如果所有有效值都是先验已知的,则有更便宜的方法来强制执行范围约束。我在这个问题中看不到任何内容表明查找表是不变的。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2017-09-02
    • 2019-11-18
    • 2020-05-22
    • 2016-02-29
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多