映射“一对多”关系的正确方法。在多个实体中具有相同关系时

Posted

技术标签:

【中文标题】映射“一对多”关系的正确方法。在多个实体中具有相同关系时【英文标题】:Correct way of mapping a 'one to many' relationship. When having the same relation in multiple entities 【发布时间】:2018-10-04 18:52:10 【问题描述】:

假设类和关系的结构如下:

class Document

    public List<Version> DocumentVersions  get; set; 
    // Other properties


class Register

    public List<Version> RegisterVersions  get; set; 
    // Other properties


class Version

    public int VersionNumber  get; set; 
    // Other properties

使用 EF Core 时,它​​将分别生成 3 个表,D、R 和 V,其中 V 将有 2 个 FK,一个用于 D,一个用于 R。

我的问题是:

EF Core 默认方法是否正确?这不会导致 V 没有 FK 的无效状态,因为两个 FK 都可以为空。 我读过this,它几乎回答了我的第一个问题,但它引出了另一个问题: 如何告诉 EF 遵循该方法:我是否必须为其每个所有者创建 V 的派生类型?或者有什么方法可以将单个实体映射到多个表并告诉 EF 哪些关系属于哪个表?

也许值得一提的是,我的示例过于简单,实际上我有 6 个实体使用同一个 V 实体。

【问题讨论】:

【参考方案1】:

所以,困境是:

A) 我应该在Version 中保留两个 FK 还是

B) 构建两个表DocumentVersionRegisterVersion 而不仅仅是Version

嗯,事实是你可以两者兼得。您只需要决定哪种方法更适合您的系统。让我们快速浏览一下。

方法 A

回答您的问题;是的,EF 的默认方法是正确的。在创建两个FK和建立两个表之间,它会创建两个FK。它只会在多对多关系的中间表的情况下创建一个额外的表。

不过,我始终建议我们自己创建所有 FK,而不是让 EF 为我们创建。这样我们就可以更好地控制关系的行为,并且还可以在我们的应用程序中访问 FK,因为它们是实体的属性。

public class Version

    [Key]
    public int VersionNumber  get; set; 

    public int? DocumentID  get; set; 
    public virtual Document Document  get; set; 

    public int? RegisterID  get; set; 
    public virtual Register Register  get; set; 

    //Other properties

由于Version 有一个PK,它可以创建没有任何FK 值的记录。如果您的业务模型允许这样做,请保持原样。您可以稍后提供一个 UI 来将“版本”分配给“文档”或“注册表”。

如果您想在Version 表中添加一些规则;例如,每条记录应至少有一个 FK 或只有一个 FK,您可以通过覆盖 DbContext 类的 ValidateEntity 方法(或可能通过数据库中的一些 sql 约束)来实现。

    protected override DbEntityValidationResult ValidateEntity(
        DbEntityEntry entityEntry, IDictionary<object, object> items)
    
        // validate entity from annotations
        var result = base.ValidateEntity(entityEntry, items);

        // custom validation rules
        if (entityEntry.Entity is Version && 
            (entityEntry.State == EntityState.Added || entityEntry.State == EntityState.Modified))
        
            Version version = ((Version)entityEntry.Entity);
            if (version.DocumentID == null && version.RegisterID == null)
                result.ValidationErrors.Add(new DbValidationError(null, "A document or register must be specified."));
        

        return result;
    

请注意,您可以创建自己的注释来验证您的实体属性。但这些仅限于单个属性。如果您想添加组合多个属性的验证,ValidateEntity 方法是我知道的唯一方法。

方法 B

有两种方法可以实现这种方法。首先是保留Version 表并在顶部添加两个中间表。

public class Document

    public virtual List<DocumentVersion>  Versions  get; set; 
    // other properties


public class Register

    public virtual List<RegisterVersion> Versions  get; set; 
    // other properties


public class Version

    [Key]
    public int VersionNumber  get; set; 

    //Other properties


public class DocumentVersion

    public int DocumentID  get; set; 
    public virtual Document Document  get; set; 

    public int VersionID  get; set; 
    public virtual Version Version  get; set; 

    // other properties


public class RegisterVersion

    public int RegisterID  get; set; 
    public virtual Register Register  get; set; 

    public int VersionID  get; set; 
    public virtual Version Version  get; set; 

    // other properties

这实际上允许多对多关系,但您可以将其用作一对多。 第二种方法是将Version抽象化(不是数据库表),新建两个表继承Version

public class Document

    public virtual List<DocumentVersion>  Versions  get; set; 
    // other properties


public class Register

    public virtual List<RegisterVersion> Versions  get; set; 
    // other properties


// don't make this a DbSet
public abstract class Version

    [Key]
    public int VersionNumber  get; set; 

    //Other properties


public class DocumentVersion : Version

    public int DocumentID  get; set; 
    public virtual Document Document  get; set; 

    // other properties


public class RegisterVersion : Version

    public int RegisterID  get; set; 
    public virtual Register Register  get; set; 

    // other properties

这是一个正确而清晰的一对多关系。

结论 最重要的是,您可以使用这两种方法中的任何一种,并根据您的需要进行更改。

我已经成功地使用了这两种方法,但我更喜欢第二种方法(以及抽象类继承)。第一种方法似乎更像是一种减少数据库资源或简化开发的方法,但现代数据库根本不会因为多几个表而受到压力,开发可能会变得不必要地复杂。此外,第二种方法允许通过单独向每个连接表添加更多属性来扩展关系的功能。对于您必须处理的 6 个实体,对我来说,采用第二种方法似乎更安全。我已经在具有许多文件类型和关系的应用程序中使用了这种方法,它总是非常直接且可扩展。每个 relashion 表中的那些额外属性也非常方便。

希望我能帮上忙, 编码愉快!

【讨论】:

谢谢,您的回答很有用。我将遵循抽象类的第二种方法。【参考方案2】:

我不认为这真的是一对多的关系,看here。

如果(例如)Document 有多个(例如列表)Versions,这将是一对多的关系。

如果您希望多个实体引用同一实体类型,您可以将外键显式放置在 DocumentRegister 类中:

class Document

    public Version DocumentVersion  get; set; 
    public int DocumentVersionId  get; set;  // Or whatever datatype your ID is
    // Other properties


class Register

    public Version RegisterVersion  get; set; 
    public int RegisterVersionId  get; set;  // Or whatever datatype your ID is
    // Other properties


class Version

    public int VersionNumber  get; set; 
    // Other properties

【讨论】:

你说得对,我的问题以错误的方式提出,这不是一对多的关系,我会纠正这个问题。

以上是关于映射“一对多”关系的正确方法。在多个实体中具有相同关系时的主要内容,如果未能解决你的问题,请参考以下文章

JPA:如何具有相同实体类型的一对多关系

使用一对多关系映射从邮递员那里获取输入?春天的Java

将具有一对多关系的 .Net Core 实体映射到 Angular 2 接口

hibernate 一对多 多对一映射关系

hibernate实体xml一对多关系映射

核心数据:删除最后一个具有一对多关系的实体