Save Changes in DataGridView C# - c#

After calling the table from the database into the dataGridView and entering data into the empty cells of this DataGridView, I want to save this data to my database by clicking on the button, but I don’t know how to do this
This is how I access db:
public MySqlConnection mycon;
public MySqlCommand mycom;
public string connect = "Server=localhost;Database=base;Uid=root;password=pas;charset=utf8";
public SD.DataSet ds;
mycon = new MySqlConnection(connect);
mycon.Open();
MySqlDataAdapter ms_data = new MySqlDataAdapter(script, connect);
SD.DataTable table = new SD.DataTable();
ms_data.Fill(table);
dataGridView1.DataSource = table;
mycon.Close();

Pains me to write it, but this should be the minimum set of calls necessary:
MySqlDataAdapter ms_data = new MySqlDataAdapter(script, connect);
new MySqlCommandBuilder(ms_data);
ms_data.Update(dataGridView1.DataSource as DataTable);
You need an adapter, and then passing it to a command builder should make the CB read the select command loaded into the adapter and use it to populate the adapter's other commands (insert/update/delete), then you can call Update and pass a modified table and the rows in the table will be examined to see if they're new, modified or deleted and the appropriate command will be called to persist the changes
script needs to be an SQL that selects the primary key columns from the DB
Why "pains to say it" ? Because it's such a long winded, hard work way of doing database access compared to even the technology that replaced it, let alone something modern like EF. If you're liking working with datatables, consider:
Add a new DataSet type of file to the project
Open it, right click the surface, add a tableadapter
Configure the connection string
Configure a "select that returns rows" query like SELECT * FROM sometable WHERE id = #someId
A tableadapter and datatable pair appear, that mimick the table in the database
Switch to a form, open the data sources window (View Menu, Other Windows), drag the table to the form - a datagridview, dataset, tableadapter, navigator etc appear.
Everything is wired up to go.
Your tableadapter is a dataadapter on steroids, and will Fill/Update the datatable from/to database. The datatable has named columns and is generally much nicer to work with. All the code for loading and saving is already written into the FormX.cs so you can take a look, but it basically amounts to:
var x = new BlahBlahTableAdapter();
x.FillBy(this.BlahBlahDataSet, someIdTextBox.Text);
Filling the datatable that is binded to the grid will cause the data to appear in the grid automatically.
Saving is similarly simple:
var x = new BlahBlahTableAdapter();
x.Update(this.BlahBlahDataSet);
Footnote: I believe you need MySQL Tools For Visual Studio installed for this to play nice. Also, you might encounter issues if you made a NetCore/Net5+ WinForms project because some elements of new .NET don't support winforms properly
Footnote2: See https://bugs.mysql.com/bug.php?id=99199 if you're wondering why the tableadapter wizard option "Refresh the dataset" is not working
If you want to try something more modern, there are lots of good EF tutorials out there. EF is a tool that uses a database to create objects in your code. The data for the objects lives in the database and is automatically transported back and forth from/to objects/database by EF. If you get into it, EF Core Power Tools is a very useful VS addin to have

Related

Datagridview Error After Rowfilter -> Data Update/Add and Fill Process

I have a datagridview which filled by SQL table. When I search a value with a textbox in the data and update/insert the some values and use fill function again I get this error
"System.NullReferenceException HResult=0x80004003 Message=Object reference not set to an instance of an object."
Note: No problem with update, insert and fill function without using search textbox
This is my fill function;
DataTable dt1 = new DataTable();
void fill()
{
try
{
//txtSearch.Clear();
dt1.Rows.Clear();
dt1.Columns.Clear();
SqlDataAdapter da = new SqlDataAdapter("Select * From Bilgisayar_Zimmet", bgl.baglanti());
da.Fill(dt1);
dataGridView1.DataSource = dt1;
}
catch (Exception ex)
{
MessageBox.Show(ex.Message, "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
}
}
Here is my search code;
private void txtSearch_TextChanged(object sender, EventArgs e)
{
(dataGridView1.DataSource as DataTable).DefaultView.RowFilter = String.Format("Name LIKE '%{0}%' OR Surname LIKE '%{1}%'", txtSearch.Text, txtSearch.Text);
}
I think my problem is txtSearch_TextChanged methods. It blocks fill function because in the dgv there is filtered row.
So, if I was hanging my hat on the DataTable peg I would do the following in a net framework project - large parts of this process are switched off in netcore+ projects because of bugs/incomplete support in VS. It does work in core as a runtime thing, just not design (so you could do this design in framework, and import the code to core, or even have a core and netfw project that edit the same files..):
I would...
Have my table in my DB:
Make a new DataSet type of file:
Right click the surface and add a tableadapter
Add a connection string, and choose SELECT
Write a query - personally I recommend putting like select * table where primarykeycolumn = #id here rather than selecting all, but you apparently want to have all 2000 rows in the client. Have a look in advanced and check that all 3 boxes are ticked
Give the query a decent name - if youre selecting by#id then call it FillById etc
You're done
Flip to the forms designer for your form, and open the data sources panel (View menu, Other windows). Drag the node representing the table onto the form:
A load of stuff appears at the bottom, a grid appears, the toolbar appears. Add a textbox too (the asterisk) - we'll use it for the filter
Double click the textbox; you'll see the minimal code VS has written to load the data. The rest of the code is in the Form.Designer.cs file and the DataSet.Designer.cs file if you want to have a look. In our form we just need this code:
I'd paste it as text, but genuinely, the only line of it I actually wrote was the one line in the TextChanged:
bilgisayarZimmetBindingSource.Filter = String.Format("FirstName LIKE '%{0}%' OR LastName LIKE '%{0}%'", txtSearch.Text);
you can reuse placeholders in a string format by the way. Note that the filtering is done on the bindingsource - a device that VS has put between the table and the grid. BindingSources are very useful; they maintain position/current-row knowledge and can transmit updates up and down between grid and datatable. When related data is shown they automatically filter child datatables to show only children of a current parent
We can now run the app and load data, save data and filter data, all off that one line of code. I add a bunch of people:
See how their IDs are all negative? The datatable has an autoincrement that steps -1 then the db will calc the values when we hit save and they will be retrieved automatically, patched into the datatable and the grid will update, all automatically. If these rows were the parent of other rows, the child rows ParentId would be updated too via a DataRelation in the dataset:
We haven't had to refill anything; VS just got the single IDs out the DB
I can type a filter:
No errors; these people are editable, saveable, I can clear the textbox and see everyone etc.
So what is this voodoo? How does it work? Well.. It's not much different to yours. If we go rummaging in DataSet.Designer.cs we can find the queries that pull and push data:
read and write queries right there; the tableadapter wraps a dataadapter and sets up the relevant queries, the datarow state drives whether an insert or update etc is fired.. All the code in your question and more is written by VS and tucked away.. The dataset has strongly typed tables in that inherit from DataTable, and rows that have proper properties rather than just an array you access via string column names
We would just load data:
var dt = tableAdapter.GetData();
var dt2 = new BlahBlahDataTable();
tableAdapter.Fill(dt2);
//if we put parameters in the dataset query like SELECT * FROM person WHERE name = #n
var dt3 = new PersonDataTable();
ta.FillByName(dt3, "John");
The strongly typed datatables are nicer to LINQ with
//weakly typed datatable
dt.Rows.Cast<DataRow>()
.Where(r => r.Field<string>("FirstName").Contains("John") || r["LastName"].ToString().Contains("John"))
//strongly typed datatable
dt.Where(r => r.FirstName.Contains("John") || r.LastName.Contains("John"))
etc..
Final point: I personally wouldn't download 2000 rows into the client and hold them there. I'd download what I needed as and when. The longer you have something the more out of date it's likely to be, the more meory it consumes and the more your server disk/network gets battered serving up volumes of data noone uses. I understand the tradeoff between loading all the rows and then burning the client's CPU to search them rather than the server's, and that "string contains" are hard to optimally search/index, but still - I'd look at full text indexing and leaving them on the server
When you're making your query above, don't put SELECT * FROM BilgisayarZimmet - put SELECT * FROM BilgisayarZimmet WHERE ID = #id and call it FillById. Then when you're done with that one, add another query, SELECT * FROM BilgisayarZimmet WHERE FirstName LIKE #n OR LastName LIKE #n and call it FillbyNameLike
In code, call it like this:
tableAdapter.FillByNameLike(theDatasetX.BilgisayarZimmet, "%" + txtSearch.Text + "%")
Or even better let the user supply the wildcard, tohugh people are perhaps used to writing * so:
tableAdapter.FillByNameLike(theDatasetX.BilgisayarZimmet, txtSearch.Text.Replace("*", "%"))
When the TableAdapter fills it will clear the datatable first, so no need to clear it yourself. To turn this behaviour off, set tableAdapter.ClearBeforeFill = false. This way your grid shows only the data downlaoded from the DB, and that's a small, more recent dataset, than querying it all at app start
All in, VS write a lot of tedious code,and makes life a lot nicer. I use these nearly exclusively if operating in DataTable mode

C# DataGridViewRow - is new or updated row

I have DataGridView where I am showing data read from database:
DataSet ds = new DataSet();
sqlDa.Fill(ds);
dgView.DataSource = ds.Tables[0]
After adding all of the rows in the UI, I need to to SQL UPDATE of rows that previously read from database, and do INSERT for new rows by clicking Save button (I don't save rows one by one when adding, just all of them when I click the Save button):
foreach (DataGridViewRow dgvRow in dgView.Rows)
{
// do insert for new rows, and update for existing ones from database
}
How can I know what rows are newly added and what are not? Can I add some type of attribute to every row that is read from database so that can I know that they need to be updated?
How can I know what rows are newly added and what are not?
You don't need to; the datatable the DGV is showing is already tracking this. If you make a SqlDataAdapter and plug a SqlCommandBuilder into it see the example code in the docs so that it gains queries in its InsertCommand/UpdateCommand/DeleteCommand properties (or you can put these commands in yourself, but there isn't much point given that a command builder can make them automatically) then you just say:
theDataAdapter.Update(theDataTable);
If you didn't save it anywhere else you can get it from the DataSource of the DGV:
theDataAdapter.Update(dgView.DataSource as DataTable);
Ny the way, the word "Update" here is nothing to do with an update query; Microsoft should have called it SaveChanges. It runs all kinds of modification query (I/U/D) not just UPDATE
If you really want to know, and have a burning desire to reinvent this wheel, you can check a DataRow's RowState property, and it will tell you if it's Added, Modified or Deleted, so you can fire the appropriate query (but genuinely you'd be reimplementing functionality that a SqlDataAdapter already has built in)
All this said, you might not be aware that you can make your life massively easier by:
Add a new DataSet type of file to your project (like you would add a class). Open it
Right-click in the surface of it, choose add TableAdapter
Design your connection string in (once)
Enter your query as a "select that produces rows" like SELECT * FROM SomeTable WHERE ID = Id (it's advisable to use a where clause that selects on the ID; you can add more queries later to do other things, like SELECT * FROM SomeTable WHERE SomeColumn LIKE #someValue but for now selecting on ID gives you a base query to use that is handy for loading related data). You can also use existing or new stored procs if you want
Give it a sensible name pair like FillById, GetDataById - FillBy fills an existing table, Get gets a new one
Finish
You'll now have objects available in your code that are wrappers data adapters and datatables - same functionality but more nicely strongly typed
e.g. you can fill your grid with:
var ta = new SomeTableAdapter();
dgView.DataSource = ta.GetDataByFirstName("John%"); //does select * from table where firstname like 'john%' into a datatable
The datatables are strongly typed, so you don't access them like this:
//no
foreach(DataRow row in someTable.Rows){
if((row["someColumn"] as string) == "hello" && row.IsNull("otherColumn"))
row["otherColumn"] = "goodbye";
}
You have named properties:
//yes
foreach(var row in someTable){
if((row.SomeColumn == "hello" && row.IsOtherColumnNull())
row.OtherColumn = "goodbye";
}
Much nicer. LINQ works on them too, without AsEnumerable or Cast and endless casting the values.
It's not magic; VS writes boatloads of code behind the scenes for you - check in the YourDataSet.Designer.cs file - hundreds of SqlCommands, fully parameterized, for all the table operations (Select/Insert/Update/Delete), all base don typing a SELECT command into a tool pane. It's quite nice to use really, even all these years later.
Oh, but the designer doesn't work very nicely in net core. They're really lagging behind on fixing up the bugs that netcore brings (other priorities)

How do statements in SQL correlate with C#

I have created a database and i have linked it with a Windows Form Application
in Visual Studio and it is written in Visual C#. I am curious to know whether or not the buttons that i have added in the windows form application will correspond with
the statements that are written in SQL .
Will I need to implement code in the backend of SQL or C# or just one of the two.
You could try using something called Entity Framework, which can be installed if you right-click the project name and choose to Manage NuGet packages.
This will set up a framework for you so that you don't have to write any C# code connecting your form with your database. It is some what easy to use because it creates Table Adapters which makes the connection from a table in your database to your code, an example would be:
var myTableAdapter = new myDatabase_TableAdapters.myTableTableAdapter();
System.Data.DataTable myDataTable = myTableAdapter.GetData();
Now you have the data from your table in the SQL database in a DataTable and you have your TableAdapter as a connection between the two.
Say you want to take input from your users, then for our example consider the data comes from your text boxes then you could do something like:
string vehicleRegNum = vehicleRegNumTextBox.Text,
make = makeTextBox.Text,
engineSize = engineSizeTextBox.Text,
dateReg = dateRegTextBox.Text,
rentPerDay = rentPerDayTextBox.Text;
bool avail = availCheckBox.Checked;
myTableAdapter.Insert(vehicleRegNum, make, engineSize, dateReg, rentPerDay, avail);
This .Insert will add this data to your database (Here I am assuming all the text boxes go to one table in your database and that the order is as I have given them).
So in general,
Get Entity Framework setup in NuGet
Setup a table adapter from a table in your database
Update it with user input
A side note: I always like to add a user and date column to these kinds of tables so you can use:
string usr = System.Environment.UserName
var entryDate = System.DateTime.Now;
Clarification: It was pointed out to me by #DanRayson in the comments that I should be more clear about myDatabase_TableAdapters. It is not exactly a TableAdapter, it is however a TableAdapter which EntityFramework creates in the background for the user.

Getting data out of tableadapters

I just can't understand this thing: I have an access DB I want to query using c#. I read there are this useful tableadapters that make most of the job for you.
Here is what I have done:
Added new data source to the project though the wizard
Added a table adapter via the toolbox. The table adapter has a query named GetData that I want to use to retrieve results.
I add these lines to the code to make my textbox show my first result
Database_CeciDataSetTableAdapters.BaseTableAdapter tableadapter = new Database_CeciDataSetTableAdapters.BaseTableAdapter();
textBox1.Text = tableadapter.GetData()[0].ToString();
Here is what I get in the textbox:
WordAddIn5.Database_CeciDataSet+BaseRow
Can anyone tell what am I doing wrong?

Invalid object name 'dbo.Item'

I'm kind of new to C# and am trying out the convenient looking adaper-dataset combo with local database. while I was amazed at how easy and useful it was I stumbled upon an issue:
Whenever I try to fill the Item table with the adapter I get Invalid object name SQLexception,
I use the same Dataset for all my tables, dsOns.Users / dsOns.Items.
The problem is, the users adapter does recognize and successfully works with Users database(dbo.Users) while the items adapter cannot find the table(dbo.Item).
Heres some sniplets:
UsersTableAdapter uAdapter = new UsersTableAdapter();
ItemTableAdapter iAdapter = new ItemTableAdapter();
Users select:
SELECT Id, name, password, sold, isLogged, lastLog FROM dbo.Users
Item select:
SELECT Id, name, sale_price, min_price, cog FROM dbo.Item
Both use the same connection string:
Data Source=(LocalDB)\v11.0;AttachDbFilename=|DataDirectory|\ons_data.mdf;Integrated Security=True;Connect Timeout=30
Also note that in Dataset designer, the adapter works fine, it selects successfully. The error only occurs in code.
Image of the schema:
What could possibly cause this?
Okay that was weird, I solved it by simply changing the view of the database explorer, to find that the app uses two databases, in UI(dataset designer) it uses my original db, while in code it uses the same db(but outdated since I changed it via dataset designer) that was copied to debug folder, hence containing only Users table alone and resulting in the error
I am unsure if item may be considered a reserved word in some cases perhaps in the context of your connection. Might want to refer to is at [dbo].[item].
Try changing the query which you are passing to the SqlDataReader in my case it was "Select [Name],[Location],[Email] FROM [dbo].[CreateDB]" but I tried "Select Name,Location,Email FROM YourTableName" and it worked, you can also use "Select * FROM YourTableName".

Categories

Resources