我目前正在开发一个票证系统,应该可以将文档链接到票证。票证有不同类型,每种类型都有自己的文档表与之相连。文档表本身存储有关文档的一些元信息以及与票证本身的关系。元信息是相同的,所以唯一的区别是关系不同。
出现的问题是票证类型的数量不断增加,因此我的文档表也在增加。为了限制对更多表格的需求,我想将所有文档合并到一张表格中,同时方便将来添加新文档。
我已经研究过使用继承,更具体地说是按层次结构表的方法,但我有点卡住了,希望有人能引导我走向正确的方向。
// 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 列。因此,当我尝试连接 9001 的 AMLTicketModel 时,我收到错误,因为DisputeTicketModel 中没有对应的 ID。这是我开始意识到我可能要实现反模式的地方,但从代码角度来看,这种方法还不足以放弃。
我在网上搜索过,最接近的是解决我的问题的两个选项。
这种方法很容易实现,并且过载量很小,但它会导致我的文档表很快就有很多空列。从性能的角度来看也许还不错,但它很可能会导致任何直接查询它的人眼花。
因此,我不会将我的列定义为 FK,而是像这样建立连接:
modelBuilder.Entity<AMLTicketModel>()
.HasMany(x => x.Documents)
.WithOne()
.HasPrincipalKey(x => x.Id)
.HasForeignKey(y => y.ExternaltId);
每次添加新类型时,这都会导致大量工作,因为所有连接都必须手动完成。这也将导致我无法再直接获取文档并从该方式获取连接的票证(无需执行更多手动操作)
我非常需要整洁和动态的代码,所以有人知道我如何解决我的问题吗?如果没有,我将不得不随机选择以上之一,所以请帮忙:(
如果有人将来发现这个问题并对潜在的解决方案感兴趣(如果有点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
};
}
这肯定违反了一些最佳实践和原则,但它确实有效。希望您觉得它有帮助!