【问题标题】:Delete rows from a table without checking that they exist first从表中删除行而不首先检查它们是否存在
【发布时间】:2014-11-26 00:35:34
【问题描述】:

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

由于在 SQL 服务器和 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 条。”

我明白为什么当您关心并发性时会收到一个很好的通知,但我不需要。我只想删除该行,或者忽略它丢失。

【问题讨论】:

  • 你怎么知道它是否存在?只有数据库知道吗?
  • 我不在乎它是否存在。所以我猜,是吗?问题是,运行删除不存在的行的 SQL 是完全合法的(你只会得到 0 个受影响的行),这就是我想要的行为。

标签: c# .net sql-server datatable tableadapter


【解决方案1】:

Here 是根据这个问题的有用文章。引用:

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

  • 您为自定义 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);

【讨论】:

  • 你让我乐观了一秒钟。不幸的是,我处理的是TableAdapter,而不是DataAdapter
  • 嗯,TableAdapter中有一个底层的DataAdapter,但是它的可见性是protected internal。幸运的是,TableAdapterpartial class,我可能可以通过我自己的自定义属性来获得它。我会试一试,然后回复你。
  • 正如我所说,我会尽快回复您。这就像一个魅力。非常感谢。
猜你喜欢
  • 2011-02-05
  • 2014-04-20
  • 1970-01-01
  • 1970-01-01
  • 2013-07-09
  • 1970-01-01
  • 1970-01-01
  • 2017-04-02
  • 1970-01-01
相关资源
最近更新 更多