简体   繁体   中英

How to update edits in DataGridView to table

I have bound a DataGridView to an SQL Server table in a.Net 5.0 WinForms project. Displaying the data works well.

I would like to update editions to the database as soon as I move to another row in the DataGridView. But I have not found a way to do this. The solution presented here seems not to work with an OleDbDataAdapter. The Update method does not accept a DataRow. Examples in DOCS require a DataSet which I try to avoid. Other examples use a button to save changes.

The data gets loaded like this:

var dataAdapter = new OleDbDataAdapter(sqlQueryString, connString);
var dataTable = new DataTable();
dataAdapter.Fill(dataTable);                // fill data table from SQL server
var bindingSource = new BindingSource();
bindingSource.PositionChanged += new System.EventHandler(bindingSource_PositionChanged);
bindingSource.DataSource = dataTable;       // connect binding source to data table
dataGridView.DataSource = bindingSource;    // connect DataGridView to binding source

For the update I finally have tried this:

private void bindingSource_PositionChanged(object sender, EventArgs e)
{
    DataRow dataRow = ((DataRowView)((BindingSource)sender).Current).Row;
    if (dataRow.RowState == DataRowState.Modified)  // this is successful
    {
        dataAdapter.Update(dataRow);    // compile error
    }
}

I get the compile error

Cannot convert from 'System.Data.DataRow' to 'System.Data.DataRow[]'.

Any hint is appreciated.

MVVM

In modern programming, there is the tendency to separate the model from the view. This separation makes it easier to change the way that your data is displayed without having to change your model. You can also change parts of the model without having to change the display. It is easier to reuse the model and to unit test it without having to start a forms program.

In WPF this separation between model and view is almost enforced. When using winforms you have to take care that you do not mix them more than needed.

To keep these two separated, adapter code is needed to glue your model to your view. This adapter code is quite often called the viewmodel. the abbreviation of these three is quite often called MVVM. Consider to familiarize yourself with the ideas of MVVM.

Use a BindingList in your DataSource

If you want to separate your model from your view, you need methods to fetch the data that must be displayed from the database, and data to update items.

I don't know what you will be displaying in your DataGridView, but let's assume it is a sequence of Products, something like this:

class Product
{
    public int Id {get; set;}
    public string ProductCode {get; set;}
    public string Name {get; set;}
    public string Description {get; set;}
    public decimal Price {get; set;}
    ...
}

You will have methods to fetch the Products that must be displayed, and to Update one Product, or maybe several Products at a time:

IEnumerable<Product> FetchProductsToDisplay(...)
{
    // TODO: fetch the products from the database.
}

void UpdateProduct(Product product) {...}
void UpdateProducts(IEnumerable<Product> products) {...}

Implementation is out of scope of this question. By the way, did you notice, that because I put fetching and updating data in separate procedures, I hid where the Products are saved? It can be in an SQL server, but if you want it could also be a CSV or XML file, or even a dictionary, which could be handy for unit tests.

Besides: you can unit tests these methods without using your forms.

Using the visual studio designer you have added the columns and defined which column should show which Product property. You could also have done this in the constructor using property DataGridViewColumn.DataPropertyName

public MyForm()
{
    InitializeComponents();

    this.columnProductCode.DataPropertyName = nameof(Product.ProductCode);
    this.columnName.DataPropertyName = nameof(Product.Name);
    ...
}

You don't need to set the DataPropertyName for properties that you won't show anyway.

Now to display the products, it is enough to assign the Products to the DataSource of the DataGridView:

var productsToDisplay = this.FetchProductsToDisplay(...);
this.dataGridView1.DataSource = productsToDisplay.ToList();

This will display the products. However, changes that the operator makes: Add / Remove / Edit rows are not updated. If you need this functionality, then the Products need to put in an object that implements IBindingList , like (surprise!) BindingList<Product> :

private BindingList<Product> DisplayedProducts
{
    get => (BindingList<Product>)this.dataGridView1.DataSource;
    set => this.dataGridView1.DataSource = value;
}

To Initialize the DataGridView:

private void DisplayProducts()
{
    this.DisplayedProducts = new BindingList<Product>(this.FetchProductsToDisplay().ToList());
}

Now whenever the operator makes any change to the DataGridView: Add / Remove rows, or change the Displayed values in a row, these changes are reflected in DisplayedProducts .

If for instance the operator clicks Apply Now to indicate he has finished editing the products:

private void OnButtonApplyNow_Clicked(object sender, ...)
{
    var products = this.DisplayedProducts;
    // find out which Products are Added / Removed / Changed
    this.ProcessEditedProducts(products);
}

Of course you can Add / Remove / Change displayed products programmatically:

void AddProductsToDisplay()
{
    Product product = this.DisplayedProducts.AddNew();
    this.FillNewProduct(product);
}

Back to your question

Ask yourself: Is it wise to update the database as soon as the position is changed?

If the operator starts typing, then remembers he can copy-paste items, he will stop typing, go to other controls to copy, and then continue editing the cell by pasting. Maybe he goes to other rows to look at information to decide what to put in the cell.

Another scenario: the Descriptions of Product A and Product B need to be exchanged. Think of the operator actions needed for this. When would it be wise to update the database? When are you certain that the operator is content with the new data?

Hence it is not wise to update the database as soon as a row is edited. The operator should explicitly indicate he has finished editing.

private void OnButtonOk_Clicked(object sender, ...)
{
    var products = this.DisplayedProducts;
    // find out which Products are Added / Removed / Changed
    this.ProcessEditedProducts(products);
}

Further improvements

Once you've separated your data (model) from the way this data is displayed (view), using the DataSource, it is quite easy to access the Product that is displayed in the current row or in the selected rows:

Product CurrentProduct => (Product) this.dataGridView1.CurrentRow?.DataBoundItem;

IEnumerable<Product> SelectedProducts = this.dataGridView1.SelectedRows
    .Cast<DataGridViewRow>()
    .Select(row => row.DataBoundItem)
    .Cast<Product>();

you can use foreach loop.

private void AddInfo()
{
    // flag so we know if there was one dupe
    bool updated = false;

    // go through every row
    foreach (DataGridViewRow row in dgv_Purchase.Rows)
    {
        // check if there already is a row with the same id
        if (row.Cells["Pro_ID"].ToString() == txt_ProID.Text)
        {
            // update your row
            row.Cells["Purchase_Qty"] = txt_Qty.Text;

            updated = true;
            break; // no need to go any further
        }
    }

    // if not found, so it's a new one
    if (!updated)
    {
        int index = dgv_Purchase.Rows.Add();

        dgv_Purchase.Rows[index].Cells["Purchase_Qty"].Value = txt_Qty.Text;
    }
}

Finally I've found the 2 missing lines:

private SqlCommandBuilder commandBuilder;   // on UserControl level
commandBuilder = new SqlCommandBuilder(dataAdapter);    // when loading data

A book has helped me: Michael Schmalz, C# Database Basics, O'Reilly

It is strange that the DOCS reference of SqlDataAdapter doesn't mention the SqlCommandBuilder.

Thanks to everybody who has spent precious time for a New contributor.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM