"Table does not contain primary key" - c#

I am trying to populate a combo box based off of the value member that has been selected from a data grid view.
Here is my code to initialise it
DataSet dsCIF2 = new DataSet();
DataRow drPitch;
String sqlPitch = #" Select * from Pitch";
String connStr5 = Properties.Resources.cString;
SqlDataAdapter daPitch = new SqlDataAdapter(sqlPitch, connStr5);
DataTable dtPitch = new DataTable();
daPitch.Fill(dtPitch);
daPitch.Fill(dsCIF2, "Pitch");
comboBox2.DisplayMember = "PitchDesc";
comboBox2.ValueMember = "PitchID";
comboBox2.DataSource = dtPitch;
With the following code I use this to find the ID of the pitch from the selected row on the data grid view and it returns the correct pitch ID, as seen through debugging.
int matchBookingID = 0;
matchBookingID = Convert.ToInt32(DGV.SelectedRows[0].Cells[0].Value);
drMatchData = dsCIF.Tables["MatchStats"].Rows.Find(matchBookingID);
Pitch = Convert.ToInt32(drMatchData["PitchID"].ToString());
Now when I try to use that ID to find the datarow within the pitch table I get an error saying
Table doesn't have a primary key
on this line of code
drPitch = dsCIF2.Tables["Pitch"].Rows.Find(Pitch);
I don't know why I am getting this error, thanks in advance!
Update: The table does have a primary key
SQL CODE
create TABLE PITCH
(
PitchID int NOT NULL,
PitchDesc varchar(30) NOT NULL,
CONSTRAINT pkPitchID PRIMARY KEY(PitchID),
)

Turns out I forgot to fill the schema of the table. It works now that I have added the following line of code to the initialisation code
daPitch.FillSchema(dsCIF2, SchemaType.Source, "Pitch");
It now looks like this
DataSet dsCIF2 = new DataSet();
String sqlPitch = #" Select * from Pitch";
String connStr5 = Properties.Resources.cString;
SqlDataAdapter daPitch = new SqlDataAdapter(sqlPitch, connStr5);
SqlCommandBuilder cmdBPitch = new SqlCommandBuilder(daPitch);
daPitch.FillSchema(dsCIF2, SchemaType.Source, "Pitch");
DataTable dtPitch = new DataTable();
daPitch.Fill(dtPitch);
daPitch.Fill(dsCIF2, "Pitch");

Related

How to insert child category by specific parent category into table using ado.net?

I am trying to insert categories and subcategories from Excel into a database table.
I have 1 Excel file which contains some data and from this Excel file I am creating dataset which contains lots of datatables.
In this dataset I have 2 datatables in the form of this:
Datatable 0 with records:Category
ParentCategory Description
Electronics jhdkhsd
Sports kjshfhs
Datatable 1 with records:SubCategory
Subcategory ParentCategory Description
Mobile Electronics weprwp
Tv Electronics sdflskd
Balls Sports kjshdfkjh
Shoes Sports uytuyt
Now my database tables are like this:
Category:Id,Name,Description,parentid
So far I am successful inserting parent category but now trying to insert child categories but that is where currently i am struggling.
This my code so far:
var dsFinal = new DataSet();
//Some code to read excel sheets and data from excel and create datatables and records with it.
dsControlSheet.Tables[0].Columns.Add("Id");
DataColumn parentId = new DataColumn("ParentId", typeof(int));
parentId.DefaultValue = 0;
dsFinal.Tables[0].Columns.Add(parentId);
dsFinal.Relations.Add("Abc",dsFinal.Tables[0].Columns["ParentCategory"],
dsFinal.Tables[1].Columns["ParentCategory"],false); //creating relation ship between Category datatable
// and SubCategory datatable on field ParentCategory
using (SqlConnection connection = new SqlConnection(""))
{
SqlDataAdapter adapter = new SqlDataAdapter();
var insertCommand = new SqlCommand("insert into Category (Name,Description) values (#ParentCategory,#Description) SET #Id = SCOPE_IDENTITY()", connection);
var parameter = insertCommand.Parameters.Add("#Id", SqlDbType.Int, 0, "Id");
insertCommand.Parameters.Add("#ParentCategory", SqlDbType.NVarChar, 50, "ParentCategory");
insertCommand.Parameters.Add("#Description", SqlDbType.NVarChar, 50, "Description");
parameter.Direction = ParameterDirection.Output;
insertCommand.UpdatedRowSource = UpdateRowSource.OutputParameters;
adapter.InsertCommand = insertCommand;
adapter.Update(dsFinal.Tables[0]); //successfully inserted parent category and got autoincremented value in Id column of my 0th datatable
//trying to insert child category using above insert command
foreach (DataRow parentCategory in dsFinal.Tables[0].Rows)
{
var child = parentCategory.GetChildRows("Abc").CopyToDataTable();//get child category of particular parent
adapter.Update(child);
}
}
Here in the last loop to insert child category; I am confused about how to use
same insertCommand variable to insert child category?
Update:I have used datatable Expression to calculate parentid like this:
using (SqlConnection connection = new SqlConnection(""))
{
SqlDataAdapter adapter = new SqlDataAdapter();
var insertCommand = new SqlCommand("insert into Category (Name,Description) values (#ParentCategory,#Description) SET #Id = SCOPE_IDENTITY()", connection);
var parameter = insertCommand.Parameters.Add("#Id", SqlDbType.Int, 0, "Id");
insertCommand.Parameters.Add("#ParentCategory", SqlDbType.NVarChar, 50, "ParentCategory");
insertCommand.Parameters.Add("#Description", SqlDbType.NVarChar, 50, "Description");
parameter.Direction = ParameterDirection.Output;
insertCommand.UpdatedRowSource = UpdateRowSource.OutputParameters;
adapter.InsertCommand = insertCommand;
adapter.Update(dsFinal.Tables[0]); //successfully inserted parent category and got autoincremented value in Id column of my 0th datatable
//For inserting child category..
//added column parentid to store child category
SqlDataAdapter da = new SqlDataAdapter();
dsFinal.Tables[1].Columns.Add("ParentId", typeof(int), "IIF(Parent.ParentCategory=ParentCategory,parent.Id,0)");
var insertChildCategoryCommand = new SqlCommand("insert into Category (Name,Description,ParentId) values (#Subcategory,#Description,#ParentId) SET #Id = SCOPE_IDENTITY()", connection);
var parameter1 = insertChildCategoryCommand.Parameters.Add("#Id", SqlDbType.Int, 0, "Id");
insertChildCategoryCommand.Parameters.Add("#Subcategory", SqlDbType.NVarChar, 50, "Subcategory");
insertChildCategoryCommand.Parameters.Add("#Description", SqlDbType.NVarChar, 50, "Description");
insertChildCategoryCommand.Parameters.Add("#ParentId", SqlDbType.int, 0, "ParentId");
parameter1.Direction = ParameterDirection.Output;
insertChildCategoryCommand.UpdatedRowSource = UpdateRowSource.OutputParameters;
da.InsertCommand = insertChildCategoryCommand;
//Error here that computed column cannot be inserted.Here computed column is parentid
da.Update(dsFinal.Tables[1]);
}
Error:Computed column(parentid) cannot be inserted.
You are almost there with latest code.
The only problem is that the calculated columns are not allowed to be used for inserting/updating the database table. Btw, the error message is not "Computed column(parentid) cannot be inserted.", but:
The column mapping from SourceColumn 'ParentId' failed because the DataColumn 'ParentId' is a computed column.
I could agree that the message could have been better, and also I didn't find any documentation describing that. Most probably the rationale is that the computed columns are not normally stored.
Whatever the reason is, that's the reality. You have no other choice than creating a regular column and populating it with data manually.
There are many ways to do that (both efficient and ineffient), but once you already created a relation, you can use the DataRow.GetParentRow method to locate the related category record.
With all that being said, replace the line
dsFinal.Tables[1].Columns.Add("ParentId", typeof(int),
"IIF(Parent.ParentCategory=ParentCategory,parent.Id,0)");
with the following snippet:
var dtSubCategory = dsFinal.Tables[1];
dtSubCategory.Columns.Add("ParentId", typeof(int));
foreach (var drSubCategory in dtSubCategory.AsEnumerable())
{
var drCategory = drSubCategory.GetParentRow("Abc");
drSubCategory["ParentId"] = drCategory != null ? drCategory["Id"] : 0;
}
and you are done.
EDIT: Let me make it clear. The only time critical operation here is locating the category id by name. Using the relation and GetParentRow is equivalent of the evaluating the expression accessing parent as in your attempt. Data relation internally maintains a lookup structure for supporting such operations.
If you want to get the best possible performance, then don't create a relation, but a dictionary. What you need is given a name (string), find the corresponding id (int), so Dictionary<string, int> is a perfect candidate for that:
var categoryIds = dsFinal.Tables[0].AsEnumerable().ToDictionary(
dr => dr.Field<string>("ParentCategory"), // key
dr => dr.Field<int>("Id") // value
);
var dtSubCategory = dsFinal.Tables[1];
dtSubCategory.Columns.Add("ParentId", typeof(int));
foreach (var drSubCategory in dtSubCategory.AsEnumerable())
{
int categoryId;
categoryIds.TryGetValue(drSubCategory.Field<string>("ParentCategory"), out categoryId);
drSubCategory["ParentId"] = categoryId;
}
Change your relation so it's using Id and ParentId and your first solution will be working just right.
When you will update that first table and the get the autoincremented value, it will automatically update the second table via the relation. Here is an example.
DataSet ds = new DataSet();
DataTable dt = new DataTable();
var colParent = dt.Columns.Add("id", typeof(int));
DataTable dtChild = new DataTable();
var colChild = dtChild.Columns.Add("parentid", typeof(int));
ds.Tables.Add(dt);
ds.Tables.Add(dtChild);
ds.Relations.Add("relation1", colParent, colChild);
DataRow rowParent = dt.NewRow();
rowParent["id"] = 1;
dt.Rows.Add(rowParent);
DataRow rowChild = dtChild.NewRow();
rowChild["parentid"] = rowParent["id"];
dtChild.Rows.Add(rowChild);
// at this point, parentid from rowChild is "1".
rowParent["id"] = 10; // DataAdapter.Update simulation.
// and now parentid from rowChild has been update via the relation to "10".
When using this method, i'll usually set my column to get a temporary "id" value by setting the column as an identity column with a seed and step set to -1. This would go after the third line.
colParent.AutoIncrement = true;
colParent.AutoIncrementSeed = -1;
colParent.AutoIncrementStep = -1;
Unless you really need to have your relation on those string fields, there's no need to do any loop, computed column or anything overly complicated.

Adding data in multiple tables in one shot with a foreign key constraints between them

I have a problem where I want to have insert operations on different tables in one transaction. So that if transaction fails, none of table get polluted with in-consistent data.
There is another constraint in the problem. Table-2 has a foreign key relationship with primary key (auto_increment) of Table-1. So when I add new entry in Table-1 and Table-2, I need to add new primary key of Table-1 in new Table-2 entry which is the part of same transaction.
I am using MYSQLDataAdaptor for populating the SB.
Problem statement is bit tricky, please let me know, if we need more clarification on the same.
Thanks in advance !!
I have resolved this problem using transaction concept.
MySqlDataAdapter [] all_adapters = new MySqlDataAdapter[2];
DataTable [] data_tables = new DataTable[2];
for (int i = 0; i < 2; i++ )
{
all_adapters[i] = new MySqlDataAdapter(query[i], connection);
MySqlCommandBuilder cb = new MySqlCommandBuilder(all_adapters[i]);
all_adapters[i].InsertCommand = cb.GetInsertCommand();
data_tables[i] = new DataTable();
all_adapters[i].Fill(data_tables[i]);
}
MySqlTransaction transaction = connection.BeginTransaction();
try
{
for (int i = 0; i < 2; i++ )
{
all_adapters[i].InsertCommand.Transaction = transaction;
if (i == 0)
{
... Add entry in table-1
// Fetch new auto_increment primary key of table-1
string new_query = "SELECT LAST_INSERT_ID();";
MySqlCommand cmd = new MySqlCommand(new_query, connection);
int primary_id_table1 = Convert.ToInt32(cmd.ExecuteScalar());
}
else
{
... Add entry in table-2.
Use primary_id_table1 to populate the foreign key in table-2
}
}
transaction.Commit();
}
catch (Exception e)
{
transaction.Rollback();
}
This will make sure that either both the table are updated in the data base simultaneously or none of the tables get updated, as the transaction effect is rolled back. This will help in maintaining the consistency of the database. Hence, maintaining the ACID property among the given transaction.
I think it is possible only if you keep track of Table-1 primary key and generate your inserts with pre-calculated primary key. So you must be sure, no one can't insert same time any data into same table.
I scratched my head over this one for quite a while until I finally realized the issue. I was attempting to update two primary tables with autoincrementing ID fields and then update a third table with the values of the IDs from the records from the first two tables, all within a transaction. I found that the AutoIncrement Primary Key fields in a dataset won't be the actual value of the records written after an update (their fields will have values, but not necessarily the correct ones). It will just be the next in the sequence from the seed value, when the table schema is read. So to handle this, add a RowUpdated Handler to your DataAdapter and read the command.LastInsertedId property. That way the AutoIncrement values are the actual values written in the table. Then adding the new records to the third table works.
{
MySqlDataAdapter daAll = new MySqlDataAdapter("Select * from courses;Select * from productsku;Select * from productcourseassociation;", sqlConn);
MySqlDataAdapter daCourses = new MySqlDataAdapter("Select * from courses;", sqlConn);
MySqlDataAdapter daSku = new MySqlDataAdapter("Select * from productsku;", sqlConn);
MySqlDataAdapter daAssoc = new MySqlDataAdapter("Select * from productcourseassociation;", sqlConn);
daCourses.RowUpdated += DaCourses_RowUpdated;
daSku.RowUpdated += DaSku_RowUpdated;
DataSet ds = new DataSet();
daAll.FillSchema(ds, SchemaType.Source);
daCourses.TableMappings.Add("courses", "Table");
daSku.TableMappings.Add("productsku", "Table1");
daAssoc.TableMappings.Add("productcourseassociation", "Table2");
ForeignKeyConstraint fkConstraint1 = new ForeignKeyConstraint("fk1", ds.Tables[0].Columns["courseid"], ds.Tables[2].Columns["courseid"]);
ForeignKeyConstraint fkConstraint2 = new ForeignKeyConstraint("fk2", ds.Tables[1].Columns["skuid"], ds.Tables[2].Columns["skuid"]);
ds.Tables[2].Constraints.Add(fkConstraint1);
ds.Tables[2].Constraints.Add(fkConstraint2);
MySqlCommandBuilder cbCourses = new MySqlCommandBuilder(daCourses);
MySqlCommandBuilder cbSku = new MySqlCommandBuilder(daSku);
MySqlCommandBuilder cbAssoc = new MySqlCommandBuilder(daAssoc);
DataRow courseRow = ds.Tables[0].NewRow();
courseRow["coursename"] = courseName;
ds.Tables[0].Rows.Add(courseRow);
r = ds.Tables[1].NewRow();
r["skudesc"] = courseName;
r["duration"] = 1;
ds.Tables[1].Rows.Add(r);
r = ds.Tables[1].NewRow();
r["skudesc"] = courseName;
r["duration"] = 3;
ds.Tables[1].Rows.Add(r);
r = ds.Tables[1].NewRow();
r["skudesc"] = courseName;
r["duration"] = 12;
ds.Tables[1].Rows.Add(r);
daCourses.Update(ds, "Table");
daSku.Update(ds, "Table1");
daAssoc.Fill(ds, "Table2");
int courseId = Convert.ToInt32(courseRow["courseid"]);
DataRow[] skurows = ds.Tables[1].Select(String.Format("skudesc='{0}'", courseName));
foreach (DataRow dr in skurows)
{
r = ds.Tables[2].NewRow();
r["skuid"] = dr["skuid"];
r["courseid"] = courseId;
ds.Tables[2].Rows.Add(r);
}
daAssoc.Update(ds, "Table2");
}
private void DaSku_RowUpdated(object sender, MySqlRowUpdatedEventArgs e)
{
System.Diagnostics.Debug.WriteLine("Row updated");
e.Row["skuid"] = e.Command.LastInsertedId;
}
private void DaCourses_RowUpdated(object sender, MySqlRowUpdatedEventArgs e)
{
System.Diagnostics.Debug.WriteLine("Row updated");
e.Row["courseid"] = e.Command.LastInsertedId;
}

MaxLength of column seems to be always -1 for the string fields in the datatable C#

All my data table column fields from the stored procedure is string fields as like given below
[Account Code] varchar(10),
Filler1 varchar(5),
[Accounting Period] varchar(7),
[Transaction Date] varchar(8),
Filler2 varchar(2),
[Record Type] varchar(1),
[Source] varchar(2),
[Journal No] varchar(5),
[Journal Line] varchar(7)
Using the below code only I am picking the value from the SP to the dataset as given below
public DataSet InvoicesToSageExtractGoodsIn(string invoiceDateFrom)
{
SqlConnection conn = null;
DALFactory dalFactory = null;
SqlDataAdapter da = null;
try
{
DataSet dsInvoiceCustomer = new DataSet();
dalFactory = new DALFactory();
conn = new SqlConnection(dalFactory.ConnectionStringVI);
conn.Open();
SqlCommand sqlCommand = new SqlCommand("prc_ReturnSageExtractGoodsIn", conn);
sqlCommand.CommandType = CommandType.StoredProcedure;
DALSystemTable dst = new DALSystemTable(this.dao);
sqlCommand.CommandTimeout = Convert.ToInt32(dst.GetSystemConstant("CommandTimeOut"));
SqlParameter param1 = sqlCommand.Parameters.Add("#InvoiceDateFrom", SqlDbType.DateTime);//VI-165
param1.Direction = ParameterDirection.Input;
param1.Value = invoiceDateFrom;
DataSet dsInvoice = new DataSet("VisionInvoicing");
DataTable dtInvoiceNos = new DataTable("tblSageExtractGoodsIn");
da = new SqlDataAdapter(sqlCommand);
da.Fill(dsInvoice);
dsInvoice.Tables[0].TableName = "tblGenerateInvoice";
return dsInvoice;
}
catch (Exception ex)
{
throw new DatabaseException(ex.Message, ex);
}
}
When I try to pick the MaxLength of the column (dtSAGEExtract.Columns[i].MaxLength) from that dataset table it always shows -1 value.
foreach (DataRow drExtract in dtSAGEExtract.Rows)
{
int fieldCount = (dtSAGEExtract.Columns.Count);
for (int i = 0; i < fieldCount; i++)
{
int length = dtSAGEExtract.Columns[i].MaxLength;
writer.Write(drExtract[i].ToString().PadRight(length));
}
writer.WriteLine();
}
writer.Close();
Does anyone have any idea why it is always showing -1 for all the fields?
First you need to add your DataTable to the DataSet to make it work after you created the DataTable because they are not connected in your example and filling the DataSource has no impact on the DataTable created in the next line so that you can use the additional schema information:
dsInvoice.Tables.Add(dtInvoiceNos);
then you need to call the SqlDataAdapter.FillSchema Method on your SqlDataAdapter to get more information from your database then just the data when using only the Fill method
da.FillSchema(dtInvoiceNos, SchemaType.Source);
da.Fill(dsInvoice);
The fill operations must be called after you added the table to the dataset.
It does not represent the MaxLength of DataTable column. It is not depend on database schema either.
It return -1 because it is default value when you do not set anything.
http://msdn.microsoft.com/en-us/library/system.data.datacolumn.maxlength(v=vs.110).aspx
Because you not fill schema to dataset.
Normaly -1 is default length of column.
You should use:
da.FillSchema(dsInvoice, SchemaType.Mapped)
UPDATE:
DataTable dtInvoiceNos = dsInvoice.Tables[0];
Please check: http://msdn.microsoft.com/en-us/library/229sz0y5(v=vs.110).aspx

DataTable adding Columns to DataGrid instead of adding new rows?

I have a DataGridView that I'm trying to pull data to from an SQLite database.
The structure of the DataGridView is:
TaskName : String
TaskDueDate : String
Status : ComboBox
Code:
String SelectQuery = "Select TaskName,TaskDue,Status from TASKS Order BY Status";
SQLiteConnection slite = new SQLiteConnection("data source = Dash.sqlite");
slite.Open();
SQLiteDataAdapter data = new SQLiteDataAdapter(SelectQuery, slite);
SQLiteCommandBuilder Command = new SQLiteCommandBuilder(data);
DataTable table = new DataTable();
data.Fill(table);
MyTasksGrid.AutoGenerateColumns = false;
MyTasksGrid.DataSource = table;
I've spent the past few hours on here looking at several question including this one,
however it was producing the exact same results for me as the code above.
This question also suggests my code is correct from the accepted answer?
I get the correct amount of rows showing up in the grid, but all the cells are blank. If I remove the AutoGeneratColumns line it adds the data but in new columns, which I don't want. (I want it to be added in the already defined columns.)
Could anyone give me a hand to see where I am going wrong?
String SelectQuery = "Select ID,TaskName,TaskDue,Status from TASKS Order BY Status";
SQLiteConnection slite = new SQLiteConnection("data source = SupportDash.sqlite");
slite.Open();
SQLiteDataAdapter data = new SQLiteDataAdapter(SelectQuery, slite);
SQLiteCommandBuilder Command = new SQLiteCommandBuilder(data);
var Bind = new BindingSource();
DataTable table = new DataTable();
Bind.DataSource = table;
MyTasksGrid.AutoGenerateColumns = false;
MyTasksGrid.DataSource = table;
MyTasksGrid.DataSource = Bind;
MyTasksGrid.Refresh();
data.Fill(table);
I also needed to add the 'DataPropertyName' in the control properties.

DataTable + entering row at specific row in C#

I have a Data table and I want to enter a row at the beginning and end of the table how can I achieve this:
C# code:
Guid tempGuid1 = new Guid();
dt1.Rows[0]["ID"] = tempGuid1;
dt1.Rows[0]["Name"] = "Select a WishList";
Guid tempGuid = new Guid();
dt1.Rows.Add(tempGuid, "Create a new WishList");
Ok after I fill the table I want to enter a brand new row at top of the table
Guid tempGuid1 = new Guid();
dt1.Rows[0]["ID"] = tempGuid1;
dt1.Rows[0]["Name"] = "Select a WishList";
and than at the end of the table
Guid tempGuid = new Guid();
dt1.Rows.Add(tempGuid, "Create a new WishList");
I can enter at the end but the brand new row giving me problems, currently the code overwrites the top row which is row[0].
You can use the InsertAt method to insert a new row at the position you want.
Referencing row[0] means you are overwriting the data already there, as you have discovered.
DataRow newRow = dt1.NewRow();
newRow["ID"] = tempGuid1;
newRow["Name"] = "Select a WishList";
dt1.Rows.InsertAt(newRow, 0);
Consider the DataRowCollections "InsertAt" method. It accepts a data row and an integer representing the row index at which the row should be inserted. So you can use this code:
Guid tempGuid = new Guid();
DataRow firstRow = dt1.NewRow();
firstRow["ID"] = tempGuid;
firstRow["Name" = "Select a Wishlist";
dt1.InsertAt(firstRow, 0);

Categories