实体框架 - 在 SQL 数据库中保存文档的快照

本文关键字:保存 文档 快照 数据库 框架 SQL 实体 | 更新日期: 2023-09-27 18:35:39

>背景 我正在使用EF6,可以自由更改数据库。

我今天遇到了这个问题。假设我有:

public class Company
{
    public int Id { get; set; }
    public string Name { get; set; }
}
public class Address
{
    public int Id { get; set; }
    public int CompanyId { get; set; }
    public string Line1 { get; set; }
    public string Line2 { get; set; }
}
public class Invoice
{
    public int Id { get; set; }
    public int CompanyId { get; set; }
    public int AddressId { get; set; }
    public bool IsCompleted { get; set; }
}

允许用户更新CompanyAddress。还允许用户更新Invoice。但由于它是财务文档,如果用户将IsCompleted标记为 true,它必须以某种方式保存地址的快照。

目前,它是通过以下方式完成的:

public class Invoice
{
    public int Id { get; set; }
    public int CompanyId { get; set; }
    public int AddressId { get; set; }
    public bool IsCompleted { get; set; }
    //Auditing fields 
    public string CompanyName { get; set; }
    public string AddressLine1 { get; set; }
    public string AddressLine2 { get; set; }
}

我认为这很难理解。我在想:

选项 1:将审核保存到其自己的审核表中:

public class Invoice
{
    public int Id { get; set; }
    public int CompanyId { get; set; }
    public int AddressId { get; set; }
    public bool IsCompleted { get; set; }
    //Null if IsCompleted = false. 
    public DateTime? CompletedTimeStamp { get; set; }
}
public class CompanyAudit
{
    public int CompanyId { get; set; }
    public string Name { get; set; }
    public DateTime TimeStamp { get; set; }
}
public class AddressAudit
{
    public int AddressId { get; set; }
    public string Line1 { get; set; }
    public string Line2 { get; set; }
    public DateTime TimeStamp { get; set; }
}

但是,如果我们确实更改了公司和地址的架构,这似乎需要创建很多表,并且需要做很多工作。此外,它不是很强大。如果没有一堆接线,我就无法将其重用于其他文档。但是,这是我主要在互联网上找到的。每个表对应一个审核表。

选项 2:将所有审核保存到同一表中:

public class Audit
{
    public int DocumentId { get; set; }
    public string DocumentType { get; set; }
    public string JsonData { get; set; }
    public DateTime TimeStamp { get; set; }
}

但是,这似乎不是很标准。我以前从未将 Json 数据保存到 SQL 数据库。这不好吗?如果是这样,可能会出什么问题?

我应该选择选项 1 还是选项 2?

实体框架 - 在 SQL 数据库中保存文档的快照

简答

假设您使用的是 SQL Server,我建议您为该作业创建一些可 XML 序列化的 DTO,并使用 XML 数据类型将 XML 存储在专用列中

稍长的答案

我已经走上了完全相同的道路。我们需要在打印数据时保存数据的快照,并且涉及许多表格,在此过程中会重复

要求和评估

我们不想合并其他技术(例如Andreas提出的文件系统或某些NoSQL/文档数据库),而是将所有内容存储在SQL Server中,否则这将具有复杂的备份方案,部署,维护等。

我们想要一些易于理解的东西。新开发人员应熟悉所使用的技术。建筑不应该受到太大的影响。

对于序列化,有几个选项:XML,JSON,二进制格式化程序,DataContractSerializer,Protocol Buffers...我们的要求:易于版本控制(用于添加的属性或关系)、可读性、与 SQL Server 的一致性。

使用所有提到的格式应该可以轻松进行版本控制。可读性:XML和JSON在这里获胜。与SQL Server的一致性:XML在SQL Server中原生支持,这是我们的选择。

实现

我们做了几件事:

  1. 在我们的数据库项目中与现有实体并行创建其他 DTO,不是用于 EF,而是用于 XML 序列化。它们使用 XmlAttributes 进行批注,类似于一个复杂的自包含结构,其中包含保存文档数据所需的一切。根InvoiceSnapshot类具有支持序列化的ParseToXml方法。

  2. 根据需要更新我们的实体以包含快照:

    public string InvoiceXml { get; set; }
    public InvoiceSnapshot Invoice
    {
        get 
        { 
            return this.InvoiceXml != null 
                ? InvoiceSnapshot.Parse(this.InvoiceXml)
                : null; 
        }
        set { this.InvoiceXml = value != null ? value.ToXml() : null; }
    }
    
  3. 更新实体配置以创建 XML 列并忽略 InvoiceSnapshot 属性:

    public class InvoiceEntityConfig : EntityTypeConfiguration<InvoiceEntity>
    {
        public InvoiceEntityConfig()
        {
            this.Property(c => c.InvoiceXml).HasColumnType("xml");
            this.Ignore(c => c.Invoice);
        }
    }
    
  4. 修改我们的业务对象,以便它们从实体(可编辑状态)或 XML-DTO(快照、只读状态)加载自身。我们在这两个接口上使用接口,它们有助于简化流程。

进一步的步骤

  • 应在单独的标量列中添加常见查询的元数据,并为它们编制索引。仅当您确实要显示发票时,才检索 xml 数据。
  • 您可以查看 SQL Server 可以为您执行有关 XML 的操作,尤其是当您需要基于属性进行查询时。它可以为它们编制索引,并且可以在查询中使用 XPath。
  • 对 xml 进行签名或哈希处理,以确保快照数据不会被篡改。

选项2

更好的方法是存储生成发票 (pdf)以及它在普通文件中的所有还原。

您仍然可以使用帐单表格但是,如果某些客户数据发生变化,则无需担心用户重新打印旧文档。

要存储生成的文档,它几乎相同至于将模型存储在 JsonData 中,但您不需要保护模板和模板生成器的版本。

选项 1 只是蛮力,也许更好释放使用"事件存储"、"事件溯源"和"查询和命令"模式。

public class Document //or DocumentFile
{
    public int DocumentId { get; set; }
    public string DocumentType { get; set; }
    public string FilePath { get; set; }
    [Index]
    public String Owner { get; set;} //exp. "Customer:Id", "Contact:Id"  maybe just int
    [Index]
    public String Reference { get; set; } //exp. "Invoice:Id", "Contract:Id"   maybe just int
    public DateTime TimeStamp { get; set; } //maybe int Reversion
}