DataTable doesn't bind columns caption - c#

My issue is almost the same from this one: Why DataColumn.Caption doesn't work?
But there, he manually created his DataTable, while in my case I'm binding it to an Access database through an OleDbConnection with a connection string. In that database, I have set the captions for each column of the tables so that I could only do the following:
DataTable t = cnn.Consulta(sSQL); //returns a DataTable with a string SQL select query.
DataGridView dg = new DataGridView();
dg.DataSource = t;
int i = 0;
string caption;
foreach (DataGridViewColumn cln in dg.Columns)
{
caption = t.Columns[i].Caption;
cln.HeaderText = caption;
i++;
}
Note that my foreach block is basically the same as the one given as solution in the post mentioned above, but as I have set the captions directly in the Access file, instead of doing it in the code, C# is keeping binding it as the column name, as if they were empty (???).
Now imagine if I had tons of tables, each of them with tons of columns with a non-friendly name? Well, it's true that I'd still have to set a ton of captions anyway, but it would be better than writing 300 lines of code to assign it to each DataColumn.
Unless there is another property where I can store a string to use as header text, I'm stuck with this.

Related

DataTable update() inserts duplicate new rows without checking if it exists

I'm trying to use the update() method, but it is inserting my datatable data into my database without checking if the row exists, so it is inserting duplicate data. It is also not deleting rows that don't exist in datatable. How to resolve this? I want to synchronize my datatable with server table.
private void Form1_Load(object sender, EventArgs e)
{
// TODO: This line of code loads data into the 'MyDatabaseDataSet11.Vendor_GUI_Test_Data' table. You can move, or remove it, as needed.
this.vendor_GUI_Test_DataTableAdapter.Fill(this.MyDatabaseDataSet11.Vendor_GUI_Test_Data);
// read target table on SQL Server and store in a tabledata var
this.ServerDataTable = this.MyDatabaseDataSet11.Vendor_GUI_Test_Data;
}
Insertion
private void convertGUIToTableFormat()
{
ServerDataTable.Rows.Clear();
// loop through GUIDataTable rows
for (int i = 0; i < GUIDataTable.Rows.Count; i++)
{
String guiKEY = (String)GUIDataTable.Rows[i][0] + "," + (String)GUIDataTable.Rows[i][8] + "," + (String)GUIDataTable.Rows[i][9];
//Console.WriteLine("guiKey: " + guiKEY);
// loop through every DOW value, make a new row for every true
for(int d = 1; d < 8; d++)
{
if ((bool)GUIDataTable.Rows[i][d] == true)
{
DataRow toInsert = ServerDataTable.NewRow();
toInsert[0] = GUIDataTable.Rows[i][0];
toInsert[1] = d + "";
toInsert[2] = GUIDataTable.Rows[i][8];
toInsert[3] = GUIDataTable.Rows[i][9];
ServerDataTable.Rows.InsertAt(toInsert, 0);
//printDataRow(toInsert);
//Console.WriteLine("---------------");
}
}
}
Trying to update
// I got this adapter from datagridview, casting my datatable to their format
CSharpFirstGUIWinForms.MyDatabaseDataSet1.Vendor_GUI_Test_DataDataTable DT = (CSharpFirstGUIWinForms.MyDatabaseDataSet1.Vendor_GUI_Test_DataDataTable)ServerDataTable;
DT.PrimaryKey = new DataColumn[] { DT.Columns["Vendor"], DT.Columns["DOW"], DT.Columns["LeadTime"], DT.Columns["DemandPeriod"] };
this.vendor_GUI_Test_DataTableAdapter.Update(DT);
Let's look at what happens in the code posted.
First this line:
this.ServerDataTable = this.MyDatabaseDataSet11.Vendor_GUI_Test_Data;
This is not a copy, but just an assignment between two variables. The assigned one (ServerDataTable) receives the 'reference' to the memory area where the data coming from the database has been stored. So these two variables 'point' to the same memory area. Whatever you do with one affects what the other sees.
Now look at this line:
ServerDataTable.Rows.Clear();
Uh! Why? You are clearing the memory area where the data loaded from the database were. Now the Datatable is empty and no records (DataRow) are present there.
Let's look at what happen inside the loop
DataRow toInsert = ServerDataTable.NewRow();
A new DataRow has been created, now every DataRow has a property called RowState and when you create a new row this property has the default value of DataRowState.Detached, but when you add the row inside the DataRow collection with
ServerDataTable.Rows.InsertAt(toInsert, 0);
then the DataRow.RowState property becomes DataRowState.Added.
At this point the missing information is how a TableAdapter behaves when you call Update. The adapter needs to build the appropriate INSERT/UPDATE/DELETE sql command to update the database. And what is the information used to choose the proper sql command? Indeed, it looks at the RowState property and it sees that all your rows are in the Added state. So it chooses the INSERT command for your table and barring any duplicate key violation you will end in your table with duplicate records.
What should you do to resolve the problem? Well the first thing is to remove the line that clears the memory from the data loaded, then, instead of calling always InsertAt you should first look if you have already the row in memory. You could do this using the DataTable.Select method. This method requires a string like it is a WHERE statement and you should use some value for the primarykey of your table
var rows = ServerDataTable.Select("PrimaryKeyFieldName = " + valueToSearchFor);
if you get a rows count bigger than zero then you can use the first row returned and update the existing values with your changes, if there is no row matching the condition then you can use the InsertAt like you are doing it now.
You're trying too hard, I think, and you're unfortunately getting nearly everything wrong
// read target table on SQL Server and store in a tabledata var
this.ServerDataTable = this.MyDatabaseDataSet11.Vendor_GUI_Test_Data;
No, this line of code doesn't do anything at all with the database, it just assigns an existing datatable to a property called ServerDataTable.
for (int i = 0; i < GUIDataTable.Rows.Count; i++)
It isn't clear if GUIDataTable is strongly or weakly typed, but if it's strong (I.e. it lives in your dataset, or is of a type that is a part of your dataset) you will do yourself massive favors if you do not access it's Rows collection at all. The way to access a strongly typed datatable is as if it were an array
myStronglyTypedTable[2] //yes, third row
myStronglyTypedTable.Rows[2] //no, do not do this- you end up with a base type DataRow that is massively harder to work with
Then we have..
DataRow toInsert = ServerDataTable.NewRow();
Again, don't do this.. you're working with strongly typed datatables. This makes your life easy:
var r = MyDatabaseDataSet11.Vendor_GUI_Test_Data.NewVendor_GUI_Test_DataRow();
Because now you can refer to everything by name and type, not numerical index and object:
r.Total = r.Quantity * r.Price; //yes
toInsert["Ttoal"] = (int)toInsert["Quantity"] * (double)toInsert["Price"]; //no. Messy, hard work, "stringly" typed, casting galore, no intellisense.. The typo was deliberate btw
You can also easily add data to a typed datatable like:
MyPersonDatatable.AddPersonRow("John, "smith", 29, "New York");
Next up..
// I got this adapter from datagridview, casting my datatable to their format
CSharpFirstGUIWinForms.MyDatabaseDataSet1.Vendor_GUI_Test_DataDataTable DT = (CSharpFirstGUIWinForms.MyDatabaseDataSet1.Vendor_GUI_Test_DataDataTable)ServerDataTable;
DT.PrimaryKey = new DataColumn[] { DT.Columns["Vendor"], DT.Columns["DOW"], DT.Columns["LeadTime"], DT.Columns["DemandPeriod"] };
this.vendor_GUI_Test_DataTableAdapter.Update(DT);
Need to straighten out the concepts and terminology in your mind here.. that is not an adapter, it didn't come from a datagridview, grid views never provide adapters, your datatable variable was always their format and if you typed it as DataTable ServerDataTable then that just makes it massively harder to work with, in the same way that saying object o = new Person() - now you have to cast o every time you want to do nearly anything Person specific with it. You could always declare all your variables in every program, as type object.. but you don't.. Hence don't do the equivalent by putting your strongly typed datatables inside DataTable typed variables because you're just hiding away the very things that make them useful and easy to work with
If you download rows from a database into a datatable, and you want to...
... delete them from the db, then call Delete on them in the datatable
... update them in the db, then set new values on the existing rows in the datatable
... insert more rows into the db alongside the existing rows, then add more rows to the datatable
Datatables track what you do to their rows. If you clear a datatable it doesn't mark every row as deleted, it just jettisons the rows. No db side rows will be affected. If you delete rows then they gain a rowstate of deleted and a delete query will fire when you call adapter.Update
Modify rows to cause an update to fire. Add new rows for insert
As Steve noted, you jettisoned all the rows, added new ones, added (probably uselessly) a primary key(the strongly typed table will likely have already had this key) which doesn't mean that the new rows are automatically associated to the old/doesn't cause them to be updated, hen inserted a load of new rows and wrote them to the db. This process was never going to update or delete anything
The way this is supposed to work is, you download rows, you see them in the grid, you add some, you change some, you delete some, you hit the save button. Behind the scenes the grid just poked some new rows into the datatable, marked some as deleted, changed others. It didn't go to the huge (and unfortunately incorrect) lengths your code went to. If you want your code to behave the same you follow the same idea:
var pta = new PersonTableAdapter();
var pdt = pta.GetData(); //query that returns all rows
pta.Fill(somedataset.Person); //or can do this
pdt = somedataset.Person; //alias of Person table
var p = pdt.FindByPersonId(123); //PersonId is the primary key in the datatable
p.Delete(); //mark person 123 as deleted
p = pdt.First(r => r.Name = "Joe"); //LINQ just works on strongly typed datatables, out of the box, no messing
p.Name = "John"; //modify joes name to John
pdt.AddPersonRow("Jane", 22);
pta.Update(pdt); //saves changes(delete 123, rename joe, add Jane) to db
What you need to appreciate is that all these commands are just finding or creating datarow obj3cts, that live inside a table.. the table tracks what you do and the adapter uses appropriate sql to send changes to the db.. if you wanted to mark all rows in a datatable as deleted you can visit each of them and call Delete() on it, then update the datatable to save the changes to the db

Adding datatype to table columns with ClosedXML

I'm using ClosedXML to create an xlsx-file with some data. In the Excel-sheet I have a table with data. In the columns of the table (not the column of the sheet) I want to specify datatypes.
/* This datatable is created and populated somewhere else in my program*/
Datatable dt = new Datatable();
ws.Cell(t.Position.Row, t.Position.Column).InsertTable(dt);
IXLTables allTables = ws.Tables;
var table = allTables.ElementAt(i);
int j = 1;
/* The options object hold the excel datatypes for each column*/
foreach (var c in option.Tables.ElementAt(i).Columns)
{
table.Column(j).DataType = c.Type;
j++;
}
i++;
The datatype is found and added in the foreach-loop from an options object, how this works in my program is probably not important.
The problem is that when I add a datatype to a column in the table it includes the header of the table. Since the header is "Text" and the value I might specified is Number I get an error. Anyone got an idea how to make it ignore the headerline of the table and simply add datatype to the columns below?
Thanks in advance.
Since setting the data type can work on a per column basis in Excel it's not unreasonable to expect the same from ClosedXml. As you've found it doesn't work like that however. The way I do it is to define a range that selects the whole column except the header. I've not worked with tables the way you are but the following snippet should give you a direction.
IXLWorksheet sheet = wb.Worksheets.Add("Sheet1");
sheet.Cell(1, 1).InsertTable(dt);
foreach (var column in sheet.ColumnsUsed())
{
string columnLetter = column.ColumnLetter();
string rng = $"${columnLetter}2:columnLetter}sheet.RangeUsed().RowCount()}";
sheet.Range(rng).DataType = some data type;
}

How to find the DataBase type of a bound DataGridView column

I need to know the original type of each column in my DataGridView.
It is bound with a dynamic SQL select, say, "SELECT * FROM artists;"
I want to add a form view of the data above the grid and am programmtically creating Labels and TextBoxes and then some to hold the fields. I add them to a FlowLayoutPanel but I would like to adapt the sizes, especially the multiline property and the height to accomodate long comment and description fields of say 200-500 characters.
All I found when looking into the text columns was datatype string.
I know I can look up the columns by querying the systables, but it would be nice to find the original datatype a bit closer than that; also I'm using MYSQL atm, and a solution that doesn't need to query the database would hopefully also be DBMS independent.
Edit 1
I fill the DGV with nothing fancy:
DBDA = new MySqlDataAdapter(sql, DBC);
MySqlCommandBuilder cb = new MySqlCommandBuilder(DBDA);
DBDS = new DataSet(ddlb_tables.Text);
DBDA.FillSchema(DBDS, SchemaType.Mapped); //<- This was the missing piece of code!!
DBDA.Fill(DBDS, ddlb_tables.Text);
dataGridView1.DataSource = DBDS;
dataGridView1.DataMember = ddlb_tables.Text;
Edit 2
With the help of the accepted answer (DBDA.MissingSchemaAction) I could solve my problem. Here is the resulting function in its first, raw version:
public int getColumnSize(DataGridViewColumn dc)
{
try
{
DataGridView DGV = dc.DataGridView;
DataSet DS = (DataSet)DGV.DataSource;
DataTable DT = DS.Tables[0];
DataColumn DC = DT.Columns[dc.Name];
return DC.MaxLength;
} catch { }
return -1;
}
Not getting the original type is not a problem, as long as I know the length of the fields.
use the DataSet instead DataGrid:
foreach (DataColumn col in DBDS[ddlb_tables.Text].Text.Columns)
{
if (col.DataType == typeof(string))
{
var len = col.MaxLenght;
...
}
}
EDIT:
You may need to add the following line before filling:
DBDA.MissingSchemaAction = MissingSchemaAction.AddWithKey;
Source: The DataAdapter.Fill method does not set all of the properties of the DataTable and DataColumn objects
EDIT 2:
Or for result sets without a key:
DBDA.FillSchema(DBDS, SchemaType.Source);
Or:
DBDA.FillSchema(DBDS, SchemaType.Mapped);

Datagridview is not displaying anything no column header nor data

I have seen many question and applied their results but nothing happens I still do not see any column header/data in the gridview.
The code is simple, I do get data in Dataset and I see it in debug and I simply assign to datagridview using
datagridviewjobs.Datasource=tempJobsDataset;
But nothing happens it stays empty!
You cannot set the Datasource of a grid to a whole DataSet, a DataSet could have dozens of tables associated with it, so it will not know which table to bind to. Try this instead
datagridviewjobs.Datasource=tempJobsDataset.Tables("MyTable");
If you fill it after asigning you have to accept the changes on the table in order to get the gridview to update...
here is a complete initialisation whith a wrapping class.
private void init(DataGridView datagridview, IHave_A_DataTable x)
{
datagridview.DataSource = x.GetDataTable();
datagridview.Columns[datagridview.ColumnCount - 1].AutoSizeMode = DataGridViewAutoSizeColumnMode.Fill;
datagridview.CurrentCell = datagridview[0, datagridview.RowCount - 1];
x.Changed += new EventHandler((o, e) =>
{
IHave_A_DataTable sender = o as IHave_A_DataTable;
sender.GetDataTable().AcceptChanges();
});
}
In addition a dataset stores more than onwe table but the gridview can only accept a data table.

How do I bind a DataTable with complex data objects to a GridView?

My DataTable is programatically generated, and contains objects of type JobInstance in the cells.
DataTable dataTable = new DataTable();
// first column - label
dataTable.Columns.Add("JobType", typeof(string));
// other columns (data-driven)
foreach(string config in Configurations)
{
DataColumn col = new DataColumn(config, typeof(JobInstance)); // !!!
dataTable.Columns.Add(col);
}
// rows
foreach(string jobType in JobTypes)
{
DataRow row = dataTable.NewRow();
row["JobType"] = jobType;
foreach(string config in Configurations)
{
row[config] = GetJobInstance(jobType, config); // returns a JobInstance object
}
dataTable.Rows.Add(row);
}
When I bind this DataTable to an ASP GridView, it only displays the first column. But if I remove the "typeof(Job)" when creating the column (the line with // !!!), the GridView displays all the columns by showing the result of JobInstance.ToString() in the cells. However, I have no access to the underlying JobInstance object, only the string values that are being displayed.
I need access to the JobInstance object when displaying the GridView (for example in the OnRowDataBound), because I want to access the fields inside JobInstance for each cell to determine formatting options, and add other links in each cell.
Is this possible?
Thanks.
Yes, This is possible.
Define a new Class for your JobInstance and put your fields in it, and for the sake of clarity let it be in a separate file.
in your aspx code-behind, define a List<JobInstance> collection, then fill it in the instances of JobInstance, then assign this list to the DataSource of your `GridView'.
To be able to access the instances of List<JobInstance>, make the list be a field-member in your aspx page's class.
Hope that helps, let me know if you need any help.

Categories

Resources