Constructing a insert query based on a LINQ query - c#

I am trying to build a sql insert query based on the number of filled out textboxes returned from a LINQ query. Basically, the start of the textboxes start at the tab index number 13 and ends at the tab index number 33 and adds the non empty textboxes to a keyvaluepair list. The issue that I am confused about is how to add the filled out textboxes' values to named parameters inside the insert query without having a error of number of query values and destination fields are not the same. Here is the code I have in place:
// use LINQ to fetch all the children textboxes based on the ones that are not empty
System.Collections.Generic.Dictionary<string, string> dictionary = tabCtrl1.TabPages["tabPage1"].Controls.OfType<TextBox>()
.Where(t => t.TabIndex >= startTabIndex && t.TabIndex <= endTabIndex && !string.IsNullOrWhiteSpace(t.Text))
.Select(x => new System.Collections.Generic.KeyValuePair<string, string>(x.Name, x.Text))
.ToDictionary(z => z.Key, z => z.Value);
// loop through all the children textboxes
// and assign them to the list members.childTextBoxes
foreach (System.Collections.Generic.KeyValuePair<string, string> kvp in dictionary)
{
members.childTextBoxes.Add(new System.Collections.Generic.KeyValuePair<string, string>($"{kvp.Key}", $"{kvp.Value}"));
}
and then constructing of the insert query:
for (int i = 0; i < members.childTextBoxes.Count; i++)
{
using (members.DBCommand = new System.Data.OleDb.OleDbCommand("INSERT INTO children (pid, childName, birthday, childEmail)" +
"VALUES (" + lastInsertId + ", #" + members.childTextBoxes[i].Key + ")", members.DBConnection))
{
members.DBCommand.Parameters.AddWithValue("#" + members.childTextBoxes[i].Key, members.childTextBoxes[i].Value);
}
// error occurs here. i'm assuming its to do
if (members.DBCommand.ExecuteNonQuery() > 0)
{
MessageBox.Show("Records inserted", "QBC", MessageBoxButtons.OK);
}
}
}
I hope this is enough information that describes the problem I am confused about.
Any help would be appreciated.
Thanks!
I can try to add more information if it helps make my question more clear.
update-
I went ahead and tried to use .Add instead of .AddWithValue but unfortunately that kept giving me an insert into query syntax error.
Here is the updated code for the insert query builder:
string fieldList = $"{string.Join(",", members.childTextBoxes.Select(tb => mapToDatabase[tb.Key]))}";
string valueList = $"{string.Join(",", members.childTextBoxes.Select(tb => "?"))}";
string insertQuery = $"INSERT INTO children {fieldList} VALUES {valueList}";
using (members.DBCommand = new System.Data.OleDb.OleDbCommand(insertQuery, members.DBConnection))
{
foreach (var field in members.childTextBoxes)
{
members.DBCommand.Parameters.Add("#" + field.Key, OleDbType.LongVarChar).Value = field.Value;
}
if (members.DBCommand.ExecuteNonQuery() > 0) // error occurs here
{
MessageBox.Show("Records inserted", "QBC", MessageBoxButtons.OK);
}
}

The best I can tell from the code you are showing in your question, you would want something like this:
var controlMap = new Dictionary<string, string>();
controlMap.Add(nameof(txtChildName), "ChildName");
controlMap.Add(nameof(txtParentName), "ParentName");
string fieldList = $"({string.Join(",", childTextBoxes.Select(tb => controlMap[tb.Key]))})" ;
string valueList = $"({string.Join(",", childTextBoxes.Select(tb => "?"))})";
string insertStatement = $"INSERT INTO children {fieldList} VALUES {valueList}";
var command = new OleDbCommand(insertStatement, members.DBConnection);
foreach (var field in childTextBoxes)
{
command.Parameters.Add(field.Value);
}
OleDbCommand doesn't support named parameters, so you have to use positional ones. They are marked with an "?", and added in the order they are used.
You also need to build both the field list, and the values list in the SQL insert statement, so that that order of fields in your field list matches the order that the "?" markers will be populated when you add the parameters.
I haven't been able to test this, since I don't have your full setup, but it should get you pretty close. It assumes that childTextBoxes is declared as List<KeyValuePair<string, string>> since I can't see your actual declaration. You may have to adjust that a bit if it isn't correct.

fixed the error, I had to enclose the insert values with parenthesis.
string insertQuery = $"INSERT INTO children (pid, {fieldList}) VALUES (?,{valueList})";
that fixed it.

Related

Remove rows where column contains specific text

I want to remove all the rows of the data whose columns contains ? e.g. in around 100 rows for Column Status I am getting value as Unknown?, Error?, InProgress, Done
So , I want to remove all the rows which contains ?
Below are the code I am using
//I am splitting the string on the basis of delimeter ,
var data = from val in UserData
select val.Split(',');
//Below code is not working
var filterdata = from rows in data
where rows.Contains("?")
select rows;
You forgot to invert the contains:
string[] someStringArray = new string[]
{
"\"ABC\" ,\"Error?\",\"OK\"",
"\"DEF\",\"Inprogress\",\"FINE\"",
"1,2,3",
"?,2,3",
"1,?,3",
"4,5,6"
};
//I am splitting the string on the basis of delimeter ,
var data = from val in someStringArray
select val.Split(',');
//Below code is not working
var filterdata = from rows in data
where !rows.Contains("?") // "!" to select the rows WITHOUT "?"
select rows;
foreach (var item in filterdata)
{
foreach (var i in item)
{
Console.Write(i + ",");
}
Console.WriteLine();
}
return;
Result:
"DEF","Inprogress","FINE",
4,5,6,
This code is perfectly working, I think.
Beside this, I doing a wild guess: You're not searching for quesionmarks "?". The "?" is a character which is often shown if the character can't be shown in your expected encoding.
Have a look which number your chars have:
var chars = someStringArray.SelectMany(s => s.Select(c => c));
foreach (var item in chars.GroupBy(g => g.ToString() + " (" + ((int)g) + ")"))
{
Console.WriteLine(item.Key + ": " + item.Count());
}
Real questionmarks have a 63. If not you've got encoding problems..
You wrote:
I want to remove all the rows of the data whose columns contains "?"
You can never change the input sequence using LINQ functions. So you can't remove rows from your original data using LINQ.
What you can do, is use your data to create a new sequence that doesn't contain question marks. If desired, you can replace your original data with the new sequence.
Looking at your code, it seems that UserData is a sequence of strings, of which you expect that these strings contains comma separated values.
You want to split these CSV strings into their columns, but you don't want rows where any of your columns contain "?"
"A,?,B,C" => do not use this one, one of the column values equals "?"
"A,B,C" => use this one, none of the column values equal "?"
"A, Hello?, B" => use this one, although the second column contains a question mark
this second column is not equal to question mark
This is done as follows:
static readonly char[] separatorChars = new char[] {','}
const string questionMark = "?";
static readonly IEqualityComparer<string> comparer =
var rowsWithoutQuestionMarkValues = userData
// Split each line into column values, using comma as separator
.Select(line => line.Split(separatorChar)
// do not use the line if any of the columns equals the question mark
.Where(splitLine => !splitLine.Any(column => column == questionMark));
If your code might be running in a culture where a questionmark might look differently, for instance: "分号", consider using an IEqualityComparer<string>
readonly IEqualityComparer<string> comparer = GetStringComparerForMyCulture();
var result = ...
.Where(splitLine => !splitLine.Any(column => comparer.Equals(column, questionMark));

SSIS Script Component Input0Buffer method no GetName()?

I am looking for a way to obtain my property names in a SSIS data flow task Script Component. I have been searching high and low only finding this. I have been trying to get this code to work, but I am too novice to understand what is happening here and I don't feel it is explained very well(no offense).
The source before this component is using a SQL query joining two tables. Inside the component, I would like to compare column to column. Then call an update method I created to use SqlConnection to perform the update.
public override void Input0_ProcessInputRow(Input0Buffer Row)
{
if (Row.TableALastName != Row.TableBLastName)
// Call the update method if the last name did not match.
this.UpdateRecord("TableBLastName", Row.TableALastName.ToString(), Row.TableAAssociateId.ToString());
}
}
private void UpdateRecord(string columnName, string change, string associateId)
{
SqlConnection sqlConnection;
sqlConnection = new SqlConnection(this.Variables.Connection);
string updateQuery = "UPDATE [SomeDataBase].[dbo].[TableB] SET " + columnName + " = " + change + " WHERE [Associate_ID] = " + associateId;
using (SqlCommand cmd2 = new SqlCommand(updateQuery, sqlConnection))
{
sqlConnection.Open();
cmd2.ExecuteNonQuery();
sqlConnection.Close();
}
}
I would like to somehow get the PropertyName of Row.TableBLastName instead of having to hard code "TableBLastName" for each test I am doing, which will be a lot.
The problem is that the input buffer class does not have Property.GetName() This also means I can't add a method to the class to get the property names, as it is regenerated each run.
public Input0_ProcessInputRow(Input0Buffer Row)
{
Dictionary<string, List<string>> list = new Dictionary<string, List<string>>();
List<string> propertyList = new List<string>();
Type myType = typeof(Input0Buffer);
PropertyInfo[] allPropInfo = myType.GetProperties();
List<PropertyInfo> SqlPropInfo = allPropInfo.Where(x => !x.Name.Contains("AM_")).ToList();
// Loop through all the Sql Property Info so those without AM_
for (int i = 0; i < SqlPropInfo.Count(); i++)
{
List<string> group = new List<string>();
foreach (var propInfo in allPropInfo)
{
if (propInfo.Name.Contains(SqlPropInfo[i].Name))
{
// Group the values based on the property
// ex. All last names are grouped.
group.Add(propInfo.GetValue(Row, null).ToString());
}
}
// The Key is the Sql's Property Name.
list.Add(SqlPropInfo[i].Name, group);
}
foreach (var item in list)
{
// Do a check if there are two values in both SQL and Oracle.
if (item.Value.Count >= 2)
{
if (item.Value.Count() != item.Value.Distinct().Count())
{
// Duplicates exist do nothing.
}
else
{
// The values are different so update the value[0]. which is the SQL Value.
UpdateRecord(item.Key, item.Value[0], Row.AssociateId);
}
}
}
}
I separated the values from the two tables so there are two lists values from TableA and TableB. You can prefix the values from TableA with "AM_" or something distinct so you can use reflection to to get the properties with and without the prefix and find out which values belong to which table. Then I just loop through the properties and group the values with the properties from the target value (so those without the prefix "AM_") I then loop through the grouped list and compare the two values and if it's different, update TableA with the TableB values to match them
You are already in SSIS so I will propose using that (no matter how quick I usually jump to C# to solve problems)
This is a classic conditional split scenario:
Do your test then run the results into a SQL Update statement.

Iterate over Database table row and store values in a Dictionary?

I want to implement a Dictionary cache in my program. How can I store the database result seen in the image below in a Dictionary Collection ?
I want to iterate over the Database table and store the content of LanguageName and IsoCode columns in a Dictionary like this Dictionary<LanguageName,IsoCode>.
My database (ctlang) looks like this:
Here is my code:
private string GetLanguageForIsoCode(string isoCode)
{
//check the isocode column and return the corresponding language
using (var unitOfWork = dataAccessUnitOfWorkFactory.Create())
{
//need to call every time the sql query
string query = "SELECT languagename FROM ctlang WHERE isocode='" + isoCode + "'";
List<string> result = unitOfWork.OwEntities.Database.SqlQuery<string>(query).ToList();
if (result.FirstOrDefault() != null)
{
return result.FirstOrDefault();
}
//if language not available in Database, fallback to German as default language
Thread.CurrentThread.CurrentCulture = CultureInfo.GetCultureInfo("de");
//displayName = Deutsch
return CultureInfo.GetCultureInfo("de").NativeName;
}
}
Bonus question: How can I search for a key in a dictionary using the value ?
It is. There is no problem in returning a key by its value, the only issue would be that there could theoretically be more than one key assigned to that value, since the key is what matters. But in your particular case that should be no problem, since only one isocode represents one language. So there would be no problem to make it like that, with dictionary:
public Dictionary<string, string> languagesAndKeys = new Dictionary<string, string>(); //Create it
Then you can search for keys/values like that:
string myValueByKey = languagesAndKeys["myKey"]; //getting value by key is easy
string myKeyByValue = languagesAndKeys.FirstOrDefault(item => item.Value == "myValue").Key; //getting the key of the FIRST matching value/or returning the default type. You'll need a check to be sure.
Afterwards, you can easily load the data from the sql table into the dictionary. For this goal, you can either use a temporary datatable with dataadapter, which will work well as long as the table is not that big, or you can use a DataReader to loop trough rows in the sql table one by one. I'm gonna be using a temp DataTable:
string cmdText = "SELECT * FROM ctlang"; //As far as I saw your cmd text in the code example, you may still want to take a look tho
string connectionString = ""; //fill the connection string according to your SQL server data
SqlDataAdapter dataAdapter = new SqlDataAdapter(cmdText, connectionString);
DataTable dTable = new DataTable();
dataAdapter.Fill(dTable);
foreach(DataRow row in dTable.Rows)
languagesAndKeys.Add(row[1].ToString(), row[0].ToString());//second column as a key, first column as a value - just like the structure of your table.
This is how I solved the problem. In my Database the Table with the languages is named CTLANG and the columns are LANGUAGENAME and ISOCODE. I wanted to map these to into a Dictionary collection. So that at the end the dictionary looks like this: ["LanguageName","IsoCode"].
private static Dictionary<string, string> languageToIsoCode; //dictionary cache
private void InitializeLanguageCacheDictionary()
{
using (var unitOfWork = dataAccessUnitOfWorkFactory.Create())
{
languageToIsoCode = (from p in unitOfWork.OwEntities.CTLANG
select new {p.LANGUAGENAME, p.ISOCODE}).ToDictionary(p => p.LANGUAGENAME, p => p.ISOCODE);
}
}
Thanks to #D.Petrov I also found a way to search for the key in a dictionary and give its value back.
And this is how I optimized my method to use the dictionary cache.
private string GetLanguageForIsoCode(string isoCode)
{
if (languageToIsoCode == null)//if cache empty initialize it
{
InitializeLanguageCacheDictionary();
}
//searches inside the dictionary, look for value and then return key
//might be bad if there are more than one value asigned to a key
//because value does not habe to be unique
string languageFromIsoCodeFromCache = languageToIsoCode.FirstOrDefault(item => item.Value == isoCode).Key;
if (languageFromIsoCodeFromCache == null)
{
//fallback and use the German language as default
return CultureInfo.GetCultureInfo("de").NativeName;
}
return languageFromIsoCodeFromCache;
}

Adding a List to SQL database (Entity Database)

I got a database with members, each member has a list of sports they do.
now I want to loop through a listbox and add every selected item to my database.
This is my database :
And this is my code :
foreach (var item in sportCheckedListBox.CheckedIndices)
{
int sportindex = Convert.ToInt32(item.ToString()) + 1;
var queryResult = from sp in context.Sports
where sp.sportnr == sportindex
select sp;
foreach (var sport in queryResult)
{
myMember.Sports.Add(sport);
}
}
This looks kinda 'shady', how could I do this better ?
One thing I'd do for sure is move the query out of the loop. Queries should never exist in loops for performance and maintainability reasons. LINQ knows how to translate a (new int[] { 0, 1, 2, ... }).Contains(column) construct into a WHERE column IN (0, 1, 2, ...) statement, so let's use that:
// Get all checked items together
var lookupIndices = sportCheckedListBox.CheckedIndices.Select(i => Convert.ToInt32(item.ToString()) + 1);
// Find all matching sport numbers
var queryResult = from sp in context.Sports
where lookupIndices.Contains(sp.sportnr)
select sp;
// Now loop over the results
foreach (var sport in queryResult)
{
myMember.Sports.Add(sport);
}
// save changes
I think you can just do AddRange:
myMember.Sports.AddRange(queryResult);
myMember.Sports.SaveChanges()
You may need to covert queryResult to an IEnumerable type if it's not already though.
There is nothing fundamentally wrong with your approach, but you can achieve it more concisely with Linq.
Instead of your foreach loop, if you always want to assign a new list you could use
myMember.Sports = queryResult.ToList();
If you want to instead concatenate results to an existing list, you could use
myMember.Sports = myMember.Sports.Concat(queryResult.ToList());
If you wanted to do the same as above, but not have any duplicates (as defined by the object you are adding), instead
myMember.Sports = myMember.Sports.Union(queryResult.ToList());

How can I pass an unkown number of arguments to C#'s "Database.Open("DatabaseName").Query()" method?

I have tried Googling to find a solution, but all I get is totally irrelevant results or results involving 2 dimensional arrays, like the one here: http://social.msdn.microsoft.com/Forums/vstudio/en-US/bb4d54d3-14d7-49e9-b721-db4501db62c8/how-does-one-increment-a-value-in-a-two-dimensional-array, which does not apply.
Say I have this declared:
var db = Database.Open("Content");
var searchTerms = searchText.Split('"').Select((element, index) => index % 2 == 0 ? element.Split(new[] { ' ' }, StringSplitOptions.RemoveEmptyEntries) : new string[] { element }).SelectMany(element => element).ToList();
int termCount = searchTerms.Count;
(Note: All that you really need to know about searchTerms is that it holds a number of search terms typed into a search bar by the user. All the LINQ expression is doing is ensuring that text wrapped in qoutes is treated as a single search term. It is not really necessary to know all of this for the purpose of this question.)
Then I have compiled (using for loops that loop for each number of items in the searchTerms list) a string to be used as a SELECT SQL query.
Here is an example that shows part of this string being compiled with the #0, #1, etc. placeholders so that my query is parameterized.
searchQueryString = "SELECT NULL AS ObjectID, page AS location, 'pageSettings' AS type, page AS value, 'pageName' AS contentType, ";
for (int i=0; i<termCount; i++)
{
if(i != 0)
{
searchQueryString += "+ ";
}
searchQueryString += "((len(page) - len(replace(UPPER(page), UPPER(#" + i + "), ''))) / len(#" + i + ")) ";
}
searchQueryString += "AS occurences ";
(Note: All that you really need to know about the above code is that I am concatenating the incrementing value of i to the # symbol to dynamically compile the placeholder value.)
All of the above works fine, but later, I must use something along the lines of this (only I don't know how many arguments I will need until runtime):
foreach (var row in db.Query(searchQueryString, searchTerms[0]))
{
#row.occurences
}
(For Clarification: I will need a number of additional arguments (i.e., in addition to the searchQueryString argument) equal to the number of items in the searchTerms list AND they will have to be referencing the correct index (effectively referencing each index from lowest to highest, in order, separated by commas, of course.)
Also, I will, of course need to use an incrementing value to reference the appropriate index of the list, if I can even get that far, and I don't know how to do that either. Could I use i++ somehow for that?
I know C# is powerful, but maybe I am asking too much?
Use params keyword for variable numbers of parameters. With params, the arguments passed to a any function are changed by the compiler to elements in a temporary array.
static int AddParameters(params int[] values)
{
int total = 0;
foreach (int value in values)
{
total += value;
}
return total;
}
and can be called as
int add1 = AddParameters(1);
int add2 = AddParameters(1, 2);
int add3 = AddParameters(1, 2, 3);
int add4 = AddParameters(1, 2, 3, 4);
//-----------Edited Reply based on comments below---
You can use something like this to be used with SQL
void MYSQLInteractionFunction(String myConnectionString)
{
String searchQueryString = "SELECT NULL AS ObjectID, page AS location, 'pageSettings' AS type, page AS value, 'pageName' AS contentType, ";
SqlConnection myConnection = new SqlConnection(myConnectionString);
SqlCommand myCommand = new SqlCommand(searchQueryString, myConnection);
myConnection.Open();
SqlDataReader queryCommandReader = myCommand.ExecuteReader();
// Create a DataTable object to hold all the data returned by the query.
DataTable dataTable = new DataTable();
// Use the DataTable.Load(SqlDataReader) function to put the results of the query into a DataTable.
dataTable.Load(queryCommandReader);
Int32 rowID = 0; // or iterate on your Rows - depending on what you want
foreach (DataColumn column in dataTable.Columns)
{
myStringList.Add(dataTable.Rows[rowID][column.ColumnName] + " | ");
rowID++;
}
myConnection.Close();
String[] myStringArray = myStringList.ToArray();
UnlimitedParameters(myStringArray);
}
static void UnlimitedParameters(params string[] values)
{
foreach (string strValue in values)
{
// Do whatever you want to do with this strValue
}
}
I'm not sure I quite understand what you need from the question, but it looks like you're substituting a series of placeholders in the SQL with another value. If that's the case, you can use String.Format to replace the values like this:
object val = "a";
object anotherVal = 2.0;
var result = string.Format("{0} - {1}", new[] { val, anotherVal });
This way, you can substitute as many values as you need by simply creating the arguments array to be the right size.
If you're creating a SQL query on the fly, then you need to be wary of SQL injection, and substituting user-supplied text directly into a query is a bit of a no-no from this point of view. The best way to avoid this is to use parameters in the query, which automatically then get sanitised to prevent SQL injection. You can still use a 'params array' argument though, to achieve what you need, for example:
public IDataReader ExecuteQuery(string sqlQuery, params string[] searchTerms)
{
var cmd = new SqlCommand { CommandText = sqlQuery };
for (int i = 0; i < searchTerms.Length; i++)
{
cmd.Parameters.AddWithValue(i.ToString(), searchTerms[i]);
}
return cmd.ExecuteReader();
}
obviously, you could also build up the sql string within this method if you needed to, based on the length of the searchTerms array
Well, for how complex the question probably seemed, the answer ended up being something pretty simple. I suppose it was easy to overlook because I had never done this before and others may have thought it too obvious to be what I was looking for. However, I had NEVER tried to pass a variable length of parameters before and had no clue if it was even possible or not (okay, well I guess I knew it was possible somehow, but could have been very far from my method for all I knew).
In any case, I had tried:
foreach (var row in db.Query(searchQueryString, searchTerms)) //searchTerms is a list of strings.
{
//Do something for each row returned from the sql query.
}
Assuming that if it could handle a variable length number of arguments (remember each argument passed after the first in the Database.Query() method is treated as fillers for the placeholders in the query string (e.g., #0, #1, #2, etc.) it could accept it from a list if it could from an array.
I couldn't really have been any more wrong with that assumption, as passing a list throws an error. However, I was surprised when I finally broke down, converted the list to an array, and tried passing the array instead of the list.
Indeed, and here is the short answer to my original question, if I simply give it an array it works easily (a little too easily, which I suppose is why I was so sure it wouldn't work):
string[] searchTermsArray = searchTerms.ToArray();
foreach (var row in db.Query(searchQueryString, searchTermsArray)) //searchTermsArray is an array of strings.
{
//Do something for each row returned from the sql query.
}
The above snippet of code is really all that is needed to successfully answer my original question.

Categories