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



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

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 都可以为空。
  • 我已经读过这篇文章,它几乎回答了我的第一个问题,但它让我想到了另一个问题:
    • 如何告诉 EF 遵循该方法:我是否必须为其每个所有者创建 V 的派生类型? 或者有什么方法可以将单个实体映射到多个表并告诉 EF 哪些关系属于哪个表?

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

所以,困境是:

A( 我应该保留两个 FKVersion还是

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

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

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

如果(例如(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
}

最新更新