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

时间:2018-10-04 18:52:10

标签: c# orm entity-framework-core

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

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

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

2 个答案:

答案 0 :(得分:1)

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

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

如果您希望多个实体引用相同的实体类型,则可以将外键明确地放置在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
}

答案 1 :(得分: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,您可以通过覆盖ValidateEntity类的DbContext方法(或可能通过数据库中的某些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个实体,采用第二种方法对我来说似乎更安全。我在具有许多文件类型和关系的应用程序中使用了这种方法,并且它总是非常简单和可扩展的。每个关联表中的那些额外属性也非常方便。

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