3

I dynamically create Textboxes according to user input like so :

int nbrTextBoxBE = int.Parse(textBoxNbrBE.Text);
panelBE.Controls.Clear();
panelBE.Focus();
for (int i = 0; i < nbrTextBoxBE; i++)
{
    TextBox textBoxArticleCodeBE = new TextBox();
    TextBox textBoxDesignationBE = new TextBox();
    textBoxCArticleCodeBE.Name = "ArticleCodeBE" + (i + 1);
    textBoxDesignationBE.Name = "DesignationBE" + (i + 1);
    textBoxArticleCodeBE.Text = "";
    textBoxDesignationBE.Text = "";
    panelBE.Controls.Add(textBoxArticleCodeBE);
    panelBE.Controls.Add(textBoxDesignationBE);
    panelBE.Show();
}

And with a button I'd like to save these to my database in this form :

INSERT INTO myTable (ArticleCode, Designation) VALUES (ArticleCodeBEi.Text, DesignationBEi.Text)

I tried to do so with a foreach loop :

foreach (TextBox tb in panelBE.Controls.OfType<TextBox>())
{
    // do stuff 
}

but of course it generates double the number of queries I need.

A. Petit
  • 158
  • 3
  • 14

3 Answers3

4

The most correct way to do your task is using a parameter collection

// This is assumed to be even
TextBox[] txtArray = panelBE.Controls.OfType<TextBox>().ToArray();

for(int x = 0; x < txtArray.Length; x+=2)
{
    TextBox tArticle = txtArray[x];
    TextBox tDesignation = txtArray[x+1];

    // Where you build the query text
    StringBuilder sb = new StringBuilder();

    // The placeholders for the parameters to be used
    List<string> prmNames = new List<string>();

    // The parameter collection 
    List<SqlParameter> prms = new List<SqlParameter>();

    // Initial text for the query
    sb.Append("INSERT INTO myTable (ArticleCode, Designation) VALUES (");

    prmNames.Add("@p" + tArticle.Name);
    prms.Add(new SqlParameter() 
    {
        ParameterName = "@p" + tArticle.Name,
        SqlDbType = SqlDbType.NVarChar,
        Value = tArticle.Text
    });
    prmNames.Add("@p" + tDesignation.Name);
    prms.Add(new SqlParameter()
    {
         ParameterName = "@p" + tDesignation.Name,
         SqlDbType = SqlDbType.NVarChar,
         Value = tDesignation.Text
    });

    // Concat the parameters placeholders and close the query text
    sb.Append(string.Join(",", prmNames) + ")");

    // Pass everything to an utility method
    // This could be part of a bigger utility database class
    ExecuteInsertInto(sb.ToString(), prms);
}

Where ExecuteInsertInto is

private int ExecuteInsertInto(string query, List<SqlParameter> prms = null)
{
   using(SqlConnection cnn = new SqlConnection(..connectionstring goes here..))
   using(SqlCommand cmd = new SqlCommand(query))
   {
        cnn.Open();
        if(prms != null)
            cmd.Parameters.AddRange(prms.ToArray());
        return cmd.ExecuteNonQuery();
   }
}

Notice that this assumes that the controls are retrieved in the same order in which the fields are written in the INSERT INTO string. If this is not the case, then you need to take the control names, substring the control name to take only the text before the "BE" text and add that string to the StringBuilder instance.

  string fieldName = tb.Text.Substring(0, tb.Text.IndexOf("BE"));

There is also a better way to pass every command with a single call to the ExecuteInsertInto but the exact syntax depends on the database type. In short you could build a single query text with many inserts. For Sql Server see Multiple Insert Statements vs Single Inserts with multiple values

Steve
  • 213,761
  • 22
  • 232
  • 286
  • kudos for parameterised queries. you're writing half a DAL here just to explain the concept. – Timothy Groote Feb 22 '18 at 08:48
  • Initially I thought that your answer is correct, but then I noticed that it will generate an SQL that looks like this: `INSERT INTO myTable (ArticleCode, Designation) VALUES (@ArticleCodeBE1, @DesignationBE1, @ArticleCodeBE2, @DesignationBE2, @ArticleCodeBEn, @DesignationBEn)`... You need a slightly different code to create `VALUES(@p1, p2),(@p3, @p4)...` – Zohar Peled Feb 22 '18 at 08:56
  • @Steve I tried to run your code but it returns errors on the use of Append() and Add() methods. `Error 5 No overload for method 'Add' takes 2 arguments` and `Error 2 'System.Collections.Generic.List' does not contain a definition for 'Append' and no extension method 'Append' accepting a first argument of type 'System.Collections.Generic.List' could be found (are you missing a using directive or an assembly reference?)` – A. Petit Feb 22 '18 at 09:19
  • I think `params SqlParameter[] params` would be way nicer than `List prms = null` for both caller and callee. – Aluan Haddad Feb 22 '18 at 09:23
  • 2
    @A.Petit Let me rewrite the whole thing in Visual Studio. You never appreciate Intellisense enough if you don't write this code by hand – Steve Feb 22 '18 at 09:28
  • @Steve It works, I now have the right number of queries, they're like `INSERT INTO myTable (ArticleCode, Designation) VALUES (@ArticleCodeBE1,@DesignationBE1)`. I changed `prmNames.Add("@" + tArticle.Name);` to `prmNames.Add("@" + tArticle.Text);` to get the text instead. – A. Petit Feb 22 '18 at 09:52
  • @Steve I just need to find how to remove the `@` and add `'` around the fields. – A. Petit Feb 22 '18 at 09:56
  • Not sure to follow you. The placeholders should not be enclosed in quotes and in Sql Server they start with the @ char. Then in the parameter collection you refer to them with the @placeholder syntax while the Value (the Text of your textboxes) should be added to the Value property. Then you pass everything to the database engine that now has enough information to handle the values for each placeholder. – Steve Feb 22 '18 at 10:00
  • OK, but that approach is very wrong. Look at [Sql Injection Explained](http://stackoverflow.com/questions/332365/how-does-the-sql-injection-from-the-bobby-tables-xkcd-comic-work) – Steve Feb 22 '18 at 10:07
  • @Steve Yes, I reverted the changes, now I have a query like `INSERT INTO myTable (ArticleCode,Designation) VALUES (@CodeArticleBE1,@DesignationBE1)` but I have an exception thrown when executing the query : "`Must declare the scalar variable "@CodeArticleBE1".`" – A. Petit Feb 22 '18 at 10:19
  • Hum, there was an error in the code, I have fixed it sometime ago. Did you refresh the page, look at the point where code adds the parameter for tArticle – Steve Feb 22 '18 at 10:46
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/165645/discussion-between-a-petit-and-steve). – A. Petit Feb 22 '18 at 12:44
0

while not the cleanest solution in this case, you could easily do this with a normal for loop ;

var textboxes = panelBE.Controls.OfType<TextBox>();
for(int i=0; i < textboxes.length; i+=2)
{
   var articleCodeTextbox = textboxes[i];
   var designationCodeTextbox = textboxes[i+1];
   //build your query
}

the danger of this of course is that there might be an uneven number of textboxes in the panelBE control, leading you to an IndexOutOfBounds exception. Another danger is that there is no direct guarantee that your two textboxes in the loop are indeed related to each other.

If you want to be certain that your textboxes all match, and reduce the risk of an IndexOutOfBounds exception, you could do something like this :

Dictionary<TextBox, TextBox> Textboxes = new Dictionary<TextBox, TextBox>();

int nbrTextBoxBE = int.Parse(textBoxNbrBE.Text);
panelBE.Controls.Clear();
panelBE.Focus();
for (int i = 0; i < nbrTextBoxBE; i++)
{
    TextBox textBoxArticleCodeBE = new TextBox();
    TextBox textBoxDesignationBE = new TextBox();
    textBoxCArticleCodeBE.Name = "ArticleCodeBE" + (i + 1);
    textBoxDesignationBE.Name = "DesignationBE" + (i + 1);
    textBoxArticleCodeBE.Text = "";
    textBoxDesignationBE.Text = "";
    panelBE.Controls.Add(textBoxArticleCodeBE);
    panelBE.Controls.Add(textBoxDesignationBE);
    Textboxes.Add(textBoxArticleCodeBE, textBoxDesignationBE);
    panelBE.Show();
}

You could then simply loop over all textboxes that are an ArticleCodeBE textbox, and you could pull the accompanying DesignationCodeBE textbox from the dictionary like this :

foreach (TextBox articleCodeTB in panelBE.Controls.OfType<TextBox>().Where(tb => tb.Name.Contains("ArticleCodeBE")))
{
     var designationCodeTB = TextBoxes[articleCodeTB];
     //build your query  
}

An even better solution in this case would be to do something in the direction of a proper viewmodel with a dedicated control. you wouldn't have to worry about matching up textboxes anymore.

Timothy Groote
  • 8,614
  • 26
  • 52
  • 1
    The increment needs to be `i+=2`, not `i++`. This also hinges on the `textboxes` list being in the exact same order as the textboxes were added. Which is likely the case, but cannot be _guaranteed_ to be the same. – Flater Feb 22 '18 at 08:37
  • @Flater regarding the edit about the order of the textboxes ; i was already addressing that :) – Timothy Groote Feb 22 '18 at 08:52
0

How about that:

foreach (TextBox tb in this.Controls.OfType<TextBox>().Where(textbox => textbox.Name.StartsWith("DesignationBE")))
{
    //do stuff
}
Kiwimanshare
  • 130
  • 9