update of dataset doesn't work? WPF C# - c#

I've a WPF situation with one page that has a datagrid with clients, and another page with field to fill in a new client.
I want the "new-client-page" to search the highest clientID en increment that with 1 for the new client, this sounds very simple, but I've a problem with it.
In the table adapter of the table, I added a new query: SELECT MAX(clientID) FROM clients
I execute the query with:
DataSet1TableAdapters.klantenTableAdapter tableAdapter = new DataSet1TableAdapters.klantenTableAdapter();
DataSet1 datasetvar = new DataSet1();
int returnValue = (int)tableAdapter.GetMaxKlantnr();
This works fine once. I get the highest value in returnValue, but if I go for the second time to the "new-client-page", the clientID is still the same..
I tried to update the dataset with tableAdapter.Update(datasetvar); but that doesnt make sense.. :(

Lars what database are you using? Set the ID column as identity and it will be auto generated by the db, your solution would not support any concurrency anyway and it's not the way to go to read the max id and add 1 to it...

You need to set it when you add a new row. It is then executed as an INSERT by the adapter. Adapters commit only changes.
The code would look almost like:
var newRow = ClientTable.NewRow();
newRow["ClientID"] = GetNewID();
...set other fields...
ClientTable.Rows.Add(newRow);
Adapter.Update(ClientTable);

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)

Simple test of DataTable.Select() is not getting any results when it should

I am trying to test different queries on a dataset from an existing Sql Server DB. I need to be able to grab a row by its id column for the project I am doing. What seems like a straightforward query gets no results however.
DataSet prodspdata = new prodspDataSet();
DataRow[] load;
load = prodspdata.Tables["TripNumber"].Select("[ctripnumber] = '21605178'");
Console.WriteLine(load.Length);
Console.ReadLine();
Output from load.Length is 0. I understand from documentation that load should be an array of rows returned from the Select() query. I know that this result exists from looking in the data so I expect load.length to be 1.
I tested a query from within Sql Server Management Studio to make sure I wasn't missing something:
SELECT * FROM TripNumber WHERE ctripnumber = '21605178'
This returns the 1 row as expected.
Is my Select query bad? I am brand new to C#, am I testing it wrong? I simply need to know if load contains any results so I know that my query works as intended.
EDIT: So it appears my DataTable has no rows.
DataTable loadTable;
loadTable = prodspdata.Tables["TripNumber"];
Console.WriteLine(loadTable.Rows.Count);
Console.ReadLine();
This returns 0 rows whereas loadTable.Columns.Countgives me 133
Did I miss a step, perhaps something to do with a TableAdapter?
Don't use the square brackets in your filtering.
load = prodspdata.Tables["TripNumber"].Select("ctripnumber = '21605178'");
Also remember that you can use Visual Studio debugger to see the contents of DataSets and Datatables.
I had not actually filled my dataset with anything, which was causing the problem. I severely neglected the TableAdapter. The code I added was:
prodspDataSetTableAdapters.TripNumberTableAdapter ta = new prodspDataSetTableAdapters.TripNumberTableAdapter();
DataTable loadTable = ta.GetData();
So without a TableAdapter instantiated and a table filled with the data (based on the TableAdapter's settings) you basically get an empty schema of the database with tables, columns and no values. Rookie mistake.

C# - INSERTING new row in database table using DATASETS fails?

I have a database containing one table with several columns, first of which is the auto generated ID.
I've created a dataset (.xsd) from it, and I'm trying to use it for reading, writing into and updating the database.
So far, reading works fine, all I have to do is:
var reservations = new DataSet1.reservationsDataTable();
var ta = new DataSet1TableAdapters.reservationsTableAdapter();
ta.Fill(reservations);
for (int i = 0; i < reservations.Rows.Count; i++)
{
// read whatever I need here...
}
However, when I try updating my database with these lines, it fails and it doesn't create a new record in my database:
var ta = new DataSet1TableAdapters.reservationsTableAdapter();
ta.Insert(LastName,Arrival,Departure);
Of course, I previously initialize variables LastName, Arrival and Departure with the user input. I also tried creating a new Insert query, but same thing happens, even worse - it wants a string type of input for a DateTime (Arrival, Departure).
I'm not sure what I'm doing wrong...
P.S - I also need to UPDATE my database using datasets later, so I really need to figure this out.
Thank you!

Selectively filling a dataset

I need to change what rows are grabbed from the database based upon a few flags. How can I do this? My current code is like this:
this.tblTenantTableAdapter.Fill(this.rentalEaseDataSet.tblTenant);
Say if I wanted only rows that the id was greater than 50, how would I do that?
Edit:
The code to access the database was autogenerated by the original programmer a long time ago though VisualStudio. I don't know exactly how to get a connection from the autogenerated code. If I could do that I know how to use the SqlDataAdapter
Why wouldn't you use a WHERE clause in your SQL query?
Also, I would hope you don't use your ID field for anything like this. If you just want the first 50 selections, you may want to use a TOP 50 clause or something similar.
For some info on how to do this with your TableAdapter: http://www.shiningstar.net/ASPNet_Articles/DataSet/DataSetProject7.aspx
On the server side you can use plaid old SQL:
SELECT * FROM TBLTENANT WHERE id > 50
On the client side:
rentalEaseDataSet.tblTenant.DefaultView.RowFilter = "id > 50";
I would imagine that the simplest way is to change whatever SQL query is running behind the scenes using a WHERE clause.
Your TableAdapter needs to have the CommandText specified to a SQL query with a WHERE clause.
private void InitCommandCollection() {
this._commandCollection = new global::System.Data.SqlClient.SqlCommand[1];
this._commandCollection[0] = new global::System.Data.SqlClient.SqlCommand();
this._commandCollection[0].Connection = this.Connection;
this._commandCollection[0].CommandText = #"SELECT * FROM MyTable WHERE ID >50;";
this._commandCollection[0].CommandType = global::System.Data.CommandType.Text;
}
NOTE: The above query is just an example, not meant as a solution query. The format of the above code is used to edit the generated Table Adapter designer class's InitCommandCollection() method so you can specify your own SQL. It is possible your class already has some SQL in here, in which case you could just alter it.

Categories

Resources