• 解析大型.NET ERP系统 数据审计功能


    数据审计,英语表达是Audit,是追踪数据变化的过程,记录数据变化前后的值,供参考分析。通过设置,ERP可以追踪一个表的所有字段的变化,也可以只记录指定的字段的值变化。欧美企业每年都有独立的审计部门,从总经理到下层部门员工,逐个审查过去发生的经济业务的帐面数据与实际是否一致。ERP中的审计功能,通常会记录下一个表字段的值的变化。ERP系统通过LLBL Gen Pro ORM框架做数据访问层,先了解ORM提供的数据审计功能。

    审计功能的两个重要部分:记录的变化以及导致变化的动作,持久化变化的数据。

    可用于审计的功作:

    1 设置属性值。与框架的一致,改变属性的值会引发OnChanged事件,但是通过设置CurrentValue的值则不会审计。

    2 移除对象引用。比如销售单实例不再引用客户实体,从客户集合(EntityCollection)中删除客户实体。

    3 增加对象引用  与移除对象引用的情况相反,表示对象的属性引用到另一个对象或是实体增加到对象集合中。

    4 保存实体  调用SaveEntity方法。

    5 更新实体 调用SaveEntity方法。

    6 删除实体 调用方法DeleteEntity

    7  获取属性值 获取属性值或调用GetCurrentFieldValue方法取值,但是通过获取CurrentValue的值则不会审计。

    8  加载实体 调用方法FetchEntity或FetchEntityCollectionNonGeneric。

    定义一个枚举上面说到的八种情况:

    public enum AuditType
    {
            DeleteOfEntity=1,
            DirectDeleteOfEntities,
            DirectUpdateOfEntities,
            DereferenceOfRelatedEntity,
            ReferenceOfRelatedEntity,
            EntityFieldSet,
            InsertOfNewEntity,
            UpdateOfExistingEntity
    }

    实体基类EntityBase2已经定义以八种事件中的基础功能,一一列举如下:

    1 OnAuditEntityFieldSet 设置属性值

    2 OnAuditDereferenceOfRelatedEntity移除对象引用

    3 OnAuditReferenceOfRelatedEntity 增加对象引用

    4 OnAuditInsertOfNewEntity保存实体

    OnAuditUpdateOfExistingEntity更新实体

    5 OnAuditDirectUpdateOfEntities 更新实体

    6 OnAuditDeleteOfEntity 删除实体

    OnAuditDirectDeleteOfEntities 删除实体

    7 OnAuditEntityFieldGet获取属性值

    8 OnAuditLoadOfEntity加载实体

    创建数据库表AuditSetting 表示存储数据库表是否启用审计功能(Audit)。

    CREATE TABLE [dbo].[AuditSetting](
     [TableName] [NVARCHAR](100) NOT NULL,
     CONSTRAINT [PK_AuditSetting] PRIMARY KEY CLUSTERED 
    (
        [EntityName] ASC
    )WITH (PAD_INDEX  = OFF, STATISTICS_NORECOMPUTE  = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS  = ON, ALLOW_PAGE_LOCKS  = ON) ON [PRIMARY]
    ) ON [PRIMARY]
    
    GO

    设计三个数据库表,用于存放数据前后的值,用于数据审计。

    --Audit  记录用户在每个时间点操作了什么功能
    CREATE TABLE [dbo].[Audit]
    (
    [LogNo] [bigint] NOT NULL IDENTITY(1, 1),
    [Date] [datetime] NULL,
    [UserId] [nvarchar] (10) COLLATE SQL_Latin1_General_CP1_CI_AS NULL,
    [FunctionCode] [nvarchar] (8) COLLATE SQL_Latin1_General_CP1_CI_AS NULL,
    [Remarks] [nvarchar] (100) COLLATE SQL_Latin1_General_CP1_CI_AS NULL
    ) ON [PRIMARY]
    GO
    
    ALTER TABLE [dbo].[Audit] ADD CONSTRAINT [PK_Audit] PRIMARY KEY CLUSTERED  ([LogNo]) WITH (FILLFACTOR=70) ON [PRIMARY]
    GO
    
    --Audit Table 记录用户操作的功能涉及到的表
    CREATE TABLE [dbo].[AuditTable]
    (
    [LogNo] [bigint] NOT NULL,
    [TableNo] [int] NOT NULL,
    [Action] [int] NULL,
    [EntityName] [nvarchar] (60) COLLATE SQL_Latin1_General_CP1_CI_AS NULL,
    [KeyValue] [nvarchar] (200) COLLATE SQL_Latin1_General_CP1_CI_AS NULL
    ) ON [PRIMARY]
    GO
    ALTER TABLE [dbo].[AuditTable] ADD CONSTRAINT [PK_AuditTable] PRIMARY KEY CLUSTERED  ([LogNo], [TableNo]) WITH (FILLFACTOR=70) ON [PRIMARY]
    GO
    
    ALTER TABLE [dbo].[AuditTable] WITH NOCHECK ADD CONSTRAINT [FK_AuditTrailTableDetail_AuditTrail] FOREIGN KEY ([LogNo]) REFERENCES [dbo].[Audit] ([LogNo]) ON DELETE CASCADE ON UPDATE CASCADE
    GO
    
    
    --Audit Table column detail 记录列值的新值和旧值
    CREATE TABLE [dbo].[AuditTableColumn]
    (
    [LogNo] [bigint] NOT NULL,
    [TableNo] [int] NOT NULL,
    [ColumnNo] [int] NOT NULL,
    [ColumnName] [nvarchar] (100) COLLATE SQL_Latin1_General_CP1_CI_AS NOT NULL,
    [OldValue] [ntext] COLLATE SQL_Latin1_General_CP1_CI_AS NULL,
    [NewValue] [ntext] COLLATE SQL_Latin1_General_CP1_CI_AS NULL
    ) ON [PRIMARY] TEXTIMAGE_ON [PRIMARY]
    GO
    ALTER TABLE [dbo].[AuditTableColumn] ADD CONSTRAINT [PK_AuditTrailColumnDetail] PRIMARY KEY CLUSTERED  ([LogNo], [TableNo], [ColumnNo]) WITH (FILLFACTOR=70) ON [PRIMARY]
    GO
    ALTER TABLE [dbo].[AuditTableColumn] WITH NOCHECK ADD CONSTRAINT [FK_AuditTrailColumnDetail_AuditTrailTableDetail] FOREIGN KEY ([LogNo], [TableNo]) REFERENCES [dbo].[AuditTable] ([LogNo], [TableNo]) ON DELETE CASCADE ON UPDATE CASCADE
    GO
     

    定义一个数据审计类型(Facade外观模式),用于数据审计操作。

    [DependencyInjectionInfo(typeof(IEntity2), "AuditorToUse")]
    [Serializable]
    public sealed class DatabaseAuditor : AuditorBase, IDisposable
    {
            #region Class Member Declarations
            private AuditEntity _auditTrail;
            private AuditTableCollection _auditTrailTableDetails;

    因为我用的是LLBL Gen Pro的Adapter模式,所以为数据访问接口增加审计对象。

    public sealed class DataAccessAdapter 
    {
         private DatabaseAuditor _auditor;
    
         private void InitilaizeAuditor(IEntity2 entity)
         {
              _auditor = new DatabaseAuditor();
              _auditor.Adapter = this;
         }
    
         protected override void Dispose(bool isDisposing)
         {
                if (_auditor != null)
                {
                    _auditor.Dispose();
                    _auditor = null;
                }
         }
    
         public override void Commit()
         {
               if (_auditor != null)
               {
                    _auditor.PersistAuditInfo();
                    _auditor.Dispose();
                    _auditor = null;
               }
         }
    }
     
     

    重写了Commit方法,这表明与数据库相关的操作事务提交时都会调用此方法,用于保存审计信息,也就是值的变化前和变化后的数据。

    在保存字段数据时,注意上面的表AuditTableColumn的旧值OldValue和新值NewValue字段都是字符串类型,所以还需要写一个方法,将.NET数据类型转化为字符串格式的值。

    如何获取实体属性的旧值与新值,注意上面的代码中用Commit作拦截,保存值数据,数据库事物提交时,值还没有发生更改到数据库中。对比实体的属性新值和数据库中的字段旧值,即可达到这个目的。

    foreach (IEntityField2 field in  entity).Fields)
    {
           string originalValue = GetFieldOriginalValue(field, false);
           string currentValue = GetFieldCurrentValue(field, false);
    }

    取旧值的方法,也就是取ORM属性字段的DbValue,LLBL Gen Pro这一特性在通用功能设计中非常有用。

    string fieldOldValue=string.Empty;
    if (field.DbValue != null && field.DbValue != DBNull.Value)
    {
         fieldOldValue= ConvertValueToString(field.DbValue, convertZeroToEmptyString);
    }
     

    最后上一张数据审计查询结果的界面,帮助理解审计功能的设计。

    image

    数据审计给企业的审计部门提供了方便,也会降低系统性能,频繁的记录字段的旧值和新值,增加了事务的处理时间。

    对一些不重要的业务数据,应该关闭审计选项,提供系统性能。

  • 相关阅读:
    C# winform 打包成安装程序(exe)
    gitHub----【Mac】sourcetree连接github,报错:fatal:Authentication failed for'https://git…。或提示password required 解决方案
    python3-----往一个字符串中循环添加数据
    python3.8----从多层嵌套Json中解析所需要的值
    记录一些工作知识
    【转】彻底搞懂 async & defer
    【转】判断JS数据类型的四种方法
    动态表单设计
    封装,继承,多态
    语义化版本
  • 原文地址:https://www.cnblogs.com/JamesLi2015/p/4716951.html
Copyright © 2020-2023  润新知