ds.Tables.Rows.Add() создает 3 строки при однократном вызове

[Примечание Обновление:]

Я хочу, чтобы пользователь, добавляющий новую строку в datagridview (DGV), мог создать эту строку в базе данных Access и иметь возможность создавать несколько строк и несинхронно редактировать эти новые строки. DGV заполняется таблицей набора данных, и я обнаружил, что набор данных между DGV и базой данных является лучшей практикой (или, по крайней мере, обеспечивает гибкость).

Чтобы внести изменения в новые строки, созданные несколькими пользователями, набор данных должен иметь возможность извлекать первичный ключ с автоинкрементом из базы данных, а затем пополнять DGV обновленными значениями ключа для новых строк. Или, по крайней мере, это единственный способ, которым я нашел эту работу. Проблема в том, что когда я пытаюсь добавить строку в таблицу данных, получается три строки вместо одной.


[Старое сообщение удалено для краткости]

Вот как DGV сначала заполняется и привязывается к набору данных:

Dim ConMain As New OleDbConnection("Provider=Microsoft.ACE.OLEDB.12.0;Data Source=" & MasterPath)
Dim ds As New DataSet

Public Sub UniversalFiller(ByVal QueryStringFill As String, ByVal DGV As DataGridView, ByVal AccessDBTableName As String)
    If IsNothing(ds.Tables(AccessDBTableName)) Then
    Else
        ds.Tables(AccessDBTableName).Clear()
    End If
    ConMain.ConnectionString = "Provider=Microsoft.ACE.OLEDB.12.0;Data Source=" & MasterPath
    Dim daZZ As New OleDbDataAdapter(QueryStringFill, ConMain)
    Try
        daZZ.Fill(ds, AccessDBTableName)
        DGV.DataSource = ds
        DGV.DataMember = AccessDBTableName
        DGV.AutoResizeColumns()
    Catch ex As Exception
        WriteToErrorLog(ex.Message, ex.StackTrace, ex.GetHashCode, ex.Source, ex.ToString)
        ds.Clear
        MsgBox("There was an error filling the DGV, ds.cleared") 'this is for my notes, not user error'
    End Try

End Sub

Обновлять:

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

Dim daZZ As New OleDbDataAdapter(DBSelectQuery, ConMain)
Dim CurrentCellPoint As Point = DGV.CurrentCellAddress
Dim CurrentCellText As String = DGV.CurrentCell.Value.ToString
Dim cmdBldr As New OleDbCommandBuilder(daZZ)
cmdBldr.QuotePrefix = "["
cmdBldr.QuoteSuffix = "]"
MsgBox("1")
Dim DaZZDataRow As DataRow = ds.Tables(DTName).NewRow
MsgBox("2")
DaZZDataRow(CurrentCellPoint.X) = CurrentCellText
MsgBox("3")
ds.Tables(DTName).Rows.Add(DaZZDataRow)
MsgBox("4")
daZZ.InsertCommand = cmdBldr.GetInsertCommand()
MsgBox("5")
daZZ.Update(ds.Tables(DTName))
MsgBox("6")
ds.Tables(DTName).Clear()
daZZ.Fill(ds, DTName)
MsgBox("5")
daZZ.Update(ds.Tables(DTName))
DGV.CurrentCell = DGV(CurrentCellPoint.X, CurrentCellPoint.Y)

Проблема всегда в шаге ds.Tables(DTName).Rows.Add(). Неважно, что написано в скобках .Add(). Это может быть число или любое целое число, включая 0, и оно составит 3 строки. Он не вызывается несколько раз. Просто позвоните в ds.Tables(DTName).Rows.Add().

Если это не правильный способ добавить одну пустую строку, то что? Я мог бы опубликовать команду вставки/обновления SQL, но это не проблема, потому что добавление создает три строки, а не 1. Чтобы было ясно, он будет проходить через MsgBox элементов только один раз и даст 3 строки.

Мне также любопытно, что команда InsertAt дает тот же результат:

ds.Tables(DTName).Rows.InsertAt(DaZZDataRow, CurrentCellPoint.Y) вместо ds.Tables(DTName).Rows.Add() по-прежнему дает 3 новые строки. Я также пробовал это на новой скретч-БД из Access и получил ту же проблему.

Вот рабочий пример кода, как проект VS2012 плюс фиктивный база данных. Это очень просто, т.е. без обработки ошибок, но оно показывает ошибку, а также позволяет удалять строки для дополнительного удобства. Спасибо всем, кто заглянет.


person Atl LED    schedule 14.10.2013    source источник
comment
Ммм, а ты уверен, что там не 3 нажатия кнопки? Триггеры на столе?   -  person Yosi Dahari    schedule 15.10.2013
comment
@Yosi Да, это срабатывает, когда вы пытаетесь написать в новую ячейку строки.   -  person Atl LED    schedule 15.10.2013
comment
Я думаю, что Йоси спрашивает здесь, вызывается ли функция три раза по какой-то причине? Вы можете легко убедиться в этом, вставив Msgbox (эта функция только что была вызвана) в код и посмотрев, что происходит, когда вы нажимаете кнопку один раз. Если это так, значит, что-то не так с интерфейсом; если это не так, то что-то пошло не так с кодом обновления данных. Ваш первый шаг — сузить круг источников неожиданного поведения.   -  person nucleon    schedule 11.12.2013
comment
@nucleon Спасибо за предложение, я попробовал. Он появляется только с одним окном сообщения. Я по-прежнему серьезно озадачен этим. Моя работа заключается в немедленном удалении двух строк, но это грубо.   -  person Atl LED    schedule 06.01.2014
comment
@nucleon Я только что опубликовал обновленный код, я думаю, основываясь на том, что вы и Йоси предлагали. Это на самом деле не вызывается 3 раза. Любые другие мысли или устранение неполадок?   -  person Atl LED    schedule 15.01.2014
comment
@Yosi Я не добавлял никаких триггеров в таблицу, база данных является базой данных Access, поэтому могла ли она автоматически получать оттуда триггеры?   -  person Atl LED    schedule 15.01.2014
comment
Вы можете выложить куда-нибудь zip-файл с рабочим примером, кодом и доступом к БД?   -  person Fredou    schedule 16.01.2014
comment
@Fredou подойдёт ли вам решение VS2012 + доступ к базе данных? Я сделаю быструю фиктивную версию   -  person Atl LED    schedule 16.01.2014
comment
@Fredou Только что добавил пример проекта; см. ссылку внизу вопроса. Извините за задержку, я закончил ее вскоре после вашей просьбы, но затем меня оторвали от компьютера до конца смены.   -  person Atl LED    schedule 16.01.2014
comment
К сожалению, я не могу открыть ваш проект, так как я все еще на VS2010. Однако, помимо добавления строк в тройной проблеме, является ли другая цель, что когда вы добавляете новую строку в DataGridView, она обновляется с помощью идентификатора AutoNumber вновь вставленной записи? (Если да, то я могу помочь вам с этим.)   -  person Gord Thompson    schedule 16.01.2014
comment
@GordThompson Это моя настоящая конечная цель, и добавление строк в трех экземплярах - это ошибка, с которой я столкнулся на этом пути. Если я полностью исправлюсь с этой ошибкой, я бы хотел, чтобы меня поставили на правильный путь.   -  person Atl LED    schedule 16.01.2014


Ответы (2)


Я вернулся к этому вопросу, сосредоточившись на том, чтобы DataGridView отображал сгенерированное значение идентификатора AutoNumber при добавлении новой записи. Я начал с добавления элемента управления DataGridView в свою форму и с помощью мастера «Добавить источник данных проекта...», чтобы подключить его к таблице с именем [Chemicals] в моей базе данных Access «Database1.accdb». (В более ранней версии примера кода в вопросе упоминалась таблица с именем [Chemicals], поэтому я использовал именно ее.)

Когда я закончил, в моем проекте были следующие "вещи":

  • элемент управления DataGridView (dataGridView1) в моей форме, который был привязан к BindingSource (см. ниже)

  • набор данных (database1DataSet), связанный с моей базой данных Access

  • BindingSource (chemicalsBindingSource), связанный с таблицей [Chemicals] в database1DataSet, для использования в качестве DataSource для моего DataGridView

  • TableAdapter (chemicalsTableAdapter) для управления потоком данных между набором данных и фактической базой данных.

Мастер также дал мне следующую строку кода в моем методе Form1_Load. (Я использовал C#, но VB.NET был бы очень похож.)

// TODO: This line of code loads data into the 'database1DataSet.Chemicals' table. You can move, or remove it, as needed.
this.chemicalsTableAdapter.Fill(this.database1DataSet.Chemicals);

Это все, что мне было нужно для запуска моего приложения и просмотра существующих записей в моем DataGridView.

Теперь, чтобы отправить обновления DataGridView обратно в базу данных и получить значение AutoNumber для вновь созданной записи, я использовал событие RowLeave. Я обнаружил, что событие UserAddedRow запускается слишком рано (сразу после того, как пользователь начал вводить новую строку), и я хотел подождать, пока пользователь закончит редактирование строки, прежде чем что-либо делать с ней.

Код, который я использовал, был следующим (опять же, С#)

private void Form1_Load(object sender, EventArgs e)
{
    fillErUp();
}

private void fillErUp()
{
    // (moved from Form1_Load to its own method so it could also be called from _RowLeave, below)
    // TODO: This line of code loads data into the 'database1DataSet.Chemicals' table. You can move, or remove it, as needed.
    this.chemicalsTableAdapter.Fill(this.database1DataSet.Chemicals);
}

private void dataGridView1_RowLeave(object sender, DataGridViewCellEventArgs e)
{
    DataGridView dgv = (DataGridView)sender;
    if (dgv.IsCurrentRowDirty)
    {
        Cursor.Current = Cursors.WaitCursor;
        dgv.EndEdit();  // flush changes from DataGridView to BindingSource
        chemicalsBindingSource.EndEdit();  // flush changes from BindingSource to DataSet
        this.chemicalsTableAdapter.Update(this.database1DataSet);  // update database
        int currentRow = e.RowIndex;
        if (Convert.ToInt32(dgv.Rows[currentRow].Cells[0].Value) < 0)
        {
            // negative AutoNumber value indicates that the row has just been added
            int currentCol = e.ColumnIndex;
            fillErUp();  // re-fill the table in the DataSet, and hence the DataGridView as well
            dgv.CurrentCell = dataGridView1[currentCol, currentRow];
        }
        Cursor.Current = Cursors.Default;
    }
}

Это, конечно, не полный производственный код, но я надеюсь, что это поможет.

Редактировать: комментарии

Я еще раз попробовал свое предыдущее решение, чтобы посмотреть, смогу ли я добиться тех же результатов, не полагаясь на какое-либо «волшебство» Visual Studio. Мой подход на этот раз был таким:

  • Я открыл новый проект С# Windows Forms.

  • Я перетащил объект DataSet, объект BindingSource и объект DataGridView на пустую форму. Их назвали dataSet1, bindingSource1 и dataGridView1 соответственно.

  • Я создал обработчики событий Form1_Load и dataGridView1_RowLeave, а затем добавил собственный код, который в итоге выглядел так:

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Data.OleDb;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;

namespace dgvTest2
{
    public partial class Form1 : Form
    {
        OleDbConnection con = new OleDbConnection();
        OleDbDataAdapter da = new OleDbDataAdapter();
        OleDbCommandBuilder cb;

        public Form1()
        {
            InitializeComponent();
        }

        private void Form1_Load(object sender, EventArgs e)
        {
            con.ConnectionString = @"Provider=Microsoft.ACE.OLEDB.12.0;Data Source=C:\Users\Public\Database1.accdb";
            con.Open();
            da.SelectCommand = new OleDbCommand("SELECT * FROM Chemicals", con);
            cb = new OleDbCommandBuilder(da);
            cb.QuotePrefix = "["; cb.QuoteSuffix = "]";
            this.dataSet1.Tables.Add(new DataTable("Chemicals"));
            this.bindingSource1.DataSource = this.dataSet1;
            this.bindingSource1.DataMember = "Chemicals";
            this.dataGridView1.DataSource = this.bindingSource1;
            fillErUp();
        }

        private void fillErUp()
        {
            this.dataSet1.Tables["Chemicals"].Clear();
            da.Fill(this.dataSet1.Tables["Chemicals"]);
        }

        private void dataGridView1_RowLeave(object sender, DataGridViewCellEventArgs e)
        {
            DataGridView dgv = (DataGridView)sender;
            if (dgv.IsCurrentRowDirty)
            {
                Cursor.Current = Cursors.WaitCursor;
                dgv.EndEdit();  // flush changes from DataGridView to BindingSource
                bindingSource1.EndEdit();  // flush changes from BindingSource to DataSet
                da.Update(this.dataSet1.Tables["Chemicals"]);  // update database
                int currentRow = e.RowIndex;
                if (dgv.Rows[currentRow].Cells[0].Value == DBNull.Value)
                {
                    // a null ID value indicates that the row has just been added
                    int currentCol = e.ColumnIndex;
                    fillErUp();  // re-fill the table in the DataSet, and hence the DataGridView as well
                    dgv.CurrentCell = dataGridView1[currentCol, currentRow];
                }
                Cursor.Current = Cursors.Default;
            }
        }

        private void Form1_FormClosed(object sender, FormClosedEventArgs e)
        {
            con.Close();
        }
    }
}
person Gord Thompson    schedule 16.01.2014
comment
Я собираюсь немного поиграть с этим, чтобы посмотреть, смогу ли я заставить его работать. В моем реальном решении у меня есть как несколько баз данных, так и динамическое количество таблиц. Я надеялся, что из-за этого не придется полагаться на мастера, но если я смогу правильно решить проблему для одного экземпляра, надеюсь, будет легче расширить - person Atl LED; 17.01.2014
comment
@AtlLED Я могу опубликовать ссылку на копию проекта C #, если она может оказаться вам полезной, например, чтобы увидеть, что мастер сделал в фоновом режиме. - person Gord Thompson; 17.01.2014
comment
Это было бы полезно, спасибо. На самом деле мой исходный код почти заработал, выключив и включив DGV.AllowUserToAddRows, но я не могу захватить начальный символ, введенный в новую ячейку строки, потому что значение читается как DBNull. Это только первый персонаж, поэтому я пытаюсь его ухватить. - person Atl LED; 17.01.2014
comment
@AtlLED Я обновил свой ответ. Вы можете загрузить копию нового проекта C# без помощи мастера здесь. - person Gord Thompson; 17.01.2014
comment
Вау, это замечательный ответ. Дайте мне день, чтобы на самом деле пройтись по нему и реализовать его, но у меня есть сильное ощущение, что это будет принятый ответ. Насколько я понимаю, вы будете держать соединение открытым все время, пока открыта форма? - person Atl LED; 17.01.2014
comment
@AtlLED Для подключения к базе данных Access я не считаю, что держать открытое соединение слишком больно. (В отличие от многопользовательской среды клиент-серверной базы данных, где активные соединения являются более ценным товаром.) Если у вас будет несколько одновременных пользователей, то добавление операторов Close() и Open() приведет к сбросу изменений другим пользователям. (Это произойдет автоматически, но иногда требуется несколько секунд, чтобы изменения в одном соединении стали видны в других соединениях.) - person Gord Thompson; 17.01.2014
comment
Извините, что возвращаюсь к этому, но как правильно привязать конкретный DGV к конкретному bs.DataMember? Я пробовал this.dataGridView1.DataSource = this.bindingSource1.Current() с идеей просто установить элемент данных, но это не сработало. Думаю, это мой последний удар по этому делу. - person Atl LED; 22.01.2014
comment
@AtlLED Все, что я сделал, это this.dataGridView1.DataSource = this.bindingSource1 - тебе это не подходит? - person Gord Thompson; 22.01.2014

Это мой пересмотренный взгляд на некоторые части вашей транзакции.

    daZZ.InsertCommand = cmdBldr.GetInsertCommand()

Я все еще не думаю, что InsertCommand требуется. Я думаю, что это создает одну из ваших 3 строк.

    ds.Tables(DTName).Rows.Add(DaZZDataRow)

Это создает ваш второй ряд. Что допустимо, но и не нужно.

ТРЕТЬЯ строка создается DataGridView из-за DataBinding. Когда вы привязываете таблицу к DataGridView, а DGV разрешено «AddRows», он также генерирует вставленную строку.

Попробуйте полностью удалить эти 2 элемента, а также строку «Очистить» (я не думаю, что это необходимо), и я уверен, что вы добавите одну строку, которая в настоящее время добавляется в DGV.

Вам все равно придется вызывать UPDATE и FILL для повторной синхронизации данных в сетке.

person DarrenMB    schedule 16.01.2014
comment
На самом деле я думаю, что мне это нужно для обратной записи в базу данных. Если я просто обновлю таблицу данных, она не будет генерировать идентификаторы из базы данных. Я очищаю его, а затем заполняю первичным ключом из БД, чтобы не столкнуться с проблемами параллелизма. Однако, даже если мы полностью проигнорируем БД и закомментируем эти разделы, строка `ds.tables(DTname).Rows.Add() создаст 3 строки в DGV (сама по себе). Я думаю, что это настоящая проблема. - person Atl LED; 16.01.2014
comment
Пересмотренный ответ. Я думаю, что это ваша проблема сейчас. - person DarrenMB; 16.01.2014
comment
Я так не думаю, я опубликую исправленный код, как я понимаю в вопросе, после того, как пойду на семинар. Из примера, комментируя строки 86-88, 94, 98 и 102, а также запрещая добавление строки DGV, мы сводим к 2 создаваемым строкам, но также к удвоению таблицы и невыборке идентификатора, что вызывает ошибки параллелизма. . Я поиграю с этой идеей еще немного и обновлю. - person Atl LED; 16.01.2014
comment
На самом деле, я думаю, что отключение DGV, позволяющее добавлять строки, оставляя все остальное на месте, может быть решением. - person Atl LED; 16.01.2014
comment
Надеюсь, это поможет вам, я обычно избегаю привязки данных, как чумы, потому что это огромный черный ящик, с которым бывает трудно иметь дело. - person DarrenMB; 17.01.2014
comment
Я хочу сказать, что если бы у меня была свободная репутация, я бы также попытался получить за вас награду. На самом деле 3 строки были вызваны тем, что DGV мог создавать новые строки. Отключение этого в исходном скрипте заставило его работать, хотя вы потеряли первый символ, введенный в строке (который я не смог зафиксировать). Тем не менее, остальная часть скрипта, казалось, была необходима для его правильной работы. - person Atl LED; 21.01.2014
comment
Рад, что вы решили это. не беспокойтесь об этом. номер не имеет значения. Я думаю, что многие из нас используют этот сайт для одного и того же, еще одно место, где можно поделиться идеями, когда что-то застряло. - person DarrenMB; 21.01.2014