2014-10-01 4 views
2

У меня есть служба 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 затрагиваемой 0 из ожидаемых 1 записей.»

Я могу понять, почему это приятное уведомление, когда вы заботитесь о параллелизме, но мне это не нужно. Я просто хочу либо удалить строку, либо проигнорировать ее отсутствие.

+0

Как вы знаете, если он существует или нет? Знает ли только база данных? –

+0

Мне все равно, существует ли это или нет. Думаю, да? Дело в том, что вполне нормально запускать SQL, который удаляет строку, которая не существует (вы просто получите 0 затронутых строк), и это поведение, которое я хочу. – Alex

ответ

2

Here - полезная статья в соответствии с этой проблемой. Цитирование:

Если DataAdapter выполняет команду обновления и обнаруживает, что число пострадавших строк 0, он бросает DBConcurrencyException. Операция полного обновления будет прервана, и никакие дальнейшие строки в DataSet не будут рассмотрены. Обычно DBConcurrencyException происходит за одной из двух причин:

  • Вы неправильно написали SQL для пользовательского UPDATE, INSERT или DELETE команды.
  • Строка не найдена, потому что информация, используемая для ее поиска, не соответствует текущим значениям. Эта проблема сигнализирует, что другой пользователь изменил строку с момента последнего получения информации.

Вторая проблема, которую вы хотите игнорировать.

Существует два варианта обработки ошибки. Один из вариантов - обработать событие 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); 
+0

У вас был настроен оптимистично на секунду. К сожалению, я имею дело с «TableAdapter», а не с «DataAdapter». – Alex

+0

Хм, в «TableAdapter» есть базовый «DataAdapter», но его видимость «protected internal». К счастью, «TableAdapter» - это «частичный класс», и я, вероятно, доберусь до него через свое собственное пользовательское свойство. Я отдам это и вернусь к тебе. – Alex

+0

@Alex: Я отредактировал свой ответ. –

Смежные вопросы