【问题标题】:Snapshot History With Entity Framework实体框架的快照历史记录
【发布时间】:2010-11-30 02:25:13
【问题描述】:

我一直在研究 Entity Framework 的一些审计钩子。其中许多显示旧/新值比较。这对于审计跟踪非常有用,但我正在寻找快照对象。

例如...假设我有一个管理产品的应用程序。一个产品具有多个属性和关联的其他对象。假设我更改了一个对象 10 次。还可以说,我可以查看这些对象更改的屏幕(不是审计跟踪,而是屏幕实际以只读格式显示的样子),这一点很重要。我感兴趣的是能够为所有 10 个更改(取决于我想查看的更改)检索原始 EF 产品对象(以及所有相关数据)并使用它来绑定到我的屏幕。

如果我使用的是 SQL Server,我现在应该为序列化对象使用什么类型(XML、blob 等)?这样做有意义吗?

【问题讨论】:

  • 我会添加赏金以获得更详细的答案:)
  • @AnynameDonotcare 对于您要为其存储历史记录的每个表:1)添加版本列(可以是时间,可以是递增计数器)。 2)添加另一个与主表具有所有相同属性的表(但更好地扩展外键)。 3) 在更新触发器之前,如果版本列已更改 - 将旧值复制到此版本表。 4) 利润。
  • @Evk :如果您能通过简单的示例(使用EF)添加详细答案,我将不胜感激,可以用作企业应用程序的基础(关于删除操作和 mm 关系)

标签: c# entity-framework versioning audit


【解决方案1】:

让我们看看。您需要获取一个对象图并将其序列化到数据库中,该格式允许您稍后将其具体化。我认为有些工具可以做到这一点。其中之一,令我震惊的是实体框架。

你想做的是一件很平常的事。考虑一个 wiki 引擎。 wiki 需要有一个每个人都能看到的提示修订,以及每个文档的反向修订。 wiki 还需要能够以与显示提示修订相同的方式显示回修订。因此,两者应该使用相同的存储格式。

我建议您允许对所有实体类型进行版本控制。当您编辑实体类型时,您将编辑尖端修订并存储包含先前值的后修订。 (编辑提示修订而不是插入新提示的原因是因为其他对象,当前未具体化到 ObjectContext 中,可能包含指向提示的链接,您希望将其保留为指向提示的链接,而不是指向后面的版本。)

如有必要,您可以对 SQL Server 表进行分区,以便将后面的修订存储在不同的文件组中。这将允许您分别备份提示修订和备份修订。

【讨论】:

  • I would propose that you allow all of your entity types to be versioned. 你能澄清一下你的意思吗?
【解决方案2】:

在我最近构建的一个项目中,我们使用插入到 DbContext 类中的 SaveChanges 方法。这使我们能够访问ChangeTracker 类的实例。调用ChangeTracker.Entries() 可让您访问DbEntityEntry 的列表。 DbEntityEntry 具有以下有趣的属性和方法:

  • State - 是新创建、修改或删除的对象
  • Entity - 对象的原样副本
  • CurrentValues - 已编辑值的枚举
  • OriginalValues - 原始值的枚举

我们为更改集和更改创建了一组 POCO,然后我们可以通过 EF 访问这些更改。这使我们的用户可以查看字段级别的更改以及日期和负责的用户。

【讨论】:

    【解决方案3】:

    首先,您需要向表中添加一组属性:

    • 版本 - 上次修改的时间(也可以是自动递增计数器而不是时间)。
    • LastModifiedBy - 对进行最后修改的用户的引用(如果您存储了该修改)。

    然后,您有几个关于如何存储版本历史记录的选项。你可以

    1. 为您要为其存储历史记录的每个主表创建一个新表。该历史表将具有与主表相同的所有字段,但不会强制执行主键和外键。对于每个外键,还存储版本创建时引用条目的版本。

    2. 或者,您可以序列化有关您的实体的所有有趣内容,并将您想要版本化的所有实体的所有序列化 blob 存储在一个全局历史表中(我个人更喜欢第一种方法)。

    您如何填写历史记录表?通过更新和删除触发器。

    • 在您的实体的更新触发器中 - 将所有以前的值复制到历史记录表中。对于每个外键 - 还要复制引用实体的当前版本。
    • 在删除触发器中 - 基本上做同样的事情。

    请注意,越来越多的现代系统不会真正删除任何内容。他们只是将内容标记为已删除。如果您想遵循这种模式(它有几个好处) - 而不是删除添加 IsDeleted 标志到您的实体(当然,您必须在任何地方过滤已删除的实体)。

    你如何看待你的历史?只需使用历史表,因为它具有与主表相同的所有属性 - 应该不是问题。但是 - 扩展外键时 - 确保引用的实体版本与您在历史记录表中存储的相同。如果不是 - 您需要转到该引用实体的历史记录表并在那里获取值。这样一来,您就可以随时了解实体当时的样子,包括所有参考资料。

    除了以上所有 - 您还可以将实体的状态恢复到任何以前的版本。

    请注意,此实现虽然简单,但可能会占用一些空间,因为它存储快照,而不仅仅是正在进行的更改。如果您只想存储更改 - 在更新触发器中,您可以检测哪些字段已更改,将它们序列化并存储在全局历史表中。这样,您至少可以在用户界面中显示已更改的内容以及由谁更改(尽管您可能无法恢复到以前的版本)。

    【讨论】:

    • 为什么你更喜欢第一种方法而不是第二种方法,尽管它包含更多的冗余数据并且需要更多的数据库存储?除了我想通过'EF'处理所有这些操作之外,触发器会导致性能问题。你能提供一个简单的例子(两个表)
    • 我第一次听说“触发器导致性能问题”。触发器本身不能导致它们,只有当你在内部执行一些繁重的逻辑时。总而言之,您可以以完全相同的方式在 EF 中执行此操作 - 只需在更新处理程序中首先将所有当前值复制到历史表(不确定我可以在此处提供哪个示例 - 我想您知道如何将值从一个实体复制到其他)。我更喜欢第一种方法,因为它更简单明了,而且可以轻松恢复到任何以前的版本。除此之外 - 它允许在历史中搜索(如果你序列化,这可能更难做到)。
    • 如果我想通过'EF'处理触发逻辑,它应该在事务中吗?
    • 是的,当然(但请记住,SaveChanges 已经在事务中运行,因此如果您将历史记录条目一起添加到上下文中,将在那里应用更新 - 您已经在事务中执行此操作)。
    • 顺便说一下,如果您想要更简单的 EF 功能,请查看 github.com/loresoft/EntityFramework.Extended,查看 AuditLog 功能。
    【解决方案4】:

    如果您使用的是SQL Server 2016Azure SQL,请查看临时表(系统版本化的临时表)。

    https://docs.microsoft.com/en-us/sql/relational-databases/tables/temporal-tables?view=sql-server-ver15

    来自文档:

    提供内置支持的数据库功能 有关在任何时间点存储在表中的数据的信息,而不是 而不仅仅是当前时刻正确的数据。 时态是 ANSI SQL 2011 中引入的数据库功能。

    我在这里写了一个完整的指南,如何在没有任何第三方库的情况下使用 Entity Framework Core 实现它:

    https://stackoverflow.com/a/64244548/3850405

    【讨论】:

      猜你喜欢
      • 2014-08-23
      • 2013-06-05
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2015-05-20
      • 2012-12-31
      • 1970-01-01
      相关资源
      最近更新 更多