也许问题之一是您立即开始考虑表格、ID 和外键等。如果您考虑对象及其彼此之间的关系,通常会容易得多。
阅读您的图片有点困难。从文本中我收集到您有帐户的概念,显然有三种类型的帐户:普通帐户、培训师和健身房所有者。这些账户有区别吗?健身房老板有普通用户没有的属性吗?还是只是某种身份证明。
从您的绘图看来,普通用户具有用户名、密码和访问级别。培训师也有这些属性吗?
如果是这样,那么在普通的面向对象设计中,你会说它是一个聚合:培训师、普通人和健身房老板都有一个用户名、一个密码和一个访问级别。聚合也经常被称为组合。有一点点不同,但对于本次讨论来说,这并不重要。
除了聚合/组合(对象有一个帐户),您还可以考虑继承:对象是一个帐户:培训师帐户、健身房所有者帐户和普通帐户是所有不同类型的帐户的对象,有一个用户名称、密码和访问级别。
在此示例中,聚合(培训师有一个帐户)或继承(培训师帐户是一个帐户)之间没有太大区别。通常建议是倾向于聚合而不是继承enter link description here
请注意:如果您的三个帐户之间的唯一区别是某个属性的值,请考虑将它们全部设为同一类型,并使用属性 AccountType 为您提供有关它是普通帐户还是健身房所有者帐户的信息或培训师帐户。
如果这三者之间的唯一区别是访问级别,则不要创建帐户类型。访问级别将用作帐户类型。
到目前为止,我只是在谈论面向对象的设计。设计好对象后,您可能会考虑将它们放入数据库中。
让我们假设教练、健身房老板和普通人真的是不同的东西,具有不同的属性。您的课程类似于:
public enum AccessLevel
{
None,
...,
FullAccess,
}
public class Account
{
public string UserName {get; set;}
public string Password {get; set;}
public AccessLevel AccessLevel {get; set;}
}
public class Trainer
{
public .. some trainer properties {get; set;}
// a trainer has an account
public Account Account {get; set;}
}
public class GymOwner
{
public ... some gym owner properties {get; set;}
public Account Account {get; set;}
}
如果您的设计是这样的,您会发现至少您会有一张健身房老板桌和一张教练桌。但是如何处理帐户。
一种解决方案是将帐户放在单独的表中,并为教练和健身房所有者添加一些内容,这样如果您有教练,您就知道帐户表中的哪个项目属于该特定教练。
此设计中通常不使用此方法。如果对象 A 和对象 B 是一对一的关系:一个 A 属于一个 B,一个 B 属于一个 A,那么实际上它们可以放在一张表中。如果您使用实体框架并且您已经定义了上述类,则帐户属性将作为列添加到 trainer 表和作为列添加到 gym owner 表。优点是获取有关培训师的信息更快,因为这将只涉及访问一个表。
关于主键。每个表中的每个元素都应该只有一个主键。这是标识对象的属性。如果您有密钥,您可以非常快速地访问所有其他属性。为了让您更容易理解 Account 的作用,我在原始代码中省略了 Id。
但既然我们已经决定最好让 Trainers 和 GymOwners 拥有一个帐户,那么将有两个表:Trainers 和 GymOwners。这些是唯一具有主键的元素。课程如下:
public class Account
{
public string UserName {get; set;}
public string Password {get; set;}
public AccessLevel AccessLevel {get; set;}
}
public class Trainer
{
public int Id {get; set;}
public .. some trainer properties {get; set;}
// a trainer has an account
public Account Account {get; set;}
}
public class GymOwner
{
public int Id {get; set;}
public ... some gym owner properties {get; set;}
public Account Account {get; set;}
}
请注意,Account 没有表,因此 Account 不需要密钥。
那么你什么时候需要外键
如果一个 Trainer 不会有一个帐户,而是几个帐户,可能是很多帐户,通常称为帐户集合,那么我们不能再将帐户保存为 Trainer 表中的列。我们必须将 Trainer 的所有帐户放在一个单独的表中,并告诉每个帐户它属于哪个 trainer。该帐户获得了一个外键,用于他所属的培训师的主键。
在数据库术语中,这称为一对多关系:一个培训师有多个帐户。
对于实体框架,类应该是这样的:
public class Account
{
public int Id {Get; set;}
public int TrainerId {get; set;}
public string UserName {get; set;}
public string Password {get; set;}
public AccessLevel AccessLevel {get; set;}
}
public class Trainer
{
public int Id {get; set;}
public .. some trainer properties {get; set;}
// a trainer has an account
public virtual ICollection<Account> Accounts {get; set;}
}
public class MyDbContext : DbContext
{
public DbSet<Trainer> Trainers {get; set;}
public DbSet<Account> Accounts {get; set;}
}
请注意,由于 Account 有自己的表,因此它已获得主键。
除此之外,它还在属性 TrainerId 中获得了一个外键。
我还添加了一个派生自 DbContext 的类来访问这些表。直到知道我只有带有培训师的表格和带有帐户的表格。每个表称为一个 DbSet,DbSet 的类型通知实体框架表中的列。
因为 Trainer 有一个虚拟的 ICollection 的 Accounts,实体框架知道 Trainer 和 Account 之间是一对多的关系,并且由于 namer TrainerId,实体框架知道 TrainerId 是主键的外键该帐户所属的培训师。
因此,如果您有培训师的 id,您可以使用以下 Linq 语句获取该培训师的所有帐户:
int trainerId = GetMyTrainerId();
IEnumerable<Account> accountsOfTrainer = dbContext.Accounts
.Where(account => account.TrainerId == trainerId);
从 Accounts 集合中,获取属性 TrainerId 等于 trainerId 的所有记录
所以现在您知道如何设计主键并让外键指向它了。
但是健身房老板呢?如果健身房老板只有一个账户,就让它有一个账户(组成)。但是,如果您的健身房所有者也有一系列帐户怎么办。
如果您只是将健身房所有者帐户添加到帐户表中,那么您会遇到麻烦。外键指向哪个 Id?要在 Trainer 表或 Gym Owners 表中作为主键?
最安全的方法是创建 GymOwnersAccount 和 TrainersAccount。他们每个人都有自己的带有外键的表。 GymOwnersAccount 将具有 GymOwners 表的外键,TrainersAccount 将具有 trainers 表的外键。
在这里,您还可以决定让 GymOwners 帐户拥有一个帐户,但更自然地说 GymOwners 帐户是一种特殊类型的帐户,因此派生自 Account。
public class Account
{
public string UserName {get; set;}
public string Password {get; set;}
public AccessLevel AccessLevel {get; set;}
}
public class GymOwnerAccount : Account
{
public int Id {get; set;}
public int GymOwnerId {get; set;}
}
public class TrainerAccount : Account
{
public int Id {get; set;}
public int TrainerId {get; set;}
}
public class Trainer
{
public int Id {get; set;}
public .. some trainer properties {get; set;}
// a trainer has an account
public virtual ICollection<Account> Accounts {get; set;}
}
public class GymOwner
{
public int Id {get; set;}
public ... some gym owner properties {get; set;}
public virtual ICollection<Account> Accounts {get; set;}
}
public class MyDbContext : DbContext
{
public DbSet<Trainer> Trainers {get; set;}
public DbSet<Account> GymOwnerAccounts {get; set;}
public DbSet<Account> TrainerAccounts {get; set;}
}
还有其他可能的解决方案,例如给每个帐户两个外键,一个给健身房老板,一个给教练,你总是需要将一个外键设置为 0。很容易看出这可能会导致维护问题,虽然它不会给您带来任何好处,但我建议您坚持为 GymOwnerAccounts 和 TrainerAccounts 使用单独的表格。
现在我已经进入了数据库中的继承领域,您可以配置大量项目。一篇对我理解实体框架,以及类、继承、组合如何转移到数据库有很大帮助的文章是Entity Framework Code First