I'm trying to build a insert query that uses all the textboxes names (the keys so to speak in KeyValuePair<>) so I will not have to type out all the named parameters (ie the textboxes name) but instead just use all of the textboxes names from the list.
My code is as follows:
private void buttonInsert_Click(object sender, EventArgs e)
{
using (members.DBConnection = new System.Data.OleDb.OleDbConnection(#"Provider=Microsoft.Jet.OLEDB.4.0;Data Source=|DataDirectory|\qbc.mdb"))
{
members.DBConnection.Open();
// find out which textboxes were filled out
foreach (Control ctrl in Controls)
{
if (ctrl is TextBox)
{
members.textBoxes.Add(new System.Collections.Generic.KeyValuePair<string, string>(((TextBox)ctrl).Name, ((TextBox)ctrl).Text));
}
}
for (int i = 0; i < members.textBoxes.Count; i++)
{
members.DBCommand.Parameters.AddWithValue(String.Format("#{0}", members.textBoxes[i].Key),
!string.IsNullOrEmpty(members.textBoxes[i].Value) ? members.textBoxes[i].Value : "");
}
using (members.DBCommand = new System.Data.OleDb.OleDbCommand("INSERT INTO members (fullName, birthday, phoneNumber, address, email, status, anniversary)" +
"VALUES(#" + members.textBoxes + ")", members.DBConnection))
{
try
{
members.DBCommand.ExecuteNonQuery();
}
catch (System.Data.OleDb.OleDbException ex)
{
MessageBox.Show(ex.Message, "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
return;
}
}
}
}
But when I try to use members.textBoxes.Key (on the using members.DBCommand line, it is giving me an error:
List<KeyValuePair<string, string>> does not contain a definition for 'Key' and no extension method named 'Key' accepting a first argument of 'List<KeyValuePair<string,string>>' could be found (are you missing a using directive or assembly reference?)
I would really prefer not to type out all the names of the textboxes in the VALUES part of the query, if there is a way to do that.
Any help would be appreciated
Thanks!
Update -
I have changed my code to so it follows some answers given
private void buttonInsert_Click(object sender, EventArgs e)
{
// associate the textboxes with the column fields in the database
// long but meh
// household head textboxes
textBox_hh.Tag = "fullName";
textBoxHHBirthday.Tag = "birthday";
textBoxPhone.Tag = "phoneNumber";
textBoxAddress.Tag = "address";
textBoxEmail.Tag = "email";
textBoxStatus.Tag = "status";
using (members.DBConnection = new System.Data.OleDb.OleDbConnection(#"Provider=Microsoft.Jet.OLEDB.4.0;Data Source=|DataDirectory|\qbc.mdb"))
{
members.DBConnection.Open();
// find out which textboxes were filled out
foreach (var textbox in Controls.Cast<Control>().OfType<TextBox>())
{
var pair = new KeyValuePair<string, string>(textbox.Tag.ToString(), textbox.Text);
members.textBoxes.Add(pair);
}
var columnNames = string.Join(", ", members.textBoxes.Select(m => m.Key));
var parameterNames = string.Join(", ", members.textBoxes.Select(m => $"#{m.Key}"));
var query = $"INSERT INTO members ({columnNames}) VALUES ({parameterNames})";
using (members.DBCommand = new System.Data.OleDb.OleDbCommand(query, members.DBConnection))
{
try
{
foreach (var member in members.textBoxes)
{
members.DBCommand.Parameters.AddWithValue($"#{member.Key}", member.Value);
}
members.DBCommand.ExecuteNonQuery();
}
catch (System.Data.OleDb.OleDbException ex)
{
MessageBox.Show(ex.Message, "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
return;
}
}
}
}
but unfortunately the query now is just INSERT INTO members () VALUES ()
If your table's columns' names are the same as your textboxes names:
using (members.DBCommand = new System.Data.OleDb.OleDbCommand("INSERT INTO members (" + String.Join(", ", members.textBoxes.Select(tb => tb.Key).ToArray()) + ")" +
" VALUES(" + String.Join(", ", members.textBoxes.Select(tb => "'" + tb.Value+ "'").ToArray()) + ")", members.DBConnection))
Else and if the items of the list textboxes are added in the same order of your columns then try this:
using (members.DBCommand = new System.Data.OleDb.OleDbCommand("INSERT INTO members (fullName, birthday, phoneNumber, address, email, status, anniversary)" +
" VALUES(" + String.Join(", ", members.textBoxes.Select(tb => "#" + tb.Value).ToArray()) + ")", members.DBConnection))
I think you are now spending more time for trying generate insert query automatically then if you have typed it manually.
For generating proper insert query you need loop column names and column values(read names of parameters).
You definitely should use SqlParameters for values
var columnNames = string.Join(", ", members.textBoxes.Select(m => m.Key));
var parameterNames = string.Join(", ", members.textBoxes.Select(m => $"#{m.Key}"));
var query = $"INSERT INTO members ({columnNames}) VALUES ({parameterNames})";
using (var command = new OleDbCommand(query, members.DBConnection))
{
foreach (var member in members.textBoxes)
{
command.Parameters.AddWithValue($"#{member.Key}", member.Value)
}
command.ExecuteNonQuery();
}
You can associate column names with textboxes by using .Tag property
textboxFullName.Tag = "fullName";
Then create key value pairs with column names as a key
foreach (var textbox in Controls.Cast<Control>().OfType<TextBox>())
{
var pair = new KeyValuePair<string, string>(textbox.Tag.ToString(), textbox.Text);
members.textBoxes.Add(pair);
}
Related
I'm new to Dapper, and writing a query that will pull from a provided schema and table, along with using dynamic ordering and filtering.
Dapper make dynamic parameters very simple, however, I'm not sure how to do this with tables in the order by and where clauses. Here's my method below, and I see the issues with SQL injection:
public GridData GetGridData(string schema, string table, TableDataParameters tableDataParameters)
{
using (var dbConnection = VarConnection)
{
dbConnection.Open();
if (!this.TableExists(dbConnection, schema, table))
{
throw new ItemNotFoundException($"Could not locate table {schema}.{table}.");
}
string orderyByClause = string.Join(",", tableDataParameters.SortModel.Select(s => $"[{s.ColId}] {(s.Sort.ToLower() == "asc" ? "asc" : "desc")}"));
var parameters = new DynamicParameters();
string whereClause;
if (tableDataParameters.FilterModel == null || !tableDataParameters.FilterModel.Any())
{
whereClause = "1=1";
}
else
{
whereClause = string.Join(" AND ", tableDataParameters.FilterModel.Select((fm, i) =>
{
string whereParam = $"whereParam{i}";
parameters.Add(whereParam, fm.Filter);
if (fm.Operation == "startsWith")
{
return $"[{fm.Column}] LIKE #{whereParam} + '%'";
}
throw new InvalidOperationException($"Unsupported filter operation '{fm.Operation}'");
}));
}
var query = $"SELECT COUNT(1) [total] " +
$"FROM [{schema}].[{table}] " +
$"WHERE {whereClause} " +
$"SELECT * " +
$"FROM [{schema}].[{table}] " +
$"WHERE {whereClause} " +
$"ORDER BY {orderyByClause} " +
$"OFFSET {tableDataParameters.StartIndex.Value} ROWS " +
$"FETCH NEXT {tableDataParameters.StopIndex.Value - tableDataParameters.StartIndex.Value} ROWS ONLY";
int total = 0;
using (var reader = dbConnection.ExecuteReader(query, parameters))
{
// First batch, it's the count
if (reader.Read())
{
total = reader.GetInt32(0);
}
var gridColumns = new List<GridColumn>();
var gridRows = new List<string[]>();
if (reader.NextResult() && reader.Read())
{
for (int i = 0; i < reader.FieldCount; i++)
{
string key = reader.GetName(i);
gridColumns.Add(new GridColumn(key, key, null, ""));
}
var items = new object[reader.FieldCount];
reader.GetValues(items);
gridRows.Add(items.Select(i => i.ToString()).ToArray());
}
while (reader.Read())
{
var items = new object[reader.FieldCount];
reader.GetValues(items);
gridRows.Add(items.Select(i => i.ToString()).ToArray());
}
return new GridData(tableDataParameters.StartIndex.Value, tableDataParameters.StopIndex.Value, total, gridRows.Count(), gridColumns.ToArray(), gridRows.ToArray());
}
}
}
Should I use something like DbCommandBuilder.QuoteIdentifier, https://msdn.microsoft.com/en-us/library/system.data.common.dbcommandbuilder.quoteidentifier(v=vs.110).aspx
in this case? That doesn't seem like it would help so much here.
Thanks!
Dynamic parameters is an oxymoron! Dapper makes parameters easy, but you can't paramaterize table and column names. This is a restriction of SQL, not dapper. If you really want to do this, you have to use dynamic sql and string methods, and you're on your own as regards SQL injection.
You will be happier and live longer if you don't do this. It's just a bad road. You're not adding much value, and you're potentially introducing a load of problems and limitations.
It looks like you're writing an app to browse a database. Good tools already exist for this!
I'm trying to execute the following code, where two columns full of game ids in my DB are matched, then three separate queries are executed to pull related information for the game ids that matched, and it's all packaged nicely in an e-mail.
foreach (var TempId in ResultGameIDCompare)
var command1 = "EXEC DB query";
var command2 = "EXEC DB query";
var command3 = "EXEC DB query";
var GameInfo = db2.Query(command1, (string)TempId.Game_ID);
var SystemInfo = db2.Query(command2, (string)TempId.Game_ID);
var EditionInfo = db2.Query(command3, (string)TempId.Game_ID);
foreach (var result in GameInfo)
{
foreach (var result2 in SystemInfo)
{
foreach (var result3 in EditionInfo)
{
var text = " <ul>"+
"<p>My game information is as follows:" +#result.Name+"</p>"+
" <strong>System:</strong> " #result2.System+
" <strong>Price:</strong> " +#result3.Price+
" <strong>Edition:</strong> " +#result3.Edition+
"<ul>";
WebMail.Send("Test#test.com",
"This is a test",
body : text,
isBodyHtml:true );
}
}
}
The code works but if one of my queries returns an empty row nothing is fed into the collection for that game id and understandably no e-mail is generated for that game id.
So I'm trying to re-work my code so an e-mail is still generated even if an empty row is returned for the SystemInfo or EditionInfo query(GameInfo will never return an empty row) but I'm having difficulty figuring out how to go about this. I've attempted modifying my code as follows:
var Blank = " ";
foreach (var TempId in ResultGameIDCompare)
var command1 = "EXEC DB query";
var command2 = "EXEC DB query";
var command3 = "EXEC DB query";
var GameInfo = db2.Query(command1, (string)TempId.Game_ID);
var SystemInfo = db2.Query(command2, (string)TempId.Game_ID);
var EditionInfo = db2.Query(command3, (string)TempId.Game_ID);
foreach (var result in GameInfo)
{
foreach (var result2 in SystemInfo.DefaultIfEmpty(Blank))
{
foreach (var result3 in EditionInfo.DefaultIfEmpty(Blank))
{
var text = " <ul>"+
"<p>My game information is as follows:" +#result.Name+"</p>"+
" <strong>System:</strong> " #result2.System+
" <strong>Price:</strong> " +#result3.Price+
" <strong>Edition:</strong> " +#result3.Edition+
"<ul>";
WebMail.Send("Test#test.com",
"This is a test",
body : text,
isBodyHtml:true );
}
}
}
But it returns error "string' does not contain a definition for 'System'" and I'm honestly not sure if this alteration will do what I think it will do. Return " " in positions no data was returned from the query.
foreach (var result in GameInfo)
{
StringBuilder sb = new StringBuilder();
sb.Append("<ul>");
sb.AppendFormat("<p>My game information is as follows: {0}</p>", #result.Name);
foreach (var result2 in SystemInfo)
{
sb.AppendFormat("<strong>System: {0}</strong>", #result2.System);
foreach (var result3 in EditionInfo)
{
sb.AppendFormat("<strong>Price: {0}</strong>", #result3.Price);
sb.AppendFormat("<strong>Edition: {0}</strong>", #result3.Edtion);
}
}
sb.Append("</ul>");
WebMail.Send("Test#test.com",
"This is a test",
body: sb.ToString(),
isBodyHtml: true);
}
Just move the creation of the string outside into the first loop, and add to it in the inner loops as you get more data. StringBuilder is a high performance string concatenation system in the System.Text namespace and avoids pricey + concatenations.
Your variable Blank needs to be an IEnumerable of the same type as your original collection.
If your SystemInfo is a collection of strings,
SystemInfo.DefaultIfEmpty(new[]{ Blank })
will do.
i hope you can give me a hint for my problem i have with my code.
I have a DataGridView which got its Data from an Excelfile.
Now to get structure in it, i want to make groups (keyword in dsdslls) and add valueNames (value of dsdslls and keyword in dslls) to that groups and add that content (value of dslls) to valueNames as KeyValuePair.
My Problem is not to add all that stuff, but to get it back.
here is the code (add stuff):
internal Dictionary<String, Dictionary<String, LinkedList<String>>> BuildList(DataGridView dgv)
{
//create the main Dictionary
Dictionary<String, Dictionary<String, LinkedList<String>>> dsdslls = new Dictionary<String, Dictionary<String, LinkedList<String>>>();
String groupName = "Project data", value = "", valueName = "", text = "";
//run through the whole list
for (int i = 0; i < dgv.RowCount - 1; i++)
{
//create new Dictionary for the Content
Dictionary<String, LinkedList<String>> dslls = new Dictionary<String, LinkedList<String>>();
//check if the String at i is a groupName, if so add it to groupName
if (isGroupName(dgv, i))
{
groupName = dgv.Rows[i].Cells[0].Value.ToString();
text = "Adding in group: " + groupName + " to value: ";
}
//now go forward in the list until you next Cell is empty or the list ended
do
{
//check if the String at i is a valueName, if so add it to valueName
if (isValueName(dgv, i))
{
//create the LinkedList for units and values
LinkedList<String> lls = new LinkedList<String>();
valueName = dgv.Rows[i].Cells[0].Value.ToString();
//check if group and valuename are NOT the same
if (isNotSame(valueName, groupName))
{
//now run the colums for units and values and add them to the List until you reach the end of used columns
int j = 0;
do
{
value = dgv.Rows[i].Cells[1 + j].Value.ToString();
lls.AddLast(value);
if (j == 0)
{
text += "\n" + valueName + " in (" + lls.First.Value.ToString() + "): ";
}
else
{
text += lls.Last.Value.ToString();
}
j++;
} while (j < dgv.Rows[i].Cells.Count - 1);
//add the valueName and List as new keyvaluepair to the dictionary.
dslls.Add(valueName, lls);
}
}
i++;
} while (!isStringEmpty(dgv.Rows[i].Cells[0].Value.ToString()) && i < dgv.RowCount - 1);
//show added content
MessageBox.Show(text);
//check if main dictionary contains the latest groupname, if not add the groupName and the last dictionary to the main dictionary
if (!dsdslls.ContainsKey(groupName))
{
dsdslls.Add(groupName, dslls);
}
}
MessageBox.Show("Building successfully finished.");
return dsdslls;
}
I'm not getting the right content back to the specified groupName... for example:" groupName = "Project Data" i got back the content of the group:" Electrical Network" which is the next keyword in the maindictionary
now the code to get the Data:
internal void /*Dictionary<String, LinkedList<String>>*/ GetGroupContent(Dictionary<String, Dictionary<String, LinkedList<String>>> dsdslls, String groupName)
{
//Dictionary<String, LinkedList<String>> dslls = new Dictionary<String, LinkedList<String>>();
String groupN = "", text = "";
//Check if Dictionary contains groupName
if (dsdslls.ContainsKey(groupName))
{
//run through the dictionary
foreach (var s in dsdslls)
{
//give back the keyword (just for the MessageBox)
if (s.Key == groupName)
{
groupN = s.Key;
}
else
{
//run throught the little dictionary to get the keywords from it.
foreach (var t in s.Value)
{
text += t.Key + "\n";
}
}
}
MessageBox.Show("Content of Group " + groupN + ": \n" + text);
text = "";
}
//return dslls;
}
Kind regards
Mirko
It is hard to understand what you expect from this code as the main problem is not well described.
Anyway, it seems that there might be problem in your data retrieval logic.
If you want to get data of group with matching name, then you have to move else part of your if statement. You need to do text concatenation only when group with correct name is found.
...
//give back the keyword (just for the MessageBox)
if (s.Key == groupName)
{
groupN = s.Key;
//run throught the little dictionary to get the keywords from it.
foreach (var t in s.Value)
{
text += t.Key + "\n";
}
}
...
I have a list box and I bind it to my table Position and display the column Position. I inserted 2 values w/ the same position in my table and my list box displays 2 of the same values that i inserted. So, can you help me display only one of the same value in my list box?
i only have this code so far:
private void listBox1_SelectedIndexChanged(object sender, EventArgs e)
{
try
{
string select;
select = listBox1.SelectedValue.ToString();
dataGridView1.DataSource = this.mAINDATABASEDataSet.tblPosition.Select("Position like '%" + select + "%'");
int numRows = dataGridView1.Rows.Count;
txtCount.Text = Convert.ToString(numRows);
txtSearchCategory.Text = select.ToString();
try
{
MAINDATABASEDataSet.tblCategorizationDataTable GetCategoryCommand1 = GetCategoryData(this.txtSearchCategory.Text.Trim());
MAINDATABASEDataSet.tblCategorizationRow GetCategoryCommand2 = (MAINDATABASEDataSet.tblCategorizationRow)GetCategoryCommand1.Rows[0];
this.txtSG.Text = GetCategoryCommand2.SalaryGrade.ToString();
this.txtMales.Text = GetCategoryCommand2.Male.ToString();
this.txtFemales.Text = GetCategoryCommand2.Female.ToString();
}
catch
{
MessageBox.Show("This Position is not saved yet!", "Information".ToUpper(), MessageBoxButtons.OK, MessageBoxIcon.Information, MessageBoxDefaultButton.Button1);
txtSG.Text = "";
txtMales.Text = "";
txtFemales.Text = "";
}
}
catch
{
return;
}
}
You can do this by selecting distinct in LINQ. Try this for your data source:
dataGridView1.DataSource = this.mAINDATABASEDataSet
.GroupBy(i => i.Position)
.Select(i => i.First())
.Where(i => i.Position.Contains(select));
include using System.Linq; namespace in your project or reference it. Let me know if this works for you.
Q:
When i try to execute the following parametrized query:
INSERT INTO days (day,short,name,depcode,studycode,batchnum) values (?,?,?,?,?,?);SELECT SCOPE_IDENTITY();
through command.ExecuteScalar();
throws the following exception:
ERROR [07001] [Informix .NET provider]Wrong number of parameters.
Where is the problem?
EDIT:
public static int InsertDays(List<Day> days)
{
int affectedRow = -1;
Dictionary<string, string> daysParameter = new Dictionary<string, string>();
try
{
foreach (Day a in days)
{
daysParameter.Add("day", a.DayId.ToString());
daysParameter.Add("short", a.ShortName);
daysParameter.Add("name", a.Name);
daysParameter.Add("depcode", a.DepCode.ToString());
daysParameter.Add("studycode", a.StudyCode.ToString());
daysParameter.Add("batchnum", a.BatchNum.ToString());
affectedRow = DBUtilities.InsertEntity_Return_ID("days", daysParameter);
daysParameter.Clear();
if (affectedRow < 0)
{
break;
}
}
}
catch (Exception ee)
{
string message = ee.Message;
}
return affectedRow;
}
public static int InsertEntity_Return_ID(string tblName, Dictionary<string, string> dtParams)
{
int Result = -1;
DBConnectionForInformix DAL_Helper = new DBConnectionForInformix("");
string[] field_names = new string[dtParams.Count];
dtParams.Keys.CopyTo(field_names, 0);
string[] field_values = new string[dtParams.Count];
string[] field_valuesParam = new string[dtParams.Count];
dtParams.Values.CopyTo(field_values, 0);
for (int i = 0; i < field_names.Length; i++)
{
field_valuesParam[i] = "?";
}
string insertCmd = #"INSERT INTO " + tblName + " (" + string.Join(",", field_names) + ") values (" + string.Join(",", field_valuesParam) + ");SELECT SCOPE_IDENTITY();";
Result = int.Parse(DAL_Helper.Return_Scalar(insertCmd));
return Result;
}
You haven't shown where you're actually populating the parameter values. Given that you've got the right number of question marks, I suspect that's where the problem lies.
EDIT: Okay, now you've posted more code, it's obvious what's going wrong: your Return_Scalar method isn't accepting any actual values! You're not using field_values anywhere after populating it. You need to set the parameters in the command.
(You should also look at .NET naming conventions, by the way...)
Ensure that where you are providing the parameters that one of the values is not null. That may cause the provider to ignore the parameter. If this is your issue pass DBNull.
EDIT
As Jon stated you need to use command.Parameters to give the command the parameters to use in the query.