I am using C# and .NET 3.5 and have a GridView that I am setting the dataSource programatically in the code-behind page. I have data in a DataTable and then depending on a column value (isValid boolean) of each Row, I create a new row using DataRowView.AddNew() method into 1 of 2 DataViews - dvValid or dvInvalid. I am NOT creating a new DataTable.NewRow to add to the DataView Table. Then I bind the GridView to the appropriate dataView.
There is a problem when I am sorting the GridView. I am having a problem with 1 row not being sorted correctly, all other rows are sorted fine. I debugged my code and found that the DataView.Count is 1 more than the DataView.Table.Rows.Count even though I am calling DataView.Table.AcceptChanges() method. This is strange since the dataTable should have all committed rows and therefore the counts should be the same.
So why are the 2 counts different? A DataView is a subset of the DataTable so should it not have equal or less rows than the DataTable.
When I populate the DataView, should I first create the DataTables rather than creating the DataView directly? Right now, I am directly creating a DataRowView without a dDataTableRow, is this the correct approach?
Thanks for your help.
Code snippet : C#
...
//get the data as DataTable
members = GetMemberDataTable ();
//create views from a new DataTable with no rows
dvValidMembers = new DataView (CreateMembersDT("ValidMembers"));
dvInValidMembers = new DataView (CreateMembersDT("InvalidMembers"));
//iterate thru each row and put into appropriate DataView
foreach (DataRow memberRow in members.Rows)
{
if ((bool)memberRow["isValid"])
//Add to valid members Dview
member = dvValidMembers.AddNew();
else
//add to InValid members Dview
member = dvInvalidMembers.AddNew();
member["memberID"] = memberRow["memID"];
} //foreach
dvInvalidMembers.Table.AcceptChanges();
dvValidMembers.Table.AcceptChanges();
}
private System.Data.DataTable CreateMembersDT ( string tableName)
{
System.Data.DataTable dtMembers = new System.Data.DataTable(tableName);
dtMembers.Columns.Add(new DataColumn("memID", typeof(int)));
return dtMembers;
}
That 1 row that isn't sorting right, could that be the last row?
I think you are missing a DataView.EndEdit():
foreach (DataRow memberRow in members.Rows)
{
DataView dv;
if (...)
//Add to valid members Dview
dv = dvValidMembers;
else
dv = dvInvalidMembers;
member = dv.Addnew();
member["memberID"] = memberRow["memID"];
dv.EndEdit();
}
But I would also like to note that you could probably use 2 Views with a Filter on isValid and then you would only need to point them to the original members table.
Related
I am running a pretty intensive data query (hence the long timeout) but wanted to know how to sort a DataTable correctly.
My code starts like so:
SqlDataAdapter da = new SqlDataAdapter();
SqlCommand command = new SqlCommand(sql, conn);
command.CommandTimeout = 36000;
da.SelectCommand = command;
DataTable dt = new DataTable();
da.Fill(dt);
DataView dv = new DataView(dt);
// Sorting
dv.Sort = "theId DESC";
int i = 0;
while (dt.Rows.Count > 0 && i < 5)
{
DataRow row = dt.Rows[i];
string theId = row["theId"].ToString();
i++;
}
The theId has about 2,000 records with that are numerically sequential from 1 to 2,000.
The caveats or not using an ORDER BY in the SQL are due to the exception:
ERROR: Exception occurred
The query processor could not produce a query plan because a worktable is required, and its minimum row size exceeds the maximum allowable of 8060 bytes, etc.
I also cannot on-the-fly create a #TempTable due to max column size of 1,024.
The reasons the query is written a certain way are due to limitations of the system and network I am given, and cannot be adjusted nor can the query be run locally.
How do I get the sorted DataTable to work as that is what needs to be passed into the next function which is also outside of my control due to it being an api call to another system?
You can't really sort a DataTable. You do it via a DataView, as you have done, but that only sorts the view, not the table. If you then access the data via the Rows collection, you won't see the sort. You have to access the data via the DataView.
There's actually no need to create a DataView explicitly though. Every DataTable already has a DataView in its DefaultView property. When you bind a DataTable in WinForms, that's where the dusplayed data comes from. That's how you can sort the data in a DataGridView or via a BindingSource.
When you use the Rows collection, you get DataRow objects. When you use the DefaultView, you get DataRowView objects. They are quite similar in many ways but also have some differences. If you just want the data, you can index either using the column name and get the value, so they work the same way in that regard. If you access the data via the DefaultView but need the DataRow for some reason, you can access it via the Row property of the DataRowView. Try this code to see how it all works:
var table = new DataTable();
table.Columns.Add("Name", typeof(string));
table.Rows.Add("Peter");
table.Rows.Add("Paul");
table.Rows.Add("Mary");
Console.WriteLine("Rows, unsorted");
foreach (DataRow row in table.Rows)
{
Console.WriteLine(row["Name"]);
}
Console.WriteLine("DefaultView, unsorted");
foreach (DataRowView rowView in table.DefaultView)
{
Console.WriteLine(rowView["Name"]);
}
table.DefaultView.Sort = "Name";
Console.WriteLine("Rows, sorted");
foreach (DataRow row in table.Rows)
{
Console.WriteLine(row["Name"]);
}
Console.WriteLine("DefaultView, sorted");
foreach (DataRowView rowView in table.DefaultView)
{
Console.WriteLine(rowView["Name"]);
}
Console.WriteLine("DefaultView to DataRow, sorted");
foreach (DataRowView rowView in table.DefaultView)
{
Console.WriteLine(rowView.Row.Field<string>("Name"));
}
You'll be able to see from that that the data in the Rows collection is the same before and after the sort whereas the DefaultView shows data in the specified order. The last loop shows how you can access the DataRows from the DataRowViews and then use them where DataRowViews can't be, e.g. in LINQ to DataSet methods.
Note that, if you really do require the DataTable itself to be sorted then you can call ToTable on the DataView to generate a new DataTable. The new table will contain data based on the Sort and RowFilter properties of the DataView and you can also omit some columns if you want. You can also specify that only distinct records should be included.
I Have requirement to fill the datagridview row by row . i.e. if 3rd row is currently selected then data needs to be filled in 3rd row (my query returns always single row ) . same for every row.
some what like
DataTable dt = new DataTable();
dataadapter.Fill(dt);
dataGridView1.Rows[index].DataSource = dt; (just a hunch, but not working)
(instead of ) //dataGridView1.DataSource = dt;
hope that I made my requirement clear to you
Is their any way to accomplish it ....
Thanks in advance....
If your query returns a single row then you just have to fill the columns with correct values from your query:
dataGridView1.Rows[rowIndex].Cells["ColumnName"].Value =
dt.Rows[0]["ColumnNameInDataTable"].ToString();
Your grid will not be bound to the data this way. But it will fill the columns in the current row like you've asked.
I don't think this is possible directly,
You could use a List with a certain amount of empty objects and bind this List to your DataGridView... Then you would have empty Rows to work with. You can then fill single rows of the List with data and update the binding.
I also think you need a BindingSource and not a DataTable in this case...
Something like this:
private List<YourObject> gridData; // List
private BindingSource bindingSource;
public void Init()
{
this.gridData = new List<Anything>();
//prefill list, in this case we want to have 100 empty rows in DataGrid
for (var i = 0; i < 100; i++)
{
this.gridData.Add(new YourObject());
}
this.bindingSource.DataSource = this.gridData;
}
public void UpdateRow(int row)
{
this.gridData[row] = (from .. in select ...).FirstOrDefault(); // your query
}
I have a datatable filled with a report from a web service. I am now trying to display the datatable in an datagridview. This is the code I use to build the datatable:
// Create DataTabe to handle the output
DataTable dt = new DataTable();
dt.Clear();
dt.Columns.Add("EmployeeFirstName");
dt.Columns.Add("EmployeeLastName");
dt.Columns.Add("DepartmentName");
dt.Columns.Add("DepartmentCode");
dt.Columns.Add("LocationName");
dt.Columns.Add("DivisionCode");
dt.Columns.Add("EarningName");
dt.Columns.Add("OTHours");
dt.Columns.Add("WorkDate")
Fill the new datatable:
foreach (ReportRow row in report.Rows)
{
dt.Rows.Add(string.Join(",", row.ColumnValues));
}
Then I try to bind the data in the datatable to the dataGridview:
dataGridView1.AutoGenerateColumns = true;
dataGridView1.DataSource = dt;
dataGridView1.Refresh();
When I run the application it only displays the data from the first column in the datatable. Do I need a loop of sorts to work through the columns or am I just missing a step?
Yes that's cause you are adding only one value to your dt when you say dt.Rows.Add(string.Join(",", row.ColumnValues));. You should be doing something like below (assuming that ReportRow also has the columns with same names like "EmployeeFirstName" else change the names accordingly)
foreach (ReportRow row in report.Rows)
{
DataRow dr = dt.NewRow();
dr["EmployeeFirstName"] = row["EmployeeFirstName"];
dr["EmployeeLastName"] = row["EmployeeLastName"];
dr["DepartmentName"] = row["DepartmentName"];
//rest of the columns fill
//once all columns filled
dt.Rows.Add(dr);
}
dt.Rows.Add(string.Join(",", row.ColumnValues)); -> You can either add a single DataRow item or a array of objects.
From your call, you chose the later, you are adding a array of objects, except you are adding ONE SINGLE object.
string.Join(",", row.ColumnValues) is one object.
Well after sleeping I have found the issue with dropping it into an sql table... I didn't take into account that the export to a CSV and the addition of the " , " would affect the export to sql. Here is the modification of the lines of code that was the issue:
foreach (ReportRow row in report.Rows)
{
dt.Rows.Add(row.ColumnValues);
}
Thank you all for your responses!
In my WinForms application I'm populating two DataGridView's like below;
private void PopulateData()
{
//Load data
DataTable dtAll = LoadData();
DataTable dtSelected = dtAll.Clone();
dtAll.PrimaryKey = new DataColumn[] { dtAll.Columns["PK"] };
dtSelected.PrimaryKey = new DataColumn[] { dtSelected.Columns["PK"] };
DataView leftGridView = new DataView(dtAll);
DataView rightGridView = new DataView(dtSelected);
dgvLeft.AutoGenerateColumns = false;
dgvLeft.DataSource = leftGridView;
dgvRight.AutoGenerateColumns = false;
dgvRight.DataSource = rightGridView;
}
Then in some other place I'm exchanging columns between two DataGridView like below;
private void ExchangeData()
{
//Get current row of left grid
DataRow selectedRow = ((DataRowView)dgvLeft.CurrentRow.DataBoundItem).Row;
//Find the row from all data table
DataRow foundRow = dtAll.Rows.Find(selectedRow["PK"].ToString());
if (foundRow == null)
return;
//Exchange row between grids
dtAll.Rows.Remove(foundRow);
dtSelected.ImportRow(foundRow);
}
But only dtAll.Rows.Remove(foundRow); is completing correctly and reflected in the DataGridView but the line dtSelected.ImportRow(foundRow); doesn't add the row to dtSelected. I changed this line to dtSelected.ImportRow(selectedRow); but the result is same. Any thoughts?
In MSDN something catches my attention was;
If the new row violates a Constraint it won’t be added to the data
table.
Note: This question is not related to following SO posts;
DataTable.ImportRow is not adding rows
Why DataTable.Rows.ImportRow doesn't work when passing new created DataRow?
DataTable importRow() into empty table
ImportRow is not working
EDIT: I added the PrimaryKey part, DataView and DataRowCollection.Find method later to incorporate some filtering feature. Without these the code worked as intended.
Another EDIT: I removed the PrimaryKey part from PopulateData method and modified the ExchangeData method as follows;
//Get current row of left grid
DataRow selectedRow = ((DataRowView)dgvLeft.CurrentRow.DataBoundItem).Row;
//Find the row from all data table
int foundRow = dtAll.Rows.IndexOf(selectedRow);
//Exchange row between grids
dtAll.Rows.RemoveAt(foundRow);
dtSelected.ImportRow(selectedRow);
But the issue is same.
OK then it was because of my order of the code to execute. Let me explain.
This was the code I execute for the exchange;
//Exchange row between grids
dtAll.Rows.RemoveAt(foundRow);
dtSelected.ImportRow(selectedRow);
Here the row is first deleted before it's been imported to the dtSelected table. That's why dtSelected never got the row imported whatever the way I tried.
So changing the order of the code fixes my issue;
//Exchange row between grids
dtSelected.ImportRow(selectedRow);
dtAll.Rows.RemoveAt(foundRow);
The fear emotion in Inside Out says a phrase which suites this situation. "My Bad"
I have been working on asp.net/C# project. I could bind the DataTable to gridview. But i have a situation where i don't want to bind all the records in DataTable. is there any method to bind only few records to gridview? any Alternatives to GridView.DataBind()?
Thank you
try this,
using datatable select query you can achieve this,
for example,
DataTable dt=yourdata;
DataRow[] dr=dt.Select("columnname='Maths'");
foreach (DataRow row in dr) {
dt.ImportRow(row);
}
GridView1.DataSource=dt;
GridView1.DataBind();
where Maths = your search criteria.
You can add the rows manually, i.e. without using a data source.
var newRowIndex = dataGridView1.Rows.Add(firstValue, secondValue, thirdValue);
You might use it like this:
foreach (System.Data.DataRow row in myDataTable.Rows)
{
if(meets my criteria...)
{
dataGridView1.Rows.Add(row["Column1"], row["Column2"], row["Column3"]);
}
}
In fact, this may be a better solution as it allows you to sort columns easily in the UI whereas binding to a data source does not allow that.
Use DataView for the DataTable, So that you can able to filter records and assign the dataview to Grid.
for example:
DataView dv = new DataView(DataTable, "Column1=Value", "Column2", DataViewRowState.CurrentRows);