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

2

0

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.

Novice answered 23/6, 2016 at 8:32 Comment(0)
N
2

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;
}
Niphablepsia answered 25/6, 2016 at 18:55 Comment(7)
Can we do this without without forloop as I am having thousands of categories and each categories with lots of child categories that is why I came up with expression so as to avoid forloop.with doing this in loop it will be incredibly slow in case of thousands of categories and subcategories. What do you think??Novice
As I wrote in the answer, "You have no other choice". Also loops are no problem, the only time critical operation is finding the corresponding parent. Which your computed column was doing internally anyway (when in you accces Parent in the expression).Niphablepsia
I too have search alot regarding this but didn't able to find any solution.So this expression and loop will be same in terms of performance??Novice
I believe so. Again, outer loop is not an issue (data adapter is also looping over the table records when you call Update). Inner loop would be an issue, for instance if you try to locate the category record using linear search. Fortunately that's not the case when you use data relation, because it creates a fast lookup memory structure. Another (and probably faster) way would be to use dictionary instead of data relation.Niphablepsia
Awesome explanation.how I will use this dictionary instead of data relation in this scenario for example??Novice
Let us continue this discussion in chat.Novice
Can you check this question and see if you can give me any suggestions:#38158325Novice
M
1

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.

Mycosis answered 1/7, 2016 at 2:8 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.