EF Core 继承:对多个关系使用相同的 FK 列

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

我目前正在开发一个票证系统,应该可以将文档链接到票证。票证有不同类型,每种类型都有自己的文档表与之相连。文档表本身存储有关文档的一些元信息以及与票证本身的关系。元信息是相同的,所以唯一的区别是关系不同。

出现的问题是票证类型的数量不断增加,因此我的文档表也在增加。为了限制对更多表格的需求,我想将所有文档合并到一张表格中,同时方便将来添加新文档。

我已经研究过使用继承,更具体地说是按层次结构表的方法,但我有点卡住了,希望有人能引导我走向正确的方向。

// This is my main class where all shared properties are defined
// I also plan to use this for documents that are just connected to a customer and not a ticket
public class CustomerDocumentsModel : EntityBase
{
    [Required]
    [Column("customer_id")]
    [MaxLength(24)]
    public string CustomerId { get; set; }

    [Required]
    [Column("file_name")]
    [MaxLength(500)]
    public string FileName { get; set; }

    // ... other shared properties
}

// I also have this setup in my OnModelCreating
modelBuilder.Entity<CustomerDocumentsModel>().HasDiscriminator<string>("document_type");
// This is my generic class that should link a documents relation to the other table
public class CustomerDocumentType<T> : CustomerDocumentsModel
{
    public virtual T ExternalEntity { get; set; }

    [Column("fk_external_id")]
    [ForeignKey(nameof(ExternalEntity))]
    public int? FKExternalId { get; set; }

    [Required]
    [Column("external_type")]
    public string ExternalType { get; set; } = typeof(T).Name;
}

// And I create the generic tables with the ticket relation like this
public class CustomerDocumentsAML : CustomerDocumentType<AMLTicketModel> { }
public class CustomerDocumentsDisputes : CustomerDocumentType<DisputeTicketModel> { }

到目前为止一切顺利。我可以创建一个迁移并将其应用到我的数据库,但这就是事情变得棘手的地方。当我尝试插入不同类型的文档(未连接到特定票证的文档除外)时,我违反了外键约束。

你们中的一些人可能已经猜到了,这是因为我对所有不同类型的文档重复使用了相同的 FK 列。因此,当我尝试连接 9001AMLTicketModel 时,我收到错误,因为DisputeTicketModel 中没有对应的 ID。这是我开始意识到我可能要实现反模式的地方,但从代码角度来看,这种方法还不足以放弃。


我在网上搜索过,最接近的是解决我的问题的两个选项。

  1. 每种类型都有一个独特的 FK 列

这种方法很容易实现,并且过载量很小,但它会导致我的文档表很快就有很多空列。从性能的角度来看也许还不错,但它很可能会导致任何直接查询它的人眼花。

  1. 忽略FK的使用并在ticket中进行连接

因此,我不会将我的列定义为 FK,而是像这样建立连接:

modelBuilder.Entity<AMLTicketModel>()
    .HasMany(x => x.Documents)
    .WithOne()
    .HasPrincipalKey(x => x.Id)
    .HasForeignKey(y => y.ExternaltId);

每次添加新类型时,这都会导致大量工作,因为所有连接都必须手动完成。这也将导致我无法再直接获取文档并从该方式获取连接的票证(无需执行更多手动操作)


我非常需要整洁和动态的代码,所以有人知道我如何解决我的问题吗?如果没有,我将不得不随机选择以上之一,所以请帮忙:(

c# .net sql-server entity-framework entity-framework-core
1个回答
0
投票

如果有人将来发现这个问题并对潜在的解决方案感兴趣(如果有点hacky),请更新。

我稍微修改了我的泛型类并通过注释进行了所有连接。模型的创建主要也是通过构造函数完成的,因此我们也可以重用基本构造函数。

[Index(nameof(ExternalId), IsUnique = false)]
public abstract class CustomerDocumentsModel<T> : CustomerDocumentsModel
{
    public CustomerDocumentsAMLModel() : base() { }

    public CustomerDocumentsAMLModel(/* params */) : base(/* params */)
    { }

    [Column("external_id")]
    [ForeignKey(nameof(ExternalEntity))]
    public int? ExternalId { get; set; }

    public virtual T ExternalEntity { get; set; }
}

通过使用注释,我们以一种简单的方式设置连接,如果将来添加新的文档类型,该连接也会自动添加。然而,在迁移过程中,人们总是需要记住删除实际的外键生成。如果我们删除它,则 SQL 中不会创建任何 FK,但所有连接仍然存在于 EF 中,因此仍然可以发挥作用。

// Remove all appearances of this
table.ForeignKey(
    name: "FK_CustomerDocuments_AMLTickets_external_id",
    column: x => x.external_id,
    principalTable: "AMLTickets",
    principalColumn: "id");

我还在我的基类中添加了一个文档类型(枚举),它与工厂结合使用,以在创建对象时生成正确的模型。只要我用正确的模型生成文档,EF 就会在查询时处理剩下的事情。

public static class CustomerDocumentFactory
{
    public static CustomerDocumentsModel Create(/* dto */)
        => dto.DocumentType switch
        {
            CustomerDocumentType.General => 
                new CustomerDocumentsModel(/* params */),
            CustomerDocumentType.AMLDocument =>
                new CustomerDocumentsAMLModel(/* params */),
            // ... etc
    };
}

这肯定违反了一些最佳实践和原则,但它确实有效。希望您觉得它有帮助!

© www.soinside.com 2019 - 2024. All rights reserved.