删除表中的行,而不首先检查它们是否存在

本文关键字:检查 存在 是否 删除 | 更新日期: 2023-09-27 18:25:14

我有一个WCF服务,它接受DataTable,并将它们合并到现有数据中。以前,这只需要添加行,效果很好,但现在有了删除行的新要求(无论它们是否真的存在),我遇到了一个问题。

由于SQL server和DataTable中有问题的行的数量可能相当大,所以我不想加载现有的行并将它们与我的DataTable进行比较。

我的代码执行以下操作:

public Statistics ApplyChanges(DataTable changeData)
{
    var stats = new Statistics();
    if (changeData.IsEmpty)
    {
         Trace.WriteLine(string.Format("Client {0} had nothing to do; called ApplyChanges anyway. (Should normally not happen.)", ClientId));
         return stats;
    }
    FooDataSet ds = new FooDataSet();
    ds.Bar.Merge(changeData, false, MissingSchemaAction.Ignore);
    foreach (var row in ds.Bar)
    {
        // This is the new requirement. If the incoming row's 'Value' is null,
        // delete the row from the database, or, if the row doesn't exist, do
        // nothing.
        if (row.Field<string>("Value") == null)
            row.Delete();
        else
            row.SetAdded();
    }
    int changesApplied;
    using (var scope = new TransactionScope())
    {
        BarTableAdapter barAdapter = new BarTableAdapter();
        changesApplied = barAdapter.Update(ds.Bar);
        scope.Complete();
    }
    stats.ChangesApplied = changesApplied;
    stats.ChangesFailed = ds.Bar.Count(r => r.HasErrors);
    Trace.WriteLine(string.Format("Client {0} applied {1} changes, had {2} changes fail.", ClientId, changesApplied, stats.ChangesFailed));
     return stats;
}

现在,我(也许天真地)想,就像添加一样,如果一行不存在,它要么会被默默地忽略,要么在最坏的情况下,设置HasErrors属性,但没有。相反,行

changesApplied = barAdapter.Update(ds.Bar);

引发异常DBConcurrencyException,并显示以下消息:"并发冲突:DeleteCommand影响了预期1条记录中的0条。"

我明白为什么当你关心并发时,这是一个很好的通知,但我不需要。我只想删除行,或者忽略它的缺失。

删除表中的行,而不首先检查它们是否存在

这里有一篇针对这个问题的有用文章。报价:

如果DataAdapter执行更新命令并检测到受影响的行数为0,则会引发DBConcurrentException。这个整个更新操作将中止,并且将检查数据集。通常,会发生DBConcurrentException两个原因之一:

  • 您为自定义UPDATE、INSERT或DELETE命令错误地编写了SQL
  • 找不到该行,因为用于查找该行的信息与当前值不匹配。此问题表示自上次检索信息以来,另一个用户已经更改了行

第二个是你想忽略的问题。

处理错误有两种选择。一种选择是处理DataAdapter.RowUpdated事件,该事件在命令执行后但在引发错误之前触发。您可以使用此事件处理程序来记录问题,并以编程方式指示DataAdapter忽略该错误并继续处理其他错误。下面是一个显示并跳过所有错误的示例:

protected void OnRowUpdated(object sender, SqlRowUpdatedEventArgs e) // handles DataAdapter.RowUpdated
{
    // Check how many records were affected. ' If no records were affected, there was an error. 
    if (e.RecordsAffected == 0) {
        // log?
        // Don't throw an exception, continue with following 
        e.Status = UpdateStatus.SkipCurrentRow;
    }
}

另一个更简单的选择是将DataAdapter.ContinueUpdateOnError属性设置为true。然后,在更新完成后,您可以调查错误、记录错误或向用户显示错误。DataAdapter将尝试每次更改。

barAdapter.ContinueUpdateOnError = true; 

由于您使用的是强类型TableAdapter,它仅将DataAdapter作为protected属性,因此无法直接更改此设置。您可以做的是扩展这个自动生成的类(它是一个partial class)。因此,在同一目录中创建另一个具有相同名称的类,例如:public partial class BarTableAdapter

现在,您可以创建可以访问DataDapter的新属性或方法。请注意,类必须位于同一个(自动生成的)命名空间中。例如:

namespace ApplicationName.DataSetNameTableAdapters
{
    public partial class BarTableAdapter 
    {
        public bool ContinueUpdateOnError
        {
            get
            {
                return this.Adapter.ContinueUpdateOnError;
            }
            set
            {
                this.Adapter.ContinueUpdateOnError = value;
            }
        }
    }
}

不要扩展原始类(.designer.cs),它将在设计器中的每次更改时被覆盖。

现在你可以做:

BarTableAdapter barAdapter = new BarTableAdapter();
barAdapter.ContinueUpdateOnError = true;
changesApplied = barAdapter.Update(ds.Bar);