I have two tables with a master-child relation which have few million records. I'm using a typed dataset generated by visual studio. Since my tables are very large and I don't want to implement Virtual mode, I've modified the master table with a SELECT TOP X, to limit the number of rows seen, sorted by internal criteria. Now the problem with the child table is that it still brings the million rows on application startup.
What is the solution of still using the generated DataSet and limit the child DataGridView from loading all data ? The first solution that comes in my mind is to manually implement the SqlDataAdapter data filling for the child rows in an event for row selectin changed in master DataGridView. That seems to only load on demand.
Since there was no response so far, here is the quickest solution I could find:
Keep your current typed dataset and table adapters added
Add a FillBY method to your child tableAdapter using the dataset designer. Example here
Remove from the Form_Load event the call to child tableAdapter Fill() method which was bringing all your database results there
Create a handler for CurrentChanged event of your generated bindingSource for the master table and add it in the form Load event.
Should be something like this:
this.myMasterTableBindingSource.CurrentChanged += new EventHandler(myMasterTableBindingSource_CurrentChanged);
Fill your child dataGridView with the info based on the selected item in master table:
void myMasterTableBindingSource_CurrentChanged(object sender, EventArgs e)
{
DataRowView selectedRow = myMasterTableBindingSource.Current as DataRowView;
if (selectedRow != null && !selectedRow.IsNew)
{
this.myChildTableTableAdapter.FillByUser(this.myDataSet.MyChildTable, (int)selectedRow["UserID"]);
}
}
Related
I created a very simple test to boil this issue down to common elements. I have an MS Access DB with 1 and only 1 table in it. 2 columns (ID, Name) and added 4 rows of data to the table. Here is the steps to reproduce the problem:
I added a DataSet containing a DataTable to datasources of my project. (Add new datasource from database)
I added a test Form1 with 1 and only 1 control, a DataGridView
Using designer I bound that grid to the table in the datasources of project. (Using DataSource property of DataGridView at design-time).
My Form_Load looks like this:
private void Form1_Load(object sender, EventArgs e)
{
// TODO: This line of code loads data into the 'testDBDataSet.TestTable' table. You can move, or remove it, as needed.
this.testTableTableAdapter.Fill(this.testDBDataSet.TestTable);
BindingSource bs = (BindingSource)dgvTestData.DataSource;
DataTable dt = (dgvTestData.DataSource) as DataTable;
}
When I step past the last line of code, dt is null. What am I missing?
I know it is something simple and probably obvious, but I don't see it. If I just run the code back to the form, the DataGridView has data in it.
What am I doing wrong? How can I get the underlying DataTable from BindingSource which is set as DataSource of my DataGridView using above steps?
What am I doing wrong?
When dgvTestData.DataSource is BindingSource you can not expect dgvTestData.DataSource as DataTable return anything else than null.
The as operator is like a cast operation. However, if the conversion isn't possible, as returns null instead of raising an exception.
How can I get the underlying DataTable from BindingSource?
It seems you bound the grid to a binding source which its DataSource is set to a DataSet and its DataMember is name of a DataTable. It's the default behavior when you set data source of grid to a table using designer or drag a Table from Data Sources Window and drop on your form. So if you are following default scenarios, you can use such code to get the DataTable:
BindingSource bs = (BindingSource)dgvTestData.DataSource;
var dt = (bs.DataSource as DataSet).Tables[bs.DataMember];
If you are using any other scenario different than the default scenario, you can simply use DataSource and DataMember properties of your binding sources to extract the DataTable which is using for data-binding.
Is there a better way?
It depends, why not simply use:
var dt = this.testDBDataSet.TestTable;
In my windows form I have to create a table (at runtime) of specified by the user name (in textbox) and then use this table as datasource of DataGridView. Finally, the filled table must be inserted into the database. I tried to solve this problem in two ways:
I have created a DataTable, specified its columns etc. but I couldn't insert the DataTable into the database, even using SqlDataAdapter.Update() - propably I misused DataAdapter. I'm working with C# for 1 week and I don't fully understand the idea of DataSets, TableAdapters etc. So I decided to use SQL commands.
Using ExecuteNonQuery() I've created table in my database, then I recognized, that I have to use magic SqlDataAdapter and I gave up.
I want to manage the content of DataGridView in the same way, as in the case of DataTable added to DataSet using configuration wizard.
I know, that I can skip DataGridView, create list of textboxes, button expanding this list, and function inserting all fields into my table after confirmation button, but my supervisor expects DataGridView.
I can't paste the code here, because I left only copy of source code on company computer. If it will be necessary I will paste here my code tomorrow.
try to use the method either RowValidating or CellValidating of DataGridView
private void dataGridViewEnterTab1_RowValidating(object sender, DataGridViewCellCancelEventArgs e)
{
if (dataGridViewEnterTab1.IsCurrentRowDirty) // Trigger when the row is dirty or has changed
{
string userName = dataGridViewEnterTab1["UserNameCol", e.RowIndex].EditedFormattedValue.ToString();
//...Your SqlDataAdapter or codes
}
}
I hope it will help this idea. :)
I have used full join to populate my datagridview with the contents of two tables, now I want to save the contents of the datagridview into a single table.
I don't want to use loops and I don't wanna access single cells of the datagridview.
Now anyone has a solution for this ?
and using datasource of the datagridview and its adapter is not the solution,so please reply honestly after understanding my problem.
How about using your full join to populate the Third Table, then bind the third table to the DataGridView, therefor when you make changes to the DataGridView the third table is updated automatically
try this. I created a new winforms project, i added a button and a datgridview. I created a public datatable and gave it columns and a row in the Form1 constructor. When you click the button it displays the number of rows contained in the datatable. So I run the program click the button MessageBox says 1 I add a row to the datagrid view and click the button Messagebox says 2
//public/Global datatable
public DataTable myTable = new DataTable();
public Form1()
{
InitializeComponent();
//create myTable Columns
myTable.Columns.Add("Name");
myTable.Columns.Add("Age");
myTable.Columns.Add("Number");
//add one row
myTable.Rows.Add(new object[] {"myName","myAge","myNumber"});
bind to the datagridview
dataGridView1.DataSource = myTable;
}
private void button1_Click(object sender, EventArgs e)
{
//display the number or rows in the datatable
MessageBox.Show(myTable.Rows.Count.ToString());
}
}
I'm not sure I understand what you're asking for, but I will attempt to be of service. You are obtaining a result set that combines two tables using a full join, and then want to output that result set into a third table? Is that correct?
First, I would recommend creating a view for this third table. That is probably the most efficient and dynamic solution. If you want to present that data to your users, have the datagrid bind to the view. If you made any changes to the two original tables, you would have to update the third "by hand". A view does this automatically.
I have a Form in my application that displays some data. When I first show the Form, I load some data into a DataTable then bind the DataTable to a DataGridView. I also start an asynchronous method that executes some slower database queries. When these slow queries complete, I need to update a few hundred rows in the DataTable, filling in values returned from the slower queries, like so:
foreach (DataRow row in data.Rows)
{
SlowLoadingData slow_stuff = slow_query_results[(int)row["id"]];
row.BeginEdit();
row[column_one] = slow_stuff.One;
row[column_two] = slow_stuff.Two;
row[column_three] = slow_stuff.Three;
row.EndEdit();
}
This is extremely slow, hanging the UI thread for a minute or more, presumably because each row is triggering a redraw.
After some research, I found a way to make it fast. First, bind the DataGridView to a BindingSource that is bound to the DataTable, instead of directly to the DataTable. Then, do as follows when you make changes to the DataTable:
binding_source.SuspendBinding();
binding_source.RaiseListChangedEvents = false;
// foreach (DataRow in Data.Rows) ... code above
binding_source.RaiseListChangedEvents = true;
binding_source.ResumeBinding();
grid.Refresh();
There is a problem, though, and it's a doozy: the code above prevents the DataGridView from detecting new rows added to the DataTable. Any new rows added to the table do not appear in the grid. The grid may also throw exceptions if you use the arrow keys to move the current cell selection off the bottom end of the grid, because the underlying data source has more rows but the grid has not created grid rows to display them.
So, two possible solutions that I can see:
Is there a better way to suppress binding updates while making changes to the underlying DataTable?
Is there an easy way to tell the DataGridView to gracefully refresh its grid row collection to match the number of underlying DataTable rows? (Note: I've tried calling BindingSource.ResetBindings, but it seems to trigger more exceptions if you have removed rows from the DataTable!)
You can try using the Merge method on the DataTable. I'll try to create a simple demo app and post it here, but the idea is simple. When you want to update the Grid, query the results into a new DataTable, and then merge the old table with the new table. As long as both tables have primary keys (you can create them them im memory if they don't come back from the DB) then it should track changes and update the DataGridView seamlessly. It also has the advantage of not losing the users place on the grid.
OK, here's a sample. I create a form with two buttons and one dataGridView. On button1 click, I populate the main table with some data, and bind the grid to it. Then, on second click, I create another table with the same schema. Add data to it (some that have the same primary key, and some that have new ones). Then, they merge them back to the original table. It updates the grid as expected.
public partial class Form1 : Form
{
private DataTable mainTable;
public Form1()
{
InitializeComponent();
this.mainTable = this.CreateTestTable();
}
private void button1_Click(object sender, EventArgs e)
{
for (int i = 1; i <= 10; i++)
{
this.mainTable.Rows.Add(String.Format("Person{0}", i), i * i);
}
this.dataGridView1.DataSource = this.mainTable;
}
private void button2_Click(object sender, EventArgs e)
{
DataTable newTable = this.CreateTestTable();
for (int i = 1; i <= 15; i++)
{
newTable.Rows.Add(String.Format("Person{0}", i), i + i);
}
this.mainTable.Merge(newTable);
}
private DataTable CreateTestTable()
{
var result = new DataTable();
result.Columns.Add("Name");
result.Columns.Add("Age", typeof(int));
result.PrimaryKey = new DataColumn[] { result.Columns["Name"] };
return result;
}
}
Have you considered disconnecting the dataGrid or the bindingSource while filling the table and reconnecting afterwards? It might look a bit ugly, but it should be a lot faster.
If you're using a BindingSource for complex data binding, it's important to understand that SuspendBinding and ResumeBinding only suspend and resume binding for the current item. This lets you disable binding for the current item and change a bunch of its properties without any of the individual changes to the property being pushed out to the bound control. (This isn't explained in the documentation for the BindingSource, where it would be useful, oh no: it's in the documentation for the CurrencyManager.)
Any changes you make to the other items in the list, i.e. everything except the current item, raise the ListChanged event. If you disable these events, the BindingSource stops telling the bound control about changes to the list until you re-enable them. This has the result you've seen: you add all of your rows to the underlying DataTable, but since you've turned ListChanged events off, the BindingSource doesn't tell the DataGridView about them, and so the DataGridView remains empty.
The proper solution is to call ResetBindings, which forces the BindingSource to refresh all of the controls bound to it, using the current values in its bound list.
What sort of exceptions are you getting after you call ResetBindings? Because it works just fine for me, whether I add, edit, delete, or remove rows from the underlying DataTable.
I ran into a similar problem. Here's a solution that is even simpler (if less elegant).
I found that this:
dataGridView.DataSource = null;
dataTable.BeginLoadData();
foreach(DataRow row in dataTable.Rows){
//modify row
}
dataTable.EndLoadData();
dataGridView.DataSource = dataTable;
...is way faster than this:
dataTable.BeginLoadData();
foreach(DataRow row in dataTable.Rows){
//modify row
}
dataTable.EndLoadData();
Cheers--DC
Just posting this as a solution: Making some notations to the comments and posts already.
The Merge Table method mentioned by BFree is a very good method to use and I think is the right approach not too mention very simple and elegant.
Here are my notes why and the big one I am not sure if any one caught was the hits on the server for the query.
The op stated in his comments to BFree that he would need to copy the table to do what he needed to, of course which table that was I am not sure , because his code:
foreach (DataRow row in data.Rows)
Those rows come from his table called data where is the copy required on that - he already has it.
Then here is something that just smacks right out on EACH Iteration of that loop :
SlowLoadingData slow_stuff = slow_query_results[(int)row["id"]];
Is the OP really querying the database each iteration of those rows (Which if it is a large table are we talking 100,000 rows +).
Think of the load on the Server (his app too must generate this query request!), and also the amount of traffic it places on a network to do this!
IF it is the only app maybe it's ok, but even at that it is not what I would prefer to do if I wanted to be efficient.
If gathering the data from the database in one query seems to much - then perhaps a better method would be to page his data in and do the merge.
SlowLoadingData Page1_SlowLoadingData = slow_query_results[Page1] as DataTable;
data.Merge(Page1_SlowLoadingData);
SlowLoadingData Page2_SlowLoadingData = slow_query_results[Page2] as DataTable;
data.Merge(Page2_SlowLoadingData);
I found that using resetBindings seems to move the scrollbar and the user is left thinking what did i do? I found that using a bindingList as a datasource, with an object that uses INotifyPropertyChanged, and then when I edited the row, (bound to an object). the row wasnt being updated until a click or selection change on the form.
but calling dgv.Refresh() seemed to solve the problem, without the scroll change.
I find the solution by Ravi LVS on codeproject works well:
BindingSource bs = new BindingSource();
DataTable dt = new DataTable();
bs.DataSource = dt;
bs.SuspendBinding();
bs.RaiseListChangedEvents = false;
bs.Filter = "1=0";
dt.BeginLoadData();
//== some modification on data table
dt.EndLoadData();
bs.RaiseListChangedEvents = true;
bs.Filter = "";
Link to original page: http://www.codeproject.com/Tips/55730/Achieve-performance-while-updating-a-datatable-bou
I want to cache a DataGridView row between 'refreshes' i.e. between a Rows.Clear() and a Columns.Clear(). However it seems that calling the Clear() methods does not unbind the data from the DataGridView instance, An example,
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
DataGridViewRow cachedRow = new DataGridViewRow();
private void button1_Click(object sender, EventArgs e)
{
this.dataGridView1.Rows.Clear();
this.dataGridView1.Columns.Clear();
DataGridViewColumn aColumn = new DataGridViewTextBoxColumn();
this.dataGridView1.Columns.Add(aColumn);
this.dataGridView1.Rows.Add(cachedRow);
}
}
This is done on a Form containing a DataGridView and a Button. Clicking the button twice gives the "Row provided already belongs to a DataGridView" error.
There has been some discussion online about this that suggests that it may be a bug, however this was around 2004.
Once a row is part of a gridview, you can't re-add it. The row itself keeps track of what DataGridView it is in. I would suggest making a copy of the cached row and adding the copy to the view. Since you make a new copy each time it won't be in the view. Alternatively, you can go through and remove only those rows that have not been cached from the view, leaving the cached rows behind so that you don't need to re-add it.
Clone it to a DataRow() and then DataTable.ImportRow to the originating DataTable.
I'm not sure why you'd want this behviour? You should only remove the rows in the grid that you want to remove.
You should look into implementing ObservableCollection and a Binding component. This way, if an item is removed from your object model, then it is automatically removed from the grid. This saves you having to perform what sounds like manual data binding and avoids this problem altogether.
If you're using DataSet or typed DataSets, then the observable functionality is already implemented for you - you just need to bind the datatable to the grid. If you have a table object in memory, you'll notice that you can load another one, then use the DataTable.Merge function to combine the results.