在 Entity Framework Code First 中将多个 Table 与单个通用目的 Table 相关联

Relate Multiple Tables to Single General Purpose Table in Entity Framework Code First

很多时候我有一个其他实体包含的通用实体集合。我不想为每个需要它的父实体类型创建一个新的集合实体,但想重新使用一个通用实体。出于性能原因,我也不想像 this answer 那样显式定义多对多关系。最简单的示例是字符串集合。

public class MyString
{
    public Guid Id { get; set; }
    public string Value { get; set; }
}

public class MyEntity
{
    public Guid Id { get; set; }
    public virtual List<MyString> { get; set; }
}

public class MyOtherString
{
    public Guid Id { get; set; }
    public string Value { get; set; }
}

public class MyOtherEntity
{
    public Guid Id { get; set; }
    public virtual List<MyOtherString> { get; set; }
}

我真的很想将 MyStringMyOtherString 合并为一个实体:

public class GeneralPurposeString
{
    public Guid Id { get; set; }
    public string Value { get; set; }
}

public class MyEntity
{
    public Guid Id { get; set; }
    public virtual List<GeneralPurposeString> { get; set; }
}

public class MyOtherEntity
{
    public Guid Id { get; set; }
    public virtual List<GeneralPurposeString> { get; set; }
}

除了现在我要在 GeneralPurposeString 中为包含 GeneralPurposeString.

集合的每个实体添加一个额外的外键

我想要的是在 GeneralPurposeString table(但不是实体)上增加一个父类别列,以指定该项目属于哪个实体。我使用 Guid 作为主键,所以 table 看起来像这样:

CREATE TABLE [GeneralPurposeString]
(
    [Id] uniqueidentifier NOT NULL 
         CONSTRAINT PK_GeneralPurposeString PRIMARY KEY,
    [ParentEntityCategory] uniqueidentifier NOT NULL,
    [ParentEntityId] uniqueidentifier NOT NULL,
    [Value] nvarchar(MAX)
)

以及一些如何在 Code First 中指定 MyEntity 具有特定类别,并且 GeneralPurposeString 的集合使用该类别,而 MyOtherEntity 使用另一个类别(Guid)因为它是 GeneralPurposeString.

的集合

关键是 GeneralPurposeString 可以是任何其他实体中的集合,并且加载父实体和包含集合将自动加载,而无需显式指定类别。

所有这些的目的是

  1. 允许 .NET 代码具有 GeneralPurposeString 未在任何地方复制的代码(实际实用程序或业务逻辑代码)。这可能也可以通过继承和显式映射来完成,但是这仍然会在数据库中留下多个 table(请参阅 #2)。
  2. GeneralPurposeString 的数据库中只有一个 table。这更像是一个整洁问题。多个 table 的性能可能会更好,但是在 ParentEntityCategory/ParentEntityId 上建立索引并覆盖 Value 应该是查找的良好性能。
  3. 不必在需要的地方显式编码此关系和查找。

我在想,如果我能克服 #2 并在幕后使用单独的 table 并实现派生的 class,那将是最简单的方法。

所以只是:

public class GeneralPurposeString
{
    public Guid Id { get; set; }
    public string Value { get; set; }
}

// It's just a GeneralPurposeString with a fancy MyEntity membership pin
public class MyEntityString: GeneralPurposeString {}

public class MyEntity
{
    public Guid Id { get; set; }
    public virtual List<MyEntityString> Strings { get; set; }
}

// Cool GeneralPurposeStrings belong to MyOtherEntity
public class MyOtherEntityString: GeneralPurposeString {}

public class MyOtherEntity
{
    public Guid Id { get; set; }
    public virtual List<MyOtherEntityString> Strings { get; set; }
}

public class MyContext: DbContext
{
    public DbSet<MyEntity> MyEntities { get; set; }
    public DbSet<MyOtherEntity> MyOtherEntities { get; set; }
}

我不必将派生的 classes 添加到 DbContext 并且 tables 默认使用派生的 class 的复数命名, 所以它实际上非常简单。

我之前对父类别的思路需要额外的 coding/annotation,即使 EF 支持它也是如此。这纯粹使用约定,在注释或 OnModelCreating().

中不需要任何额外的东西

我目前没有发现额外的 table 有任何危害。我认为(目前)不需要将所有数据集中在一个 table 中进行报告,但这实际上取决于通用实体的类型,所以我可能需要在未来重新审视它,或者如果我确实需要一个 table.

中的数据,我可能会选择多对多路线

我还能拥有:

public static class GeneralPurposeStringExtensions
{
    public static void SassThatHoopyFrood(this GeneralPurposeString s)
    {
        // do stuff
    }
}