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".
Related
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
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)
In my program, I want to select some bookIDs into a tempDB for later queries like this (using Dapper extension):
using (var conn = new SqlConnection(connStr)) {
conn.Execute("SELECT bookID INTO #tempdb WHERE ... FROM Books");
int count = conn.ExecuteScalar<int>("SELECT COUNT(*) FROM #tempdb");
var authors = conn.Query("SELECT * FROM #tempdb LEFT JOIN BookAuthors ON ...");
}
However when I execute the page, I get following exception:
Invalid object name '#tempdb'.
It seems that life-cycle of #tempdb is only valid in first query ?
It looks like you're using the implicit connection opening/closing. This will indeed cause problems with transient objects. If you need temp tables between queries, you will need to manually open the connection before you execute any such queries. This should then work fine, and many examples in the test suite make use of temp tables in the way.
However, from a practical standpoint, making use of temporary tables to transfer state between queries is ... awkward. In addition to being brittle, it isn't good for the plan cache, as #foo has a different meaning between all uses on different connection (including reset but reused connections).
I found a previous poster who met the same problem and his solution.
Using dapper, why is a temp table created in one use of a connection not available in a second use of the same connection
The post indicates that you have to "CREATE TABLE #tempdb" explicitly in your SQL first and everything goes fine. Even the poster himself don't know why such style of coding works.
I'm trying to populate a DataTable, to build a LocalReport, using the following:
MySqlCommand cmd = new MySqlCommand();
cmd.Connection = new MySqlConnection(Properties.Settings.Default.dbConnectionString);
cmd.CommandType = CommandType.Text;
cmd.CommandText = "SELECT ... LEFT JOIN ... WHERE ..."; /* query snipped */
// prepare data
dataTable.Clear();
cn.Open();
// fill datatable
dt.Load(cmd.ExecuteReader());
// fill report
rds = new ReportDataSource("InvoicesDataSet_InvoiceTable",dt);
reportViewerLocal.LocalReport.DataSources.Clear();
reportViewerLocal.LocalReport.DataSources.Add(rds);
At one point I noticed that the report was incomplete and it was missing one record. I've changed a few conditions so that the query would return exactly two rows and... surprise: The report shows only one row instead of two. I've tried to debug it to find where the problem is and I got stuck at
dt.Load(cmd.ExecuteReader());
When I've noticed that the DataReader contains two records but the DataTable contains only one. By accident, I've added an ORDER BY clause to the query and noticed that this time the report showed correctly.
Apparently, the DataReader contains two rows but the DataTable only reads both of them if the SQL query string contains an ORDER BY (otherwise it only reads the last one). Can anyone explain why this is happening and how it can be fixed?
Edit:
When I first posted the question, I said it was skipping the first row; later I realized that it actually only read the last row and I've edited the text accordingly (at that time all the records were grouped in two rows and it appeared to skip the first when it actually only showed the last). This may be caused by the fact that it didn't have a unique identifier by which to distinguish between the rows returned by MySQL so adding the ORDER BY statement caused it to create a unique identifier for each row.
This is just a theory and I have nothing to support it, but all my tests seem to lead to the same result.
After fiddling around quite a bit I found that the DataTable.Load method expects a primary key column in the underlying data. If you read the documentation carefully, this becomes obvious, although it is not stated very explicitly.
If you have a column named "id" it seems to use that (which fixed it for me). Otherwise, it just seems to use the first column, whether it is unique or not, and overwrites rows with the same value in that column as they are being read. If you don't have a column named "id" and your first column isn't unique, I'd suggest trying to explicitly set the primary key column(s) of the datatable before loading the datareader.
Just in case anyone is having a similar problem as canceriens, I was using If DataReader.Read ... instead of If DataReader.HasRows to check existence before calling dt.load(DataReader) Doh!
I had same issue. I took hint from your blog and put up the ORDER BY clause in the query so that they could form together the unique key for all the records returned by query. It solved the problem. Kinda weird.
Don't use
dr.Read()
Because It moves the pointer to the next row.
Remove this line hope it will work.
Had the same issue. It is because the primary key on all the rows is the same. It's probably what's being used to key the results, and therefore it's just overwriting the same row over and over again.
Datatables.Load points to the fill method to understand how it works. This page states that it is primary key aware. Since primary keys can only occur once and are used as the keys for the row ...
"The Fill operation then adds the rows to destination DataTable objects in the DataSet, creating the DataTable objects if they do not already exist. When creating DataTable objects, the Fill operation normally creates only column name metadata. However, if the MissingSchemaAction property is set to AddWithKey, appropriate primary keys and constraints are also created." (http://msdn.microsoft.com/en-us/library/zxkb3c3d.aspx)
Came across this problem today.
Nothing in this thread fixed it unfortunately, but then I wrapped my SQL query in another SELECT statement and it work!
Eg:
SELECT * FROM (
SELECT ..... < YOUR NORMAL SQL STATEMENT HERE />
) allrecords
Strange....
Can you grab the actual query that is running from SQL profiler and try running it? It may not be what you expected.
Do you get the same result when using a SqlDataAdapter.Fill(dataTable)?
Have you tried different command behaviors on the reader? MSDN Docs
I know this is an old question, but for me the think that worked whilst querying an access database and noticing it was missing 1 row from query, was to change the following:-
if(dataset.read()) - Misses a row.
if(dataset.hasrows) - Missing row appears.
For anyone else that comes across this thread as I have, the answer regarding the DataTable being populated by a unique ID from MySql is correct.
However, if a table contains multiple unique IDs but only a single ID is returned from a MySql command (instead of receiving all Columns by using '*') then that DataTable will only organize by the single ID that was given and act as if a 'GROUP BY' was used in your query.
So in short, the DataReader will pull all records while the DataTable.Load() will only see the unique ID retrieved and use that to populate the DataTable thus skipping rows of information
Not sure why you're missing the row in the datatable, is it possible you need to close the reader? In any case, here is how I normally load reports and it works every time...
Dim deals As New DealsProvider()
Dim adapter As New ReportingDataTableAdapters.ReportDealsAdapter
Dim report As ReportingData.ReportDealsDataTable = deals.GetActiveDealsReport()
rptReports.LocalReport.DataSources.Add(New ReportDataSource("ActiveDeals_Data", report))
Curious to see if it still happens.
In my case neither ORDER BY, nor dt.AcceptChanges() is working. I dont know why is that problem for. I am having 50 records in database but it only shows 49 in the datatable. skipping first row, and if there is only one record in datareader it shows nothing at all.
what a bizzareeee.....
Have you tried calling dt.AcceptChanges() after the dt.Load(cmd.ExecuteReader()) call to see if that helps?
I know this is an old question, but I was experiencing the same problem and none of the workarounds mentioned here did help.
In my case, using an alias on the colum that is used as the PrimaryKey solved the issue.
So, instead of
SELECT a
, b
FROM table
I used
SELECT a as gurgleurp
, b
FROM table
and it worked.
I had the same problem.. do not used dataReader.Read() at all.. it will takes the pointer to the next row. Instead use directly datatable.load(dataReader).
Encountered the same problem, I have also tried selecting unique first column but the datatable still missing a row.
But selecting the first column(which is also unique) in group by solved the problem.
i.e
select uniqueData,.....
from mytable
group by uniqueData;
This solves the problem.
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.