我创建了一种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列,因此如何创建关系?可能有什么问题吗?
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; }
}
如果对此用法有任何疑问,也可以让我知道吗?谢谢...
答案 0 :(得分:0)
每当设计类时,您都应该知道类的真正含义。
您的DbSet<...>
代表数据库的一个表。 DbSet
中的类将该表的列表示为非虚拟属性,并将表之间的关系表示为虚拟属性。
在我看来,您想要数据库中有两个表:一个带有Tools
的表和一个带有Priorities
的表。您目前认为Tools
的某些列也在Priorities
中。因此,您打算创建一个通用的基类,其名称比BaseModel
更好。
您不能为通用基类发明合适的名称这一事实应该警告您,也许这两者之间并没有真正的共同点。您确定,如果描述一个Tool
,那不是偶然的事,因为它具有与Priority
相同名称和类型的某些属性
例如。如果要定义Tools
行和Priorities
行,您会说Tool
中使用了每个Project
,而每个Priority
是Project
。这是系统设计的一部分。根据您的定义,Tools
没有其Project
即使如此,根据您的定义,每个工具都应该有一个“唯一标识方法”,您决定为此使用整数ID。同样,您决定为优先级设置一个整数ID。但是,Tool
表中的一行是否固有,标识的类型等于优先级的标识的类型?如果有人告诉您Tool
有一个Guid ID,而Priority
有一个整数ID,您是否希望您的设计一文不值?
当然不能:您的设计应该足够健壮,以至于数据库表中的细微变化都会导致您的设计中的细微变化!
1)我可以对工具和优先级实体使用ID吗?
答案:是的,您可以将Id放在基类中,并从派生类中忽略它。但是,这将增加一个约束,即每个派生类型的表中代表ID的列的名称和类型都应相同。
因此:如果您不希望局限于此:不要这样做,请从基类中删除ID并将其放在派生类中。如果以后有人决定进行较小的更改以重命名该列,或者给它提供其他类型,则您的更改将很小。
当然,这也适用于所有其他属性:如果它们“通过巧合”相同,则将值放在派生类中。如果Tools
和Priorities
都具有相同的典型特征,请将其放在基类中。
要检测项目的工具与项目的优先级之间的相似性对我来说并不容易,因此我将切换到另一个示例。
假设您有一个包含“教师和学生”的数据库。有些属性对于教师是唯一的,而某些属性对于学生是唯一的。但他们也有一些共同点,并非巧合:老师和学生都是人,都有名字,生日,可能还有地址等。如果后来有人决定某个地址有一个额外的字段,指示该地址的GPS坐标,则只需更改一个班级。
结论:仅将基类中所有派生类所固有的属性放进去,而不是巧合
根据您的设计,“工具”和“优先级”均属于项目。如果在上一步之后您确定这是它们唯一的共同点,那么您将很少选择一堆同时包含“工具”和“优先级”的对象。
在学校数据库中,将学生和教师分成一堆人是很正常的,其中每个人都有一个地址,而每个地址中将居住零个或多个人(一对多)< / p>
// 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内部的外键),则几乎不需要更改。看看您想添加新的Person类型:Parent,需要多少更改?
结论:如果您有一个基类,则每个派生类都应在另一个表中引用一个项目:将外键放在基类中。但是,如果这种关系不是所有派生项所固有的,则将外键放在派生类中。
请记住:DbContext中的每个DbSet都将成为一个表。如果您未将“工具和优先级”指定为单独的表,则不会使用Table per concreate class (TPC),而是使用table per hierarchy (TPH):“工具和优先级”都将在一个表中
由于在表中得到的所有未使用的空值,我很少使用TPH。
如果您最经常询问“ ...的老师”和“ ...的学生”,则应将其保存在单独的表格中。基类的列也位于这些单独的表中。
如果您最经常询问“ ...的人”,则“人”可能是学生或教师,请考虑使用table per type (TPT):“人”表和具有“人”外键的“教师”表表以及带有外键的学生表。所有基类属性都在“人员”表中。
很容易看到,如果您要求“ Persons that ...”,TPT将仅查询一个表,而对于TPC,则需要查询Teachers表和Students表并将结果连接起来。
但是,如果您要求“ Students that”,则TPT将需要加入Persons表和Students表。 TPC的速度更快:只访问一个表。