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

问题描述 投票:0回答:2

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

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 实体。

c# orm entity-framework-core
2个回答
2
投票

所以,困境是:

A) 我应该在

Version
还是

中保留两个FK

B) 构建两个表

DocumentVersion
RegisterVersion
而不是仅仅
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 个实体,我认为采用第二种方法似乎更安全。我在具有多种文件类型和关系的应用程序中使用了这种方法,它总是非常直接且可扩展。每个关系表中的那些额外属性也非常方便。

希望我能帮忙,

快乐编码!


1
投票

我认为这并不是真正的一对多关系,请看这里

如果(例如)

Document
有多个(例如一系列)
Version
,那么这将是一对多关系。

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

Document
Register
类中:

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
}
© www.soinside.com 2019 - 2024. All rights reserved.