Apologies if this is a daft error - but have been struggling a few weeks with this and I am no further on.
The problem is - my update call from a DT based DS to an SQL Db is only inserting nerw records, not updating modifed ones.
I have a datasource (httpWebRequested html table) which I call periodically and update a DataTable belonging to a DataSet with it. Initially I parsed it all into a second DataTable and merged it with my DataSet table - but this wasnt working, so currently I have one DataTable only and either insert new rows into table or update existing values by iterating through the source and adding a row at a time to an array - checking for existance of current row, and either updating or inserting ([Session ID] is a PK, and Data[0] is corresponding unique value):-
foreach (var row in rows.Skip(1))
{
//create new list collection
var data = new List<string>();
//interate through rows
foreach (var column in row.Descendants("td"))
{
//add data to list from Table
data.Add(column.InnerText);
}
string strSelect = "[Session ID] = '"+ data[0] +"'";
DataRow[] myRow = dt.Select(strSelect);
if (myRow.Length == 1)
{
if (myRow[0][2].ToString() != data[2].ToString())
{
myRow[0][2] = data[2];
}
if (myRow[0][3].ToString() != data[3].ToString())
{
myRow[0][3] = data[3];
}
if (myRow[0][4].ToString() != data[4].ToString())
{
myRow[0][4] = data[4];
}
if (myRow[0][5].ToString() != data[5].ToString())
{
myRow[0][5] = data[5];
}
if (myRow[0][7].ToString() != data[7].ToString())
{
myRow[0][7] = data[7];
}
}
else
{
dt.Rows.Add(data.ToArray());
}
I put a bit on the end also which counts the Added and Modified:-
int modified= 0;
int added = 0;
foreach (DataRow dr in pca.chatDataSetG.Tables[0].Rows)
{
if (dr.RowState == DataRowState.Modified)
{
modified++;
}
if (dr.RowState == DataRowState.Added)
{
added++;
}
}
At this point - the counts are fine - modified pick up the rows where attributes have changed and added is correct.
When I call my update Db method - things start to go wrong:-
public static void updateSqlTable()
{
string connectionString = "Connection String here";
string qry = #"select * from chatData";
SqlConnection conn = new SqlConnection(connectionString);
try
{
SqlDataAdapter da = new SqlDataAdapter(qry, conn);
SqlCommandBuilder sb = new SqlCommandBuilder(da);
log.Info("Building Queries...");
da.UpdateCommand = sb.GetUpdateCommand();
da.InsertCommand = sb.GetInsertCommand();
da.DeleteCommand = sb.GetDeleteCommand();
log.Info("Filling Data into Adapter...");
int modified = 0;
int added = 0;
foreach (DataRow dr in pca.chatDataSetG.Tables[0].Rows)
{
if (dr.RowState == DataRowState.Modified)
{
modified++;
}
if (dr.RowState == DataRowState.Added)
{
added++;
}
}
//This is where the modified count reverts
da.Fill(pca.chatDataSetG, "Chat");
modified = 0;
added = 0;
foreach (DataRow dr in pca.chatDataSetG.Tables[0].Rows)
{
if (dr.RowState == DataRowState.Modified)
{
modified ++;
}
if (dr.RowState == DataRowState.Added)
{
added++;
}
}
conn.Open();
log.Info("Calling Update to DB...");
int rowseffected = da.Update(pca.chatDataSetG, "Chat");
log.Info("Update Complete - " + rowseffected + " rows effected........");
}
catch (Exception ex)
{
log.Error("Error Updating Db with chat Data", ex);
}
finally
{
conn.Close();
}
}
The counts are right before the fill, but after - the added count remains the same - good, but the modified count goes to 0 - bad :( . I have tried all shapes to try and see what is going on here - but honestly - am stumped. Any help would be very, very, very much appreciated. The rows in the Db are not reflecting new values.
Peter
You do re-fill your DataSet/Tables at this line of code
//This is where the modified count reverts
da.Fill(pca.chatDataSetG, "Chat");
Within MSDN DataAdapter fill I think there is an explanation for the behaviour described
You can use the Fill method multiple times on the same DataTable. If a
primary key exists, incoming rows are merged with matching rows that
already exist. If no primary key exists, incoming rows are appended to
the DataTable.
IMO modified rows got merged with the origianl owns from the db and therefore loose its rowState, wheras inserted items got appended as described.
Related
So, I have a database with 7 tables. One of these tables, lets call it tablesOfInterest, simply contains rows of other tables in the database. This table changes regularly.
What I'm trying to do is:
Retrieve the table names from tablesOfInterest.
Display metadata about these tables. Specifically, all column names and the number of rows.
I read here Microsoft about using string[] index to display metadata about specific tables, but when I run my current code I get the error:
More restrictions were provided than the requested schema [tables]
supports.
Where am I going wrong?
Method One:
public static List<string> GetTables() {
SqlConnection sqlConnection = null;
SqlCommand cmd = null;
string sqlString;
List<string> result = new List<string>();
try
{
sqlConnection = DBConnection.GetSqlConnection();
sqlString = "select * from TablesOfInterest";
cmd = new SqlCommand(sqlString, sqlConnection);
SqlDataReader reader = cmd.ExecuteReader();
while (reader.Read())
{
result.Add(reader.GetString(0));
}
}
catch (SqlException e)
{
MessageBox.Show("SQL Error! \n" + e);
}
catch (Exception e)
{
MessageBox.Show("General Error! \n" + e);
}
finally
{
sqlConnection.Close();
}
return result;
}
Method two:
public static List<string> GetMetaDataTables()
{
SqlConnection con = null;
List<string> result = new List<string>();
String[] restrictions = new String[5];
try
{
con = DBConnection.GetSqlConnection();
foreach (string s in GetMetaData())
{
restrictions[2] = s;
}
DataTable schemaTable = con.GetSchema("Tables", restrictions);
foreach (DataRow row in schemaTable.Rows)
{
result.Add(row[2].ToString());
}
}
catch (SqlException e)
{
MessageBox.Show("Sql Error! \n " + e);
}
catch (Exception e)
{
MessageBox.Show("General Error! \n " + e);
}
finally
{
con.Close();
}
return result;
}
Update:
I tried Mikes suggestions and that certainly helped! It now displays the names of the tables correctly. It does not however display the amount of rows. To try and achieve that I did this:
dataGridView2.DataSource = Controller.GetMetaDataTables().Select(x => new {
Value = x.Value, Name = x.Key }).ToList();
In my gridView I only see the table names though, how can I select the no of rows aswell?
The specific error you're getting:
More restrictions were provided than the requested schema [tables] supports.
is because the Tables collection requires 3 restrictions: database, owner and table.
However, the next problem you have is even if restrictions was defined as:
var restrictions = new string[3];
this code would only retrieve the schema for the last table in TablesOfInterest:
foreach (string s in GetMetaData())
{
restrictions[2] = s;
}
DataTable schemaTable = con.GetSchema("Tables", restrictions);
foreach (DataRow row in schemaTable.Rows)
{
result.Add(row[2].ToString());
}
That's because the call to GetSchema belongs inside the iteration of the first for loop like this:
foreach (string s in GetMetaData())
{
restrictions[2] = s;
DataTable schemaTable = con.GetSchema("Tables", restrictions);
foreach (DataRow row in schemaTable.Rows)
{
result.Add(row[2].ToString());
}
}
The next problem you have is that result really needs to be a dictionary:
var result = new Dictionary<string, List<string>>();
because you're recovering column information for multiple tables. That would change that iteration to be something like this:
foreach (string s in GetMetaData())
{
restrictions[2] = s;
DataTable schemaTable = con.GetSchema("Tables", restrictions);
result.Add(s, new List<string>());
foreach (DataRow row in schemaTable.Rows)
{
result[s].Add(row[2].ToString());
}
}
I'm trying to eliminate or put empty an value in my DataGridview if the cell is duplicated. I saw an example but it used GridView in my case is DataGridview. Here is that code:
public void atualiza()
{
try
{
SqlConnection con = new SqlConnection(Login.conectData);
con.Open();
dsFerramenta = new DataSet();
daFerramenta = new SqlDataAdapter("SELECT Ferramentas.Ferramenta_ID, Ferramentas_Terminais.Terminal_ID, Ferramentas_Grupos.Grupo_ID, Vedante_ID, Imagem, Nome_Afinação, Vedante, Observações, Data_Criação, Utilizador FROM Ferramentas "+ "JOIN Ferramentas_Terminais ON Ferramentas_Terminais.Ferramenta_ID = Ferramentas.ferramenta_ID " +
"LEFT JOIN Ferramentas_Grupos ON Ferramentas_Grupos.Ferramenta_ID = Ferramentas.Ferramenta_ID " +
"LEFT JOIN Ferramentas_Vedantes ON Ferramentas_Vedantes.Ferramenta_ID = Ferramentas.Ferramenta_ID " +
"LEFT JOIN FormasCravação ON FormasCravação.Cravação_ID = Ferramentas.Cravação_ID " +
"LEFT JOIN TipoAfinação ON TipoAfinação.Afinação_ID = Ferramentas.Afinação_ID", con);
dsFerramenta.Clear();
daFerramenta.Fill(dsFerramenta, scrollVal, 100, "Ferramentas".Trim());
tabelaRelac.DataSource = dsFerramenta;
tabelaRelac.DataMember = "Ferramentas";
//para organizar as colunas na tabela
tabelaRelac.Columns["Terminal_ID"].DisplayIndex = 1;
tabelaRelac.Columns["Grupo_ID"].DisplayIndex = 2;
tabelaRelac.Columns["Vedante_ID"].DisplayIndex = 3;
tabelaRelac.Columns["Vedante"].DisplayIndex = 4;
tabelaRelac.Columns["Nome_Afinação"].DisplayIndex = 5;
tabelaRelac.Columns["Imagem"].DisplayIndex = 6;
tabelaRelac.Columns["Data_Criação"].DisplayIndex = 7;
tabelaRelac.Columns["Observações"].DisplayIndex = 8;
tabelaRelac.Columns["Utilizador"].DisplayIndex = 9;
con.Close();
string doubleValue = tabelaRelac.Rows[0].Cells[0].ToString().Trim();
for (int i = 0; i < tabelaRelac.Rows.Count; i++)
{
if (tabelaRelac.Rows[i].Cells[0].ToString() == doubleValue)
{
string a = tabelaRelac.Rows[i].Cells[0].ToString().Trim();
a = string.Empty;
}
else
{
doubleValue=tabelaRelac.Rows[i].Cells[0].Value.ToString().Trim();
}
}
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
}
tabelaRelac.ClearSelection();
}
After the con.close(), is my code to eliminate duplicate cell and put an empty value.
I would like some Help.
I use this Video reference to try
I'm using ToString() because DataGridView does't have proprety Text
Thank you.
It is better to handle dublicates in your SQL Query.
Also, Please keep in mind that It is very dangerous to use String Concatenation on SQL Query Generation wince it is open to SQL Injection attacks. Instead use Parameterized Queries.
If you insist on removing dublicates at DataTable level, then I suggest you to look at Ratty's Answer
Here is the code piece from Ratty's post
public DataTable RemoveDuplicateRows(DataTable dTable, string colName)
{
Hashtable hTable = new Hashtable();
ArrayList duplicateList = new ArrayList();
//Add list of all the unique item value to hashtable, which stores combination of key, value pair.
//And add duplicate item value in arraylist.
foreach (DataRow drow in dTable.Rows)
{
if (hTable.Contains(drow[colName]))
duplicateList.Add(drow);
else
hTable.Add(drow[colName], string.Empty);
}
//Removing a list of duplicate items from datatable.
foreach (DataRow dRow in duplicateList)
dTable.Rows.Remove(dRow);
//Datatable which contains unique records will be return as output.
return dTable;
}
I have a property that holds a strongly typed dataset row.
I've added an insert and update procedure to my dataset.
If I pass the adapter a row that already exists it successfully updates the information. However if I create a new row then it won't insert it to the database table. The exception is never getting thrown, and the int returned is always 0. No new row has been appended.
private MyDataset.MyDatasetGetDataRow _row;
private MyDataset.MyDatasetGetDataRow Rowa
{
get { return _row; }
set { _row = value; }
}
public void NewOne()
{
MyDatasetTableAdapters.MyDatasetGetDataTableAdapter adapter = new MyDatasetTableAdapters.MyDatasetGetDataTableAdapter();
MyDataset.MyDatasetGetDataDataTable tbl = adapter.GetData(0, 0);
MyDataset.MyDatasetGetDataRow row = tbl.NewMyDatasetGetDataRow();
Rowa = row;
}
public int Save()
{
try
{
int a = -99;
using (MyDatasetTableAdapters.MyDatasetGetDataTableAdapter adapter = new MyDatasetTableAdapters.MyDatasetGetDataTableAdapter())
{
a = adapter.Update(tbl);
}
return a;
}
catch (Exception ex)
{
throw new Exception("Error: It didn't save. (" + ex.Message + ")");
}
}
Row did not belong to a table and so the dataset did not know that the row had been newly added.
Not sure if this is written correctly but it looks correct. I am wanting to update a record if the id already exists and insert if not.
DataSet ds = new DataSet();
ds.ReadXml(XDocument.Load(Application.StartupPath + #"\xml1.xml").CreateReader());
using (var conn = new OleDbConnection("Provider=Microsoft.Jet.OLEDB.4.0; Data Source=" + Application.StartupPath + "\\Database3.mdb"))
{
conn.Open();
// make two commands here
var commInsert = new OleDbCommand("Insert INTO Table1 (description, active) VALUES (#iq_question,#active);", conn);
var commUpdate = new OleDbCommand("UPDATE Table1 SET description=#iq_question,active=#active WHERE ID=#question_id;", conn);
// here add your parameters with no value
//string question_id = row[0].ToString();
//string iq_question = row[1].ToString();
//string active = row[4].ToString();
commInsert.Parameters.Add(new OleDbParameter("#iq_question", OleDbType.VarChar));
commInsert.Parameters.Add(new OleDbParameter("#active", OleDbType.VarChar));
commUpdate.Parameters.Add(new OleDbParameter("#question_id", OleDbType.AutoNumber));
commUpdate.Parameters.Add(new OleDbParameter("#iq_question", OleDbType.Text));
commUpdate.Parameters.Add(new OleDbParameter("#active", OleDbType.Text));
foreach (DataTable table in ds.Tables)
{
foreach (DataRow row in table.Rows)
{
// here only reset the values
commUpdate.Parameters["#question_id"].Value = row[0].ToString();
commUpdate.Parameters["#iq_question"].Value = row[1].ToString();
commUpdate.Parameters["#active"].Value = row[4].ToString();
int recs = commUpdate.ExecuteNonQuery();
if (recs < 1) // when no records updated do insert
{
commInsert.Parameters["#iq_question"].Value = row[1].ToString();
commInsert.Parameters["#active"].Value = row[4].ToString();
commInsert.ExecuteNonQuery();
}
}
}
commInsert.Dispose();
commUpdate.Dispose();
conn.Close();
}
System.Windows.Forms.MessageBox.Show("Updated Latest Data Was Succesfull");
I either get an error on the insert saying it will create duplicate content, or it creates more rows with different data. So say I should be getting 10 rows from the xml file, the first time I run it I get the 10 rows with the correct data. If I run it again, I end up with 10 more so being 20 but the last 10 rows show different data. I don't think I am identifying the rows in the xml file correctly and I need to do some research on that part.
There is no Exists for MS Access. The engine is much more primitive than Sql Server. See here: Microsoft Access SQL. I think, what you can do is:
myCommand.CommandText = "UPDATE Table1 SET description=#iq_question,active=#active WHERE ID=#currentRow";
......
int recs = myCommand.ExecuteNonQuery();
if (recs < 1) // when no records updated do insert
{
myCommand.Parameters.Clear();
myCommand.CommandText = "Insert INTO Table1 VALUES(#iq_question,#active)";
.....
}
This is still 2 statements but you can save some coding by not doing Select first. Because ExecuteNonQuery will tell you if you updated anything
Another thing is that your code is a bit inefficient. You have nested loop where you can reuse same command and connection. Yuu can do this
using (var conn = new OleDbConnection(.......))
{
conn.Open();
// make two commands here
var commInsert = new OleDbCommand(.....);
var commUpdate = new OleDbCommand(.....);
// here add your parameters with no value
commInsert.Parameters.Add(new OleDbParameter(....));
.......
Foreach (....)
{
Foreach (....)
{
// here only reset the values
commUpdate.Parameters[0].Value = ...
...
int recs = commUpdate.ExecuteNonQuery();
if (recs < 1) // when no records updated do insert
{
commInsert.Parameters[0].Value = iq_question;
.....
}
}
}
commInsert.Dispose();
commUpdate.Dispose();
}
You can also use nested using for commands. Only setting values will be more efficient to what you do right now.
I've created a program to mine and import data from about 75 spreadsheets into an oracle table. I'm able to connect, iterate through sheets, and grab cells and rows seemingly fine. The problem is if the excel sheet was saved with grouped rows collapsed, it skips the rows.
I can't find anywhere if there is an extended property or reg setting to allow me to possibly expand the groups on entry? Not sure how to getting around a collapsed group (not merged cell , those I can process without a problem).
code bits :
//Starting where I iterate through a particular sheet
var connectionString = string.Format("Provider=Microsoft.ACE.OLEDB.12.0; data source={0}; Extended Properties=\"Excel 12.0;HDR=NO;IMEX=1;ReadOnly=0\"", fileName);
OleDbConnection objConn = new OleDbConnection(connectionString);
try
{
objConn.Open();
System.Data.DataTable dt = objConn.GetOleDbSchemaTable(OleDbSchemaGuid.Tables, new object[] { null, null, null, "TABLE" });
if (dt != null)
{
foreach (DataRow row in dt.Rows)
{
var adapter = new OleDbDataAdapter("SELECT F1,F2,F3,F4,F5,F6,F7,F8,F9,F10,F11,F12 FROM [" + row["TABLE_NAME"].ToString() + "]", connectionString);
var ds = new DataSet();
try
{
adapter.Fill(ds, "anyname");
}
catch
{
break;
}
DataTable data = ds.Tables[0];
int rownum = 0;
// <a bunch of variable declarations>
foreach (DataRow row_b in data.Rows)
{
// start slogging through the rows
rownum = rownum++;
// <reset some variables>
if (rownum == 1) // Catch valid scripts that contain a number
{
foreach (DataColumn column in data.Columns)
{
if (column.ToString() == "F1")
{
// <processing code for this column>
}
if (column.ToString() == "F2")
{
// <processing code for this column>
}
if (column.ToString() == "F3")
{
// <you get the picture>
}
}
}
if (rownum == 3)
{
// <moving along through the rows...different processing>
}
// <..rows 4-11..>
if (rownum > 12 )
{
// <more value assignment>
}
string allvals = APPLICATION + E_USER + STEP_DESC + VARIATIONS + STATUS + STOPS_TESTING + ISSUE_NUM + ISSUE_COMMENTS + ADDITIONAL_INFO;
allvals = allvals.Trim();
//Don't want sheets that come across as Print Area this shouldn't affect the row processing
isPrintArea = 0;
if (BOOKSHEET.Contains("Print_Area"))
{
isPrintArea = 1;
}
Boolean addornot=false;
if (cb_forallscripts.Checked == true)
{
addornot = (STEP_NUM != 0 &&
allvals != "" &&
isPrintArea == 0 &&
SCRIPT_NUM != 0);
}
else
{
addornot = (STEP_NUM != 0 &&
allvals != "" &&
isPrintArea == 0 &&
SCRIPT_NUM != 0 &&
runScripts.Contains(SCRIPT_NUM.ToString()));
}
if (addornot)
{
//<connect to our Oracle db, I set up oCmd outside this>
OracleCommand oCmd = new OracleCommand();
oCmd.Connection = oConn;
oCmd.CommandType = CommandType.Text;
oCmd.Parameters.Add("STEP_NUM", STEP_NUM);
// <... bunch of parameters ...>
oCmd.Parameters.Add("script", SCRIPT);
oCmd.CommandText = "<My insert statement> ";
oCmd.ExecuteNonQuery();
}
}
}
}
}
catch ( <error processing>)
{ }
Rows.OutlineLevel should be the VBA property you are looking for. It can be read or set. See this page for Microsoft's rather terse description. Range.ClearOutline will, as it says, clear the outline for a specified range, as explained here.