利用数据集/数据适配器的英特尔手动更新数据存储

Leveraging the intel of a DataSet / DataAdapter to manually update the datastore

我有一个数据集,其中两个 table 按关系绑定在一起:PARENT table 和 CHILD table.

我不能使用 DataAdapter 来更新数据库,而是必须调用存储过程来根据搜索数据行的状态来更新数据库。

不用说PARENT的删除(不实现级联删除),必须先删除CHILD。相反,要创建 CHILD,我需要确保 PARENT 存在,如果不存在,则创建它。

如果数据集新增了PARENT和CHILD条记录,那么同样必须先创建parent和新创建的主键parent 行需要在创建 CHILD 记录时考虑。

当涉及到DataAdapter 时,所有这些问题都会悄无声息地解决。然而,由于我被迫处理 CUD(在 CRUD 中),我必须将其构建到我的代码中,在数据集中搜索关系,检查 PARENT 和 CHILD 的行状态记录,确保 PARENT 的 CHILD 外键在尝试插入之前是正确的,等等...

是否可以利用 boiler-plate .NET (ADO) 来简化它?也许通过检查如果 DataAdapter 正在执行工作会发生的执行计划?

----------------------------编辑------------ ------------------

我认为这不会有帮助,但这是我为处理 table 编写的一些相当简单的代码。根据代码,我没有考虑到新的 PARENT 和 CHILD 都可以在数据集中提供帮助,因为迄今为止的所有测试都假定 PARENT 已经存在.这个假设是正确的,因为奇怪的是,我最初被要求在 UAT 环境中进行开发 - 看图。

无论如何,我会检查对特定 table 的更改,如果找到,我会处理。同样,没有考虑 PARENT 和 CHILD 都有新记录。下面的代码通过首先检查 CHILD 然后检查 PARENT 来处理删除,但除此之外,两个 table 上都可能有 NEW、UPDATE 和 DELETE,因此复杂性为至少现在,是压倒性的。

High-level 检查对 table 的更改。我添加了一个 Priority 变量用于稍后排序。根据示例,我将两个优先级值都设置为 "1",这意味着我稍后会利用这种关系。

var ds = myDataSet; //easier to reference!

#region Delegates
Func<DBTable, bool> ChangesFound = (table) =>
{
  var tbl = table.ToString();
  if (ds.Tables.Contains(tbl) && ds.Tables[tbl].HasChanges())
    return true;
  else
    return false;
};

Func<DBTable, DataTable> Table = (table) =>
{
  var tbl = table.ToString();
  return ds.Tables[tbl];
};
#endregion Delegates

if (ChangesFound(DBTable.CHILD)) Process_CHILD(1,Table(DBTable.CHILD));
if (ChangesFound(DBTable.PARENT)) Process_PARENT(1,Table(DBTable.PARENT));

CHILD 调用根据执行的 activity 设置二级优先级。作为child,Delete优先于Insert,优先级最高

public void Process_CHILD(int priority, DataTable modifiedTable)
{
  foreach (DataRow row in modifiedTable.Rows)
  {
    if (row.RowState != DataRowState.Added && row.RowState != DataRowState.Modified && row.RowState != DataRowState.Deleted)
      continue;

    DataRowVersion rowVersion = row.RowState == DataRowState.Deleted ? DataRowVersion.Original : DataRowVersion.Current;

    if (row.RowState == DataRowState.Added)
    {
      PriorityTwo = 1;
      // Build Insert Stored Procedure
    }
    else if (row.RowState == DataRowState.Modified)
    {
      PriorityTwo = 2;
      // Build Update Stored Procedure
    }
    else if (row.RowState == DataRowState.Deleted)
    {
      PriorityTwo = 3;
      // Build Delete Stored Procedure
    }
  }
}

PARENT 调用根据执行的 activity 设置二级优先级。作为parent,Insert优先于Delete,所以优先级最高。

public void Process_PARENT(int priority, DataTable modifiedTable)
{
  foreach (DataRow row in modifiedTable.Rows)
  {
    if (row.RowState != DataRowState.Added && row.RowState != DataRowState.Modified && row.RowState != DataRowState.Deleted)
      continue;

    DataRowVersion rowVersion = row.RowState == DataRowState.Deleted ? DataRowVersion.Original : DataRowVersion.Current;

    if (row.RowState == DataRowState.Added)
    {
      PriorityTwo = 3;
      // Build Insert Stored Procedure
    }
    else if (row.RowState == DataRowState.Modified)
    {
      PriorityTwo = 2;
      // Build Update Stored Procedure
    }
    else if (row.RowState == DataRowState.Deleted)
    {
      PriorityTwo = 1;
      // Build Delete Stored Procedure
    }
  }
}

创建所有存储过程后,我按优先级变量排序并分别处理。同样,因为我将这两个 table 的第一级优先级设置为相同,所以它们的所有 table 处理一起发生,下一个顺序为 PriorityTwo。

    foreach (var proc in storedProcedures
           .OrderByDescending(o => o.Priority)
           .ThenByDescending(t => t.PriorityTwo))
    {
    // Call the procedure, etc...
    }

这个解决方案非常糟糕:我仍然brute-forcing操作顺序,而实际上我应该取决于已经定义的数据集关系。我最终可能会构建一些由数据集中定义的关系驱动的东西,但我再次希望在我硬着头皮重建对我来说似乎已经编写的 boiler-plate 代码之前已经存在一些东西。

如果我的麻烦还不够严重,我还需要解决上面提到的另一个问题。创建 PARENT 时,其主键的标识值被传回 c#,我用它来更新生成存储过程的数据行。我仍然需要弄清楚如何将其传递给 CHILD 记录,以便在其存储过程插入中保持引用完整性...我需要在不进行硬编码的情况下这样做...

是的,解决方案 "as is" 很糟糕...

事实证明,我让这比需要的要困难得多。我认为这是由于我厌恶使用 DataTable Relationships。但是通过关系列表(至少对我而言)是要走的路。

我确定我的解决方案是在任何删除之前执行所有插入,或者在任何插入之前执行所有删除,并确保在执行 activity (Inserts/Deletes),按照数据集中关系规定的正确顺序执行此操作。

首先是生成一个列表,其中包含在 DataTable 关系中定义为 "Parent" 且从未在任何其他关系中定义为 "Child" 的所有 table。这些 "Parents" 稍后作为层次处理顺序的起点:

var topParents = new List<DataTable>();
foreach (DataRelation rel in ds.Relations)
{
    var parent = rel.ParentTable;
    var isTopParent = true;
    foreach (DataRelation rel2 in ds.Relations)
    {
        if (parent.TableName == rel2.ChildTable.TableName) isTopParent = false;
    }
    if (isTopParent && !topParents.Contains(parent))
        topParents.Add(parent);
}

连同顺序索引,我接下来构建一个字典列表来保存 table 处理顺序。索引允许我对结果进行排序:插入的升序,删除的降序。我利用递归来找出列表定义:

Dictionary<int, DataTable> insertTableOrder = new Dictionary<int, DataTable>();
foreach (var parent in topParents)
{
    foreach (DataRelation rel1 in ds.Relations)
    {
        if (rel1.ParentTable == parent)
        {
            var child1 = rel1.ChildTable;
            var relName1 = rel1.RelationName;

            if (insertTableOrder.Where(w => w.Value.TableName == parent.TableName).Count() == 0)
                insertTableOrder.Add(index++, parent);

            if (insertTableOrder.Where(w => w.Value.TableName == child1.TableName).Count() == 0)
                insertTableOrder.Add(index++, child1);

            Recursive(insertTableOrder, ref index, child1, ds);
        }
    }
}

递归函数为:

public void Recursive(Dictionary<int, DataTable> insertTableOrder, ref int index, DataTable child1, DataSet ds)
{
    foreach (DataRelation rel in ds.Relations)
        if (child1 == rel.ParentTable)
        {
            var child2 = rel.ChildTable;
            var relName2 = rel.RelationName;

            if (insertTableOrder.Where(w => w.Value.TableName == child2.TableName).Count() == 0)
                insertTableOrder.Add(index++, child2);

            Recursive(insertTableOrder, ref index, child2, ds);
        }
}

现在我有了一个按正确顺序处理顺序排列的 table 的列表,我可以构建我的存储过程 (SQLCommands) 来对数据库执行操作。我特别注意在 DataSet 中的 DataTables 之后命名方法,以便我可以利用反射来实例化这些方法。由于我确定可以执行更新 "anywhere",我也在这里处理它们:

foreach (var table in insertTableOrder.OrderBy(o => o.Key))
{
    var targetTable = table.Value.GetChanges(DataRowState.Added | DataRowState.Modified);
    if (targetTable != null && targetTable.HasChanges())
    {
        var methodName = "CreateSP_" + targetTable.TableName;

        System.Reflection.MethodInfo createSP = this.GetType().GetMethod(methodName);
        object result = createSP.Invoke(this, new object[] { index++, targetTable });
    }
}

然后是删除:

foreach (var table in insertTableOrder.OrderByDescending(o => o.Key))
{
    var targetTable = table.Value.GetChanges(DataRowState.Deleted);
    if (targetTable != null && targetTable.HasChanges())
    {
        var methodName = "CreateSP_" + targetTable.TableName;

        SetAction(string.Format("Invoking Reflected method: [{0}], for {1}", methodName, purpose),  "");
        System.Reflection.MethodInfo createSP = this.GetType().GetMethod(methodName);
        object result = createSP.Invoke(this, new object[] { index++, targetTable });
    }
}

最后我处理所有其他未在关系中定义的 table:

foreach (DataTable table in ds.Tables)
{
    if (insertTableOrder.Values.Contains(table)) continue;

    if (table.HasChanges())
    {
        var methodName = "CreateSP_" + table.TableName;

        System.Reflection.MethodInfo createSP = this.GetType().GetMethod(methodName);
        object result = createSP.Invoke(this, new object[] { index++, table });
    }
}

生成所有数据库操作后,就该执行命令了。对插入特别感兴趣的是记下由数据库(在本例中为 MSSQL)分配的主键,稍后子项的外键约束需要这些主键。基本 'storedProcedures' 对象已编码到其中,下面使用的所有必要元素确保在访问数据库之前使用外键正确更新父级的 SQLCommand(存储过程)子级。此外,由于 DataAdapter 未使用原始 DataSet,因此我需要确保 DataSet 反映 DataBase 的状态。因此,对于插入,我更新了 DataSet 的每个 DataTable 中的行,当存储过程执行将主键分配给物理行时,我将其插入到后端 table.

下面的代码被包装到一个 Try/Catch 中以特别处理,如果出现故障则拒绝对 DataSet 的更改:

using (var conn = new System.Data.SqlClient.SqlConnection(ApplicationConfig.DbConnectInfo.ConnectionString))
{
    conn.Open();

    using (var transaction = conn.BeginTransaction(IsolationLevel.ReadCommitted))
    {
        foreach (var parent in storedProcedures.OrderBy(o => o.Sequence))
        {
            var spName = parent.StoredProcedureName;

            var originalValue = parent.OriginalPrimaryKeyValue;
            var primaryKey = parent.PrimaryKeyUsed;

            parent.Execute(conn, transaction);

            if (parent.Operation == DatabaseOperaion.Insert)
            {
                var scopeIdentityValue = (int)parent.SQLParameters[0].Value;

                foreach (DataRelation rel1 in ds.Relations)
                {
                    var parentColumn = rel1.ParentColumns[0].ToString().ToLower();
                    var childColumn = rel1.ChildColumns[0].ToString().ToLower();

                    var childTable = rel1.ChildTable.TableName.ToLower();

                    //if (parent.Table == rel1.ParentTable)
                    if (parent.Table.TableName == rel1.ParentTable.TableName)
                    {
                        var children = storedProcedures
                        .Where(w => w.Table.TableName.ToLower() == childTable);

                        foreach (var child in children)
                            foreach (var parm in child.SQLParameters)
                                if (parm.ParameterName.Substring(1).ToLower() == childColumn)
                                    if ((decimal)parm.Value == originalValue)
                                        parm.Value = scopeIdentityValue;
                    }

                    var where = string.Format("{0}={1}", primaryKey, originalValue);

                    DataRow[] rows = ds.Tables[parent.Table.TableName].Select(where);

                    foreach (DataRow row in rows)
                    {
                        row.BeginEdit();
                        row[primaryKey] = scopeIdentityValue;
                        row.EndEdit();
                    }
                }
                action = string.Empty;
            }
        }
        transaction.Commit();

        ds.AcceptChanges();

    } // Transaction
} // SQL Connection

无论如何,不​​用说上面的解决方案是针对我的需要的,可能无法满足您的特定目的。但我确实希望它能帮助您克服 运行 遇到的任何困难。