【问题标题】:Inheritance with EF Code First - Table per Concrete Type (TPC)使用 EF 代码优先继承 - 每个具体类型 (TPC) 的表
【发布时间】:2019-05-29 01:39:02
【问题描述】:

我创建了这样一种Table per Concrete Type (TPC)结构如下图:

以下是这里使用的实体:

public abstract class BaseModel : MyOtherBaseClass
{
    [Key]
    public int Id { get; set; }

    //Foreign key for Project
    public int ProjectId { get; set; }

    public int Sequence { get; set; }

    public string Name { get; set; }

    public string IconUrl { get; set; }

    //Navigation Properties ####################
    public virtual Project Project { get; set; }
}


[Table("Tool")]
public class Tool : BaseModel
{
    [Key]
    public int Id { get; set; }

    public string ToolBrand { get; set; }

    //Navigation Properties ####################
    //public virtual Project Project { get; set; }
}


[Table("Priority")]
public class Priority : BaseModel
{
    [Key]
    public int Id { get; set; }

    public string PriorityCode { get; set; }

    //Navigation Properties ####################
    //public virtual Project Project { get; set; }
}

此时我对以下问题感到困惑:

1) 我可以将 ID 用于工具和优先级实体吗?而且这绝对没有必要吗?

2) 我在 BaseModel 类中使用 FK (ProjectId) 和相关表 Project。但是,由于 ProjectId 列将在 Tool 和 Priority 表中创建,如何创建关系?会不会有什么问题?

3) 如果我不喜欢 Fluent API,我应该在上下文中除了 BaseModel 实体添加 Tool 和 Priority 实体吗?因为在某些资源中添加了子类,而在其他一些资源中则没有。哪一个是真的?

public class EntityContext : DbContext
{
    public DbSet<BaseModel> BaseModel { get; set; }
    // ? public DbSet<Tool> Tool { get; set; }
    // ? public DbSet<Priority> Priority { get; set; }
}

如果对这种用法有任何问题,您也可以告诉我吗?谢谢...

【问题讨论】:

  • 如果 db 中的 Tool 和 Priority 表没有 BaseModel 的列,这个 TPC 会怎样?
  • @VidmantasBlazevicius 对不起,我不明白你的意思?和1-2-3点有关系吗?
  • 对不起,我可能误会了你。我认为该图是 db 架构,它可能只是一个类图。

标签: c# asp.net asp.net-mvc entity-framework ef-code-first


【解决方案1】:

每当您设计类时,您都应该了解这些类真正代表什么。

您的DbSet&lt;...&gt; 代表您的数据库中的一张表。 DbSet里面的类把这个表的列表示为非虚属性,表之间的关系表示为虚属性。

在我看来,您希望数据库中有两个表:一个带有Tools 的表和一个带有Priorities 的表。您目前认为Tools 的某些列也在Priorities 中。因此,您打算创建一个通用的基类,它的名字没有比BaseModel 更好的名字了。

您无法为公共基类发明一个合适的名称这一事实应该警告您,这两者之间可能没有什么真正的共同点。你确定吗,如果你描述一个Tool,那不是一个事件,它有一些与Priority 相同的名称和类型的属性

例如。如果定义Tools 的行和Priorities 的行,您会说每个Tool 都在Project 中使用,每个PriorityProject 的优先级。这是系统设计的一部分。根据您的定义,没有Tools 没有Project 是没有意义的

即便如此,根据您的定义,每个工具都应该有一个“唯一标识方法”,您决定为此使用整数 Id。同样,您决定为优先级设置一个整数 Id。但是,您的Tool 表中的一行是否固有标识的类型等于优先级标识的类型?如果有人告诉你 Tool 有一个 Guid Id 而一个 Priority 有一个整数 Id,你会希望你的设计一文不值吗?

当然不是:您的设计应该非常健壮,以至于您的数据库表中的微小变化应该会导致您的设计发生微小的变化!

哪些属性应该在基类中

1) 我可以将 ID 用于工具和优先级实体吗?

回答:是的,您可以将 Id 放在基类中并从派生类中省略它。但是,这会增加一个约束,即每个派生类型都应具有相同的名称和类型,以用于表中表示 Id 的列。

因此:如果您不想局限于此:不要这样做,请从基类中删除 Id 并将其放入派生类中。如果以后有人决定对列进行小改动,或者给它一个不同的类型,那么你的改动也会很小。

当然,这也适用于所有其他属性:如果它们“巧合”相同,则将值放入派生类中。如果是 ToolsPriorities 共同的典型东西,请将其放在基类中。

我不容易发现项目的工具和项目的优先级之间的相似之处,所以我将切换到另一个示例。

假设您有一个包含教师和学生的数据库。有些属性是教师独有的,有些属性是学生独有的。但他们也有一些共同点,这并非巧合:教师和学生都是人,有名字、生日,可能还有地址等。如果后来有人决定地址有一个额外的字段,指示地址的 GPS 坐标,那么你只需要改变一个类。

结论:只在基类中放置所有派生类固有的属性,并非巧合

外键放在哪里?

根据您的设计,工具和优先级都是属于项目的东西。如果在上一步之后您认为这是它们唯一的共同点,那么您很少会使用一堆同时包含工具和优先级的对象。

在学校数据库中,将学生和教师放入一堆人中是很正常的,其中每个人都有一个地址,每个地址中都会有零个或多个人(一对多)

// this will be a row in a table, hence it has an Id
class Address
{
     public int Id {get; set;}
     public string ZipCode {get; set;}
     public string Street {get; set;}
     ...

     // on every address live zero or more Persons (one-to-many)
     public virtual ICollection <Person> Persons {get; set;}
}

// this will not be a row in a separate table, hence it has no ID
class Person
{
     public string Name {get; set;}
     public DateTime Birthday {get; set;}
     ...
     // every Person lives at an Address, using foreign key
     public int AddressId {get; set;}
     public virtual Address Address {get; set;}
}

class Teacher : Person
{
    public int Id {get; set;}
    ...
}
class Student: Person
{
    public int Id {get; set;}
    ...
}

因此您将拥有三个表:地址、教师、学生。教师和学生都将具有人的属性。他们都住在同一个地址。

如果我们决定为教师或人员添加一列,看看需要做多少改动?如果您希望教师 ID 是 GUID,或者 Address.Id 是字符串(期望地址的主键和 Person 内部的外键发生变化),那么几乎不需要更改。如果您想添加一种新类型的人:父母,看看需要做多少改变?

结论:如果您有一个基类,其中每个派生类都应该引用不同表中的项目:将外键放在基类中。但是,如果这种关系不是所有派生项都固有的,请将外键放在派生类中。

3 我应该在上下文中添加除 BaseModel 实体之外的实体吗?

记住:DbContext 中的每个 DbSet 都将成为一个表。如果您不将工具和优先级指定为单独的表,您将不会使用Table per concreate class (TPC),而是使用table per hierarchy (TPH):工具和优先级将在一个表中

我很少使用 TPH,因为我在表中得到了所有未使用的空值。

如果您经常询问“...的老师”和“...的学生”,您应该将它们放在单独的表格中。基类的列也在这些单独的表中。

如果您最常询问“...的人员”,其中人员可能是学生或教师,请考虑使用table per type (TPT):人员表和教师表,其中人员表具有外键,以及带有外键的学生表。所有基类属性都在 Persons 表中。

很容易看出,如果您询问“Persons that...”,TPT 只会查询一个表,而对于 TPC,您需要查询 Teachers 表和 Students 表并连接结果。

但是,如果您要求“...的学生”,TPT 将需要加入 Persons 表和 Students 表。 TPC 在这里更快:只访问一个表。

【讨论】:

  • 非常感谢您完美而详细的解释。关于你的回答>>>
  • 1) 相关表名给错了,不好意思。我更正了它的名称,因为它应该是 >>> 项目 :)
  • 2) 关于您的第二条评论的结论,我检查了数据库中的派生类,并获得虽然我没有为这些派生类创建 FK 关系将 FK 添加到这些类中(仅添加到基类中,如我的问题所示)。在这个场景中我应该把外键放在派生类中吗?
  • 3) 关于第三点,我决定使用 TPC,在这种情况下,我认为我应该将 Tool 和 Priority 实体添加到我的 DbContext 中,而不是添加 BaseModel 实体。之后,将在数据库中创建 2 个表。这是真的吗?
  • 首先让你的设计正确。决定是否要 TPC、TPT 等,尤其是:为什么要这种派生而不是另一种派生?为什么是派生而不是聚合:您最常使用哪些查询和更新?阅读有关 TPC TPT 的链接。看看你实现的代码是否真的做这种表。使用适当的 fluent API 确保表之间的关系符合您的要求
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-12-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多