3

我需要在 C# 中编写一个方法来将给定数据表中的所有行保存回数据库,但我不能依赖每一行的行状态。如果没有大量循环数据库表数据或不断的数据库查询,我想不出如何最好地做到这一点。

我想基本上对数据表中的每一行执行以下插入/更新,但更有效:

INSERT INTO table
(   col1,
    col2    )
SELECT
    'value1',
    'value2'
FROM dual
WHERE NOT EXISTS ( SELECT * FROM table WHERE col1 = 'value1' );

UPDATE table
SET col2 = 'value2'
WHERE col1 = 'value1';

我在想这样的结构将是最有效的:

private void SaveDataTable(DataTable dataTable)
{
    //Remove data from the dataTable where it already exists in the database

    //Determine if the values are to be inserted or updated

    //Save relevant data
}

关于如何最好地做到这一点的任何想法?

4

2 回答 2

1

我强烈建议您让数据库管理它,而不是从应用程序的角度来处理它。SQL Server 针对集合操作进行了优化,您基本上想要执行两个集合操作:

  • 更新表中存在的所有记录,这些记录与您的表中也存在的记录不同DataTable
  • 插入DataTable表中不存在的所有记录

为此,我建议创建一个新表tableStaging

create table tableStaging (
    batchId uniqueidentifier not null, col1 int not null, col2 int not null)

几个重要的点:

  • batchId列允许您识别应用程序的不同调用者同时执行的多个操作。您希望为正在执行此操作的每个集合而不是每条记录生成一次此值。
  • batchId列是一个uniqueidentifier,因为它转换为一个Guid结构,并且可以通过调用在客户端轻松生成Guid.NewGuid
  • 您可能希望有一个复合主键,它由表batchId的主键和任何主键组成,table以使即将进行的操作更高效。

完成此操作后,您可以使用SqlBulkCopy该类批量插入您的记录DataTable有一个重载WriteToServer需要一个DataTable实例)。

最后,一旦记录在tableStaging类中,您可以调用一个存储过程,该过程只需对两个表之间存在列和主键update的所有项目执行一个插入操作,对临时表中存在的项目而不是在目标表。或者,如果您使用的是 SQL Server 2008,则可以使用该语句并一次性完成这两项操作。batchIdmerge

然后,只需清理临时表即可。由于您有batchId调用存储过程/运行命令的时间,因此您可以在完成更新主表时删除记录,或者,您可以等到您知道没有人会使用它并truncate table调用表(临时表不应该外键,所以truncate table应该可以正常工作)。

于 2012-06-05T01:26:22.367 回答
0

虽然我无法避免查询给定数据表中的每一行,但我认为这暂时可行。感谢您的回复;他们都非常有帮助。

(Oracle 特定):

internal override bool RecordExists(DataRow row)
{
    string query = string.Concat("SELECT 1 FROM ", row.Table.TableName, " ", GetDbTableFilter(row, row.Table.PrimaryKey), " AND ROWNUM = 1");

    using(OracleCommand cmd = new OracleCommand(query, (OracleConnection)_connectionLibrary.CurrentConnection))
        using(OracleDataReader reader = cmd.ExecuteReader())
            return (reader != null && reader.Read());
}

internal override string GetDbTableFilter(DataRow row, DataColumn[] columns)
{
    string filter = "";

    foreach (DataColumn column in columns)
    {
        if (!string.IsNullOrEmpty(filter))
            filter += " AND ";

        if (string.IsNullOrEmpty(row[column].ToString()))
        {
            filter += string.Concat("(", column.ColumnName, " IS NULL OR TRIM(", column.ColumnName, ") = '')");
            continue;
        }

        if (column.DataType.Equals(typeof(DateTime)))
        {
            filter += string.Concat(column.ColumnName, " = TO_DATE('", row[column].ToString(), "')");
            continue;
        }

        filter += string.Concat(column.ColumnName, " = '", row[column].ToString(), "'");
    }

    return string.Concat("WHERE ", filter);
}

public override void SaveDataTable(DataTable dataTable)
{
    dataTable.AcceptChanges();

    foreach (DataRow row in dataTable.Rows)
        if(RecordExists(row))
            row.SetModified();
        else
            row.SetAdded();

    using (OracleDataAdapter adapter = new OracleDataAdapter(string.Format("SELECT * FROM {0}", dataTable.TableName), (OracleConnection)_connectionLibrary.CurrentConnection))
        using (OracleCommandBuilder commandBuilder = new OracleCommandBuilder(adapter))
        {
            commandBuilder.SetAllValues = true;
            commandBuilder.ConflictOption = ConflictOption.OverwriteChanges;

            adapter.InsertCommand = commandBuilder.GetInsertCommand(true);
            adapter.UpdateCommand = commandBuilder.GetUpdateCommand(true);

            adapter.Update(dataTable);
        }
}
于 2012-06-05T21:22:12.047 回答