Use DatabaseTable Field Description when DataBinding - c#

Im using C#, Linq and DataGridView.
I bind my grid like so.
var db = new DataClassesDataContext();
var results = db.Results;
.... much later on in the code..
dataGridView1.DataSource = results;
It uses the Field names for the columns which saved me heaps of time but is it possible to use the FIELD description so I have better col names for my clients.
What Im doing right now is using UNDERSCORES in field names
ID
USER_NAME
SUR_NAME
so in the ColumnAdded event I do
private void dataGridView1_ColumnAdded(object sender, DataGridViewColumnEventArgs e)
{
e.Column.HeaderText = e.Column.HeaderText.Replace("_"," ");
}
But its just a work around..

If you know that these columns will always represent specific fields in your Database then you could always just set them up directly after you set the datasource for the GridView.
DataGridView.Columns[0].HeaderText = "Column 1";
DataGridView.Columns[1].HeaderText = "Column 2";
DataGridView.Columns[2].HeaderText = "Column 3";
DataGridView.Columns[3].HeaderText = "Column 4";
It's always going to depend on how different the naming convention in your database is compared with how you want to represent each column in the DataGridView. It wont always be possible to manipulate them like you have done in your example.
One thing I have noticed that maybe missing. If your planning on allowing the user to edit the data in the DataGridView and committing changes to the DataContext, you will need to use a Binding Source between these two objects in order for it to work.

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

SQLite error Insufficient parameters supplied to the command

When trying to change the active column in the following table 1.
I get the error
Error insufficient parameters supplied to the command
and I cannot figure out for the life of me what is wrong with the code. Please help.
private void dataGridView1_SelectionChanged_1(object sender, EventArgs e)
{
SQLiteConnection sqlConnection = new SQLiteConnection();
sqlConnection.ConnectionString = "datasource = SubjectTable.db";
if (dataGridView1.SelectedRows.Count > 0)
{
ID = dataGridView1.SelectedRows[0].Cells[1].Value.ToString();
//Define SELECT Statement
string commandText = "SELECT * FROM SubjectTable WHERE ID=" + ID;
//Create a datatable to save data in memory
var datatable = new DataTable();
SQLiteDataAdapter myDataAdapter = new SQLiteDataAdapter(commandText, sqlConnection);
sqlConnection.Open();
//Fill data from database into datatable
myDataAdapter.Fill(datatable);
//Fill data from datatable into form controls
CMBactive.Text = datatable.Rows[0]["Active"].ToString();
TBsubjectBUD.Text = datatable.Rows[0]["Budget"].ToString();
sqlConnection.Close();
}
}
You are getting these problems, because you are trying to do too much things in one procedure. You should separate your concerns
Separate the fetching of the data from the database from displaying this data; separate also that you do this of event selection changed.
This has the advantage that you can reuse your code more easily: if you want to do the same because of a Button press, you can reuse the code. After that it is a one-liner code if you want to add a menu item doing the same.
It is easier to test your code if you have a separate method to query the database, or a separate method to fill your controls CmbActive and TbSubjectBud.
It is easier to change your code, for instance, if you will not use SQLite anymore, but entity framework to fetch your data, the displaying and the button handling won't notice this. Only the procedure to fetch the data needs to be changed.
This makes unit testing easier: instead of a real database, you can mock the database with a Dictionary for the tests.
Finally: when using Winforms, don't fiddle with the Cells directly, use the DataSource of the DataGridView to fill and read the data. Again: separate the data from how it is displayed.
First Your actual problem: query the data
So you have an Id, and you want to fetch the values of columns Active and Budget from all Subjects from the database that have this Id. Don't fetch properties that you won't use!
Database handling
First we need a class Subject to put the data that you fetch from table SubjectTable. If you put all columns of this table in it, you can reuse the class for other queries. However. you don't have to fill in all fields. It depends how often you will call this method whether it is wise to fill all properties or only some.
Some people don't like this. Consider to fetch always all columns (inefficient), or to create classes for different queries (a lot of work).
class Subject
{
public int Id {set; set;}
public string Name {get; set;}
public DateTime StartDate {get; set;}
public string Active {get; set;}
public Decimal Budget {get; set;}
}
Create a method to fetch the Active and Budget from tableSubjects with Id, or null if there is not subject with this Id.
Put all your database queries in a separate class. For instance class Repository. You hide that it is in a database, if in future you want to save it in a CSV-file, or JSON format, no one will notice (nice if you want to use it in a unit test!)
private Subject FetchBudgetOrDefault(int id)
{
const string sqlText = #"SELECT Active, Budget FROM SubjectTable WHERE ID = #Id";
using (var dbConnection = new SQLiteConnection(this.dbConnectionString))
{
using (var dbCommand = dbConnection.CreateCommand()
{
dbCommand.Commandtext = sqlText;
dbCommand.Parameters.AddWithValue("#Id", id);
using (var dbReader = dbCommand.ExecuteReader())
{
if (dbReader.Read())
{
// There is a Subject with this id:
return new Subject()
{
Id = id,
Active = dbReader.GetString(0),
Budget = (decimal)dbReader.GetInt64(1) / 100.0D,
};
}
else
{
// no subject with this Id
return null;
}
}
}
}
}
I assumed that the decimal Budget is saved as long * 100 on purpose, to show you that by separating your concerns it is fairly easy to change the database layout without having to change all users: if you want to save this decimal in SQLite as a REAL, then the queries are the only place where you have to change the data.
By the way: this method also solved your problem: ID can't be an empty string!
If you won't do this query 1000 times a second, consider to fetch all columns of Subject. This is a bit less efficient, but easier to test, reuse, and maintain.
Display the fetched data in your form
Currently you display the data in a ComboBox and a TextBox. If you separate your concerns, there will only be one place where you do this. If you want to display the data in a Table, or do something else with it, you only have to change one place:
public void Display(Subject subject)
{
this.comboBoxActive.Text = subject.Active;
this.textBoxBudget.Text = subject.Budget.ToString(...);
}
Bonus points: if you want to change the format of the displayed budget, you'll only have to do this here.
Read and Write the DataGridView
It is seldom a good idea to read and write the cells of a DataGridView directly. It is way to much work. You'll have to do all type checking yourself. A lot of work to test and implement small changes in the displayed data.
It is way easier to use the DataSource.
In the DataSource of the DataGridView you put a sequence of similar items. If you only want to Display once, an ICollection<TSource> will be enough (Array, List). If you want to update changes automatically, use a BindingList<TSource>
In the DataGridView add columns. User property DataGridViewColumn.DataPropertyName to indicate which property should be displayed in that column.
Usually it is enough to use visual studio designer to add the columns.
If your datagridview displays Subjects, code will be like:
DataGridView dgv1 = new DataGridView();
DataGridViewColumn columnId = new DataGridViewColumn
{
DataPropertyName = nameof(Subject.Id),
...
};
DataGridView columnName = new DataGridViewColumn
{
DataPropertyName = nameof(Subject.Name),
...
};
... // other columns
dgv.Columns.Add(columnId);
dgv.Columns.Add(columnName);
...
In your forms class:
private BindingList<Subject> DisplayedSubjects {get; set;} = new BindingList<Subject>();
// Constructor:
public MyForm()
{
InitializeComponent();
this.dgv1.DataSource = this.DisplayedSubjects();
}
void FillDataGridView()
{
using (var repository = new Repository())
{
IEnumerable<Subject> fetchedSubjects = repository.FetchAllSubjects();
this.DisplayedSubjects = new BindingList<Subject>(fetchedSubjects.ToList();
}
}
This is all that is needed to display all fetched subjects. If the operator changes any cell value, the corresponding value in this.DislayedSubjects is automatically updated.
This works both ways: if you change any value in this.DisplayedSubjects, the displayed value in the DataGridView is automatically updated.
No need to read the cells directly. If you allow column reordering, or if you implement row sorting, then everything still works two ways. Because you separated the fetched data from the displayed data, you can change the display without having to change the fetched data.
Put it all together
When you get the event that the selection is changed from the datagridview you want to update Active and Budget. Let's do the from the Selected item:
void OnSelectionChanged(object sender, EventHandler e)
{
// Get the selected Subject
var selectedSubject = this.SelectedSubject;
this.Display(selectedSubject); // described above
}
Subject SelectedSubject => this.Dgv.SelectedRows.Cast<DataGridViewRow>()
.Select(row => (Subject)row.DataBoundItem)
.FirstOrDefault();
Because you separated concerns, each method is easy to understance, easy to test, easy to reuse and to change slightly: if you want to update after a Button Press or a menu item, the code will be a one liner. If you want to display other items than just Active / Budget: small changes; if you want to fetch by Name instead of Id: only limited changes needed.

DataTable doesn't bind columns caption

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.

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.

Spreading the contents of a DataTable into 3 DataGridViews

Let's say that I have a form which runs a stored procedure.
This stored procedure creates some rows in a table with pre-generated values and returns me a DataTable containing the rows created by this stored procedure.
On the form, I need to display this information on 3 different DataGridViews, so the user can change it.
The schema is the same but each of the DataGridViews will display different categories and hence hide some irrelevant columns in each of the DataGridView, but in the database, they are all part of the same table.
New rows may be added by the user on all 3 DataGridViews.
I am a bit confused how to display the information from a single DataTable into three different DataGridViews and still have an easy way to update the database with the changes made by the user to the DataGridViews.
I assume that I could break my main DataTable in three of them and then bind each DataTable to the relevant DataGridView, but wouldn't it pose problems when I will want to save the changes (updated and new rows) to the database considering that my changes are spread into 3 DataTables instead of a single one?
Would there be a better way to achieve this rather than splitting the main DataTable in the first place?
Thanks a lot in advance.
All the DataGridViews need their own DataView. The easiest way may be to use separate BindingSource components.
When you state:
dataGridView1.DataSource = dataTable1;
You are actually using the default DataView of the Table. You are looking for something like:
//untested
var view1 = new DataView(dataTable1);
dataGridView1.DataSource = view1;
var view2 = new DataView(dataTable1);
dataGridView2.DataSource = view2;
And then you can use view1, view2 to control filtering and sorting.
Thanks a lot Henk, your post led me on the right track and it solved my problem perfectly.
I can now add items on any grid views and my DataTable is updated without needing to do anything like merging like I was expecting I may have to do.
In order to try and understand the solution, I've made a little test demo, which I thought I would post here for future readers, as it includes how to filter each DataView to only include the revelant information.
It is an example code, I didn't include error checkings, etc.
private DataTable fruitsDataTable = null;
private DataView orangesDataView = null;
private DataView applesDataView = null;
private void Form1_Load(object sender, EventArgs e)
{
fruitsDataTable = new DataTable("Fruits");
// Dynamically create the DataTable schema for the sake of this example
fruitsDataTable.Columns.Add("Category", typeof(string));
fruitsDataTable.Columns.Add("Description", typeof (string));
fruitsDataTable.Columns.Add("Quantity", typeof(int));
fruitsDataTable.Columns.Add("Price", typeof(double));
// Add the fruits to the main table
fruitsDataTable.Rows.Add("ORANGE", "Fresh Oranges", 5, 5.50);
fruitsDataTable.Rows.Add("APPLE", "Granny Smith Apples", 10, 2.20);
fruitsDataTable.Rows.Add("APPLE", "Golden Apples", 40, 1.75);
fruitsDataTable.Rows.Add("ORANGE", "Bloody Oranges", 10, 7.99);
fruitsDataTable.Rows.Add("BANANA", "Ivory Coast Bananas", 5, 6.99);
mainGridView.DataSource = fruitsDataTable;
// Create a DataView for each fruit category and bind it to the relevant DataGridView control on the form
orangesDataView = new DataView(fruitsDataTable, "Category = 'ORANGE'", string.Empty, DataViewRowState.CurrentRows);
orangesGridView.DataSource = orangesDataView;
applesDataView = new DataView(fruitsDataTable, "Category = 'APPLE'", string.Empty, DataViewRowState.CurrentRows);
applesGridView.DataSource = applesDataView;
}

Categories

Resources