Previous topicNext topic
Help > 开发指南 > 实用小功能 >
保存数据表的修改日志

有一些非常重要的数据表,里面的信息非常重要,我们希望可以追踪每个字段每个单元格的新增、修改、删除记录,那么我们应该如何做到这样的效果呢?

第一步,我们需要设计一个保存记录修改日志的表格。经过思考,我觉得我们至少需要一些下列的字段,如果你有其他需求也可以自己自定义。

表名称 主键字段名 修改时间 修改者 修改内容 修改主键值 修改类型
入库单明细 入库单 2023-3-21 13:23 员工A 列【数量】从【12.2】修改成了【13】,列【单价】从【1.2】修改成了【1.3】 RK202303210002 修改、新增、删除

第二步,我们需要在表格的BeforeSave事件中写代码,以记录下相应的修改信息到我们上面设计的数据库表中去。这里我们利用到了SmGrid里面的GetChangesInfo方法,此方法会返回一个ChangesInfo对象,里面包含三个字典属性,字典包含了相应状态下对应行的修改信息。

Vb.Net
'为了演示,取当前表
Dim tbl As SmGrid=Proj.CurrentSmGrid
'日志要保存的数据源
Dim db As Database=Proj.SysDataFactory("UserDB")
'当前表所对应的表名称,方便后续筛选、追踪用。
Dim strTableName As String="物料表"
'当前表中能够唯一定位行的主键字段名,如果表格中是组合主键,那么需要另外自定义写代码来生成日志信息了,
'现成的GetChangesInfo方法满足不了多组合主键的场景。
Dim strPrimaryKeyName As String="物料编号"
'获取一个空的日志表,方便新增记录
Dim dtLog As DataTableHelp =db.ExecuteDataTableHelp("select * From 修改日志 where 1=2",True)
''方法二:我们可以通过设置第三个fillData参数为False来不加载任何数据,而不用在SQL语句中添加where筛选条件
'dtLog=db.ExecuteDataTableHelp("select * From 修改日志",True,False)
'直接获得修改信息
Dim info As ChangesInfo =tbl.GetChangesInfo(strPrimaryKeyName) '这里传入的参数为主键字段名
'遍历所有有修改的记录
For Each Item As KeyValuePair(Of String,String) In info.Modified
    Dim dr As RowData=dtLog.AddNew()
    dr("表名称")=strTableName
    dr("主键字段名")=strPrimaryKeyName
    dr("修改时间")=Proj.SysTime.Now
    dr("修改者")=Proj.User.UserID
    dr("修改内容")=Item.Value '这里是修改的记录信息
    dr("修改主键值")=Item.Key '这里是主键值
    dr("修改类型")="修改"
Next

'遍历所有新增的记录
For Each Item As KeyValuePair(Of String,String) In info.Added
    Dim dr As RowData=dtLog.AddNew()
    dr("表名称")=strTableName
    dr("主键字段名")=strPrimaryKeyName
    dr("修改时间")=Proj.SysTime.Now
    dr("修改者")=Proj.User.UserID
    dr("修改内容")=Item.Value '这里是修改的记录信息
    dr("修改主键值")=Item.Key '这里是主键值
    dr("修改类型")="新增"
Next

'遍历所有删除的记录
For Each Item As KeyValuePair(Of String,String) In info.Added
    Dim dr As RowData=dtLog.AddNew()
    dr("表名称")=strTableName
    dr("主键字段名")=strPrimaryKeyName
    dr("修改时间")=Proj.SysTime.Now
    dr("修改者")=Proj.User.UserID
    dr("修改内容")=Item.Value '这里是修改的记录信息
    dr("修改主键值")=Item.Key '这里是主键值
    dr("修改类型")="删除"
Next

'因为是新增记录,我们有一个更快的保存方案
If db.SupportSqlBurkCopy Then
    '使用SqlBurkCopy可以大大提高保存纯新增数据的速度
    db.SqlBurkCopy(dtLog.DataTable,"修改日志")
Else
    dtLog.Save()
End If

C#
// 为了演示,取当前表
SmGrid tbl = Proj.CurrentSmGrid;
// 日志要保存的数据源
Database db = Proj.SysDataFactory["UserDB"];
// 当前表所对应的表名称,方便后续筛选、追踪用。
string strTableName = "物料表";
// 当前表中能够唯一定位行的主键字段名,如果表格中是组合主键,那么需要另外自定义写代码来生成日志信息了,
// 现成的GetChangesInfo方法满足不了多组合主键的场景。
string strPrimaryKeyName = "物料编号";
// 获取一个空的日志表,方便新增记录
DataTableHelp dtLog = db.ExecuteDataTableHelp("select * From 修改日志 where 1=2", true);
// 方法二:我们可以通过设置第三个fillData参数为False来不加载任何数据,而不用在SQL语句中添加where筛选条件
// dtLog=db.ExecuteDataTableHelp("select * From 修改日志",true,false);
// 直接获得修改信息
ChangesInfo info = tbl.GetChangesInfo(strPrimaryKeyName); // 这里传入的参数为主键字段名
// 遍历所有有修改的记录
foreach (KeyValuePair<string, string> Item in info.Modified)
{
    RowData dr = dtLog.AddNew();
    dr["表名称"] = strTableName;
    dr["主键字段名"] = strPrimaryKeyName;
    dr["修改时间"] = Proj.SysTime.Now;
    dr["修改者"] = Proj.User.UserID;
    dr["修改内容"] = Item.Value; // 这里是修改的记录信息
    dr["修改主键值"] = Item.Key; // 这里是主键值
    dr["修改类型"] = "修改";
}

// 遍历所有新增的记录
foreach (KeyValuePair<string, string> Item in info.Added)
{
    RowData dr = dtLog.AddNew();
    dr["表名称"] = strTableName;
    dr["主键字段名"] = strPrimaryKeyName;
    dr["修改时间"] = Proj.SysTime.Now;
    dr["修改者"] = Proj.User.UserID;
    dr["修改内容"] = Item.Value; // 这里是修改的记录信息
    dr["修改主键值"] = Item.Key; // 这里是主键值
    dr["修改类型"] = "新增";
}

// 遍历所有删除的记录
foreach (KeyValuePair<string, string> Item in info.Added)
{
    RowData dr = dtLog.AddNew();
    dr["表名称"] = strTableName;
    dr["主键字段名"] = strPrimaryKeyName;
    dr["修改时间"] = Proj.SysTime.Now;
    dr["修改者"] = Proj.User.UserID;
    dr["修改内容"] = Item.Value; // 这里是修改的记录信息
    dr["修改主键值"] = Item.Key; // 这里是主键值
    dr["修改类型"] = "删除";
}

// 因为是新增记录,我们有一个更快的保存方案
if (db.SupportSqlBurkCopy)
    // 使用SqlBurkCopy可以大大提高保存纯新增数据的速度
    db.SqlBurkCopy(dtLog.DataTable,"修改日志");
else
    dtLog.Save();

GetChangesInfo方法返回的修改信息是固定的,而且也不能处理组合主键的场景,而每个人对修改日志的理解不一样,想保存的数据格式也不一样。所以我们又另外提供一个实现的代码示例,更方便大家自己修改,也能实现更强大的功能。

Vb.Net
'为了演示,取当前表
Dim tbl As SmGrid=Proj.CurrentSmGrid
'当前表所对应的表名称,方便后续筛选、追踪用。
Dim primaryKeyName As String="ItemNO"
'获得修改记录
Dim dtChanges As DataTableHelp = tbl.DataTableHelp.DataTable.GetChanges(DataRowState.Modified).GetDataTableHelp(True)
If dtChanges IsNot Nothing AndAlso dtChanges.DataRows.Count > 0 Then
    For Each dr As RowData In dtChanges.DataRows
        Dim strPrimaryKey As String = dr(primaryKeyName)
        Dim sb As StringBuilder = New StringBuilder()
        sb.Append("当前行列【" & primaryKeyName & "】的值为:【" & strPrimaryKey & "】")
        
        For Each dc As ColData In dtChanges.DataCols
            '如果初始值与当前值不一样
            If dr(dc.Name, DataRowVersion.Original) <> dr(dc.Name, DataRowVersion.Current) Then
                sb.Append("列【" & dc.Name & "】从原始数据【" & dr(dc.Name, DataRowVersion.Original) & "】修改为【" & dr(dc.Name, DataRowVersion.Current) & "】")
            End If
        Next
        '输出修改行信息
        Proj.MsgDebug.Add(sb.ToString())
    Next
End If

'获得新增行记录
Dim dtAdd As DataTableHelp = tbl.DataTableHelp.DataTable.GetChanges(DataRowState.Added).GetDataTableHelp(True)
If dtAdd IsNot Nothing AndAlso dtAdd.DataRows.Count > 0 Then    
    For Each dr As RowData In dtAdd.DataRows
        Dim strPrimaryKey As String = dr(primaryKeyName)    
        '输出新增行信息
        If Not String.IsNullOrEmpty(strPrimaryKey) Then
            Dim strAdd As String = "新增行,列【" & primaryKeyName & "】的值为:【" & strPrimaryKey & "】"
            Proj.MsgDebug.Add(strAdd)
        End If
    Next
End If

'删除行记录
Dim dtDelete As DataTable = tbl.DataTableHelp.DataTable.GetChanges(DataRowState.Deleted)
If dtDelete IsNot Nothing AndAlso dtDelete.Rows.Count > 0 Then
    '先获得列名称信息
    Dim strCols As String = ""
    For Each col As DataColumn In dtDelete.Columns
        strCols = strCols & "|" & col.ColumnName
    Next
    strCols = strCols.Trim("|")
    
    For Each dr As DataRow In dtDelete.Rows
        Dim strPrimaryKey As String = dr(primaryKeyName, DataRowVersion.Original)
        Dim sb As StringBuilder = New StringBuilder()
        sb.Append("删除行,列【" & primaryKeyName & "】的值为:【" & strPrimaryKey & "】")
        sb.Append(";列名称为:" & strCols)
        sb.Append(";当前行的值为:")
        For Each dc As DataColumn In dtDelete.Columns
            If dr.IsNull(dc,DataRowVersion.Original) Then
                sb.Append(" |")
            Else
                sb.Append(dr(dc.ColumnName,DataRowVersion.Original) & "|")
            End If
        Next
        '输出删除行信息
        If Not String.IsNullOrEmpty(strPrimaryKey) Then
            Proj.MsgDebug.Add(sb.ToString().Substring(0,sb.ToString().Length-1))
        End If
    Next
End If

'返回结果:当前行列【ItemNO】的值为:【WL-R-000001】列【Size】从原始数据【125】修改为【250】
'返回结果:当前行列【ItemNO】的值为:【WL-B-000002】列【Size】从原始数据【125】修改为【111】
'返回结果:新增行,列【ItemNO】的值为:【WL-G-000002】
'返回结果:删除行,列【ItemNO】的值为:【WL-Y-000001】;列名称为:_LockRowFlag|_SortFlag|_IdentifyFlag|ItemNO|ItemName|Color|Size|Number|SizeItemNO;当前行的值为: |4|7|WL-Y-000001|黄色衣服|Yellow|125| |WL-Y-000001

C#
 
// 为了演示,取当前表
SmGrid tbl = Proj.CurrentSmGrid;
// 当前表所对应的表名称,方便后续筛选、追踪用。
string primaryKeyName = "ItemNO";
// 获得修改记录
DataTableHelp dtChanges = tbl.DataTableHelp.DataTable.GetChanges(DataRowState.Modified).GetDataTableHelp(true);
if (dtChanges != null && dtChanges.DataRows.Count > 0)
{
    foreach (RowData dr in dtChanges.DataRows)
    {
        string strPrimaryKey = dr[primaryKeyName].CType<string>("");
        StringBuilder sb = new StringBuilder();
        sb.Append("当前行列【" + primaryKeyName + "】的值为:【" + strPrimaryKey + "】");
        
        foreach (ColData dc in dtChanges.DataCols)
        {
            // 如果初始值与当前值不一样
            if (dr.BaseRow[dc.Name, DataRowVersion.Original].CType<string>("") != dr.BaseRow[dc.Name, DataRowVersion.Current].CType<string>(""))
            {
                sb.Append("列【" + dc.Name + "】从原始数据【" + dr[dc.Name, DataRowVersion.Original].CType<string>("") + "】修改为【" + dr[dc.Name, DataRowVersion.Current].CType<string>("") + "】");
            }
        }
        // 输出修改行信息
        Proj.MsgDebug.Add(sb.ToString());
    }
}

// 获得新增行记录
DataTableHelp dtAdd = tbl.DataTableHelp.DataTable.GetChanges(DataRowState.Added).GetDataTableHelp(true);
if (dtAdd != null && dtAdd.DataRows.Count > 0)
{
    foreach (RowData dr in dtAdd.DataRows)
    {
        string strPrimaryKey = dr[primaryKeyName].CType<string>("");
        // 输出新增行信息
        if (!string.IsNullOrEmpty(strPrimaryKey))
        {
            string strAdd = "新增行,列【" + primaryKeyName + "】的值为:【" + strPrimaryKey + "】";
            Proj.MsgDebug.Add(strAdd);
        }
    }
}

// 删除行记录
DataTable dtDelete = tbl.DataTableHelp.DataTable.GetChanges(DataRowState.Deleted);
if (dtDelete != null && dtDelete.Rows.Count > 0)
{
    // 先获得列名称信息
    string strCols = "";
    foreach (DataColumn col in dtDelete.Columns)
        strCols = strCols + "|" + col.ColumnName;
    strCols = strCols.Trim("|");
    
    foreach (DataRow dr in dtDelete.Rows)
    {
        string strPrimaryKey = dr[primaryKeyName, DataRowVersion.Original].CType<string>("");
        StringBuilder sb = new StringBuilder();
        sb.Append("删除行,列【" + primaryKeyName + "】的值为:【" + strPrimaryKey + "】");
        sb.Append(";列名称为:" + strCols);
        sb.Append(";当前行的值为:");
        foreach (DataColumn dc in dtDelete.Columns)
        {
            if (dr.IsNull(dc, DataRowVersion.Original))
            {
                sb.Append(" |");
            }
            else
            {
                sb.Append(dr[dc.ColumnName, DataRowVersion.Original].CType<string>("") + "|");
            }
        }
        // 输出删除行信息
        if (!string.IsNullOrEmpty(strPrimaryKey))
        {
            Proj.MsgDebug.Add(sb.ToString().Substring(0, sb.ToString().Length - 1));
        }
    }
}

// 返回结果:当前行列【ItemNO】的值为:【WL-R-000001】列【Size】从原始数据【125】修改为【250】
// 返回结果:当前行列【ItemNO】的值为:【WL-B-000002】列【Size】从原始数据【125】修改为【111】
// 返回结果:新增行,列【ItemNO】的值为:【WL-G-000002】
// 返回结果:删除行,列【ItemNO】的值为:【WL-Y-000001】;列名称为:_LockRowFlag|_SortFlag|_IdentifyFlag|ItemNO|ItemName|Color|Size|Number|SizeItemNO;当前行的值为: |4|7|WL-Y-000001|黄色衣服|Yellow|125| |WL-Y-000001