DataTable find or if not found insert row - c#

I have a DataTable dt with 2 columns. First col (call it CustomerId) is unique and doesn't allow nulls. the second one allows nulls and is not unique.
From a method I get a CustomerId and then I would like to either insert a new record if this CustomerId doesn't exist or increment by 1 what's in the second column corresponding to that CustomerId if it exists.
I'm not sure how I should approach this. I wrote a select statement (which returns System.Data.DataRow) but I don't know how to test whether it returned an empty string.
Currently I have:
//I want to insert a new row
if (dt.Select("CustomerId ='" + customerId + "'") == null) //Always true :|
{
DataRow dr = dt.NewRow();
dr["CustomerId"] = customerId;
}

If the datatable is being populated by a database. I would recommend making the customerid a identity column. That way when you add a new row it will automatically create a new customerid which will be unique and 1 greater than the previous id (depending on how you setup your identity column)
I would check the row count which is returned from the select statement. Something like
I would also use string.Format...
So it would look like this
var selectStatement = string.Format("CustomerId = {0}", customerId);
var rows = dt.Select(selectStatement);
if (rows.Count < 1){
var dr = dt.NewRow();
dr["CustomerId"] = customerId;
}

This is my method to solve similar problem. You can modify it to fit your needs.
public static bool ImportRowIfNotExists(DataTable dataTable, DataRow dataRow, string keyColumnName)
{
string selectStatement = string.Format("{0} = '{1}'", keyColumnName, dataRow[keyColumnName]);
DataRow[] rows = dataTable.Select(selectStatement);
if (rows.Length == 0)
{
dataTable.ImportRow(dataRow);
return true;
}
else
{
return false;
}
}

The Select Method returns an array of DataRow objects. Just check if its length is zero (it's never null).
By the way, don't write such statements in the code directly as in this example. There's a technique for breaching your code's security called "SQL Injection", I encourage you to read the Wikipedia Article. In brief, an experienced user could write SQL script that gets executed by your database and potentially do harmful things if you're taking customerId from the user as a string. I'm not experienced in database programming, this is just "general knowledge"...

Related

Find bankbalance with Dataset

As a side project I am developing a basic banking system using C# and WPF to create the forms.
At the moment, I have four forms and two different global classes.
The forms are:
Welcome Page; This form is used to create an initial dataset stored in BankData.cs. All of my database table is stored here - the table is called UserData.
Validate User; This form is where the user enters their account number and pin. I do some validation here to ensure it's 6 characters and only integers etc. I then call the Global.cs class which has a method to option the DB and log the user in (Check if it matches). If it does, then I store the AccountNumber and PIN in the BankData.cs class.
Options Form; This form is simply just three buttons, at the moment going to the Display Balance form.
Display Balance; This form should display the current Balance and the Remainign Withdrawal limit for that user (£250 pounds, although this goes down when they withdraw more. They can't withdraw more than this).
My problem is when I am trying to display the Current Balance. I try to query the dataset using linq by converting it to a datatable. However, when I try to display the balance I am getting this instead of a balance:
System.Linq.Enumerable+WhereSelectEnumerableIterator'2[System.Data.DataRow,System.String]
I am converting everything to a string and it doesn't change this message. I also tried returning the integer value of the balance but this causes compilier errors.
I've attached the codethat should show the balance on the label from the Options Form class, and the code that finds the balance in BankData.cs
BankData.cs
public static string FindBalance()
{
DataTable dt = new DataTable();
dt = ds.Tables[0];
var id = (from DataRow dr in dt.Rows
where (string)dr["AccountNo"] == accountNumber
select dr.Table.Rows[5].ToString());
//return dt.Rows[rowNum][columName].ToString();
return id.ToString();
}
DisplayBalance cs:
lblcurBal.Content = "Current Balance: " + BankData.FindBalance();
//Find the Balance of the Account Number here.
Please help.
Try This: a small change in your code
public static string FindBalance()
{
DataTable dt = new DataTable();
dt = ds.Tables[0];
var id = (from DataRow dr in dt.Rows
where dr["AccountNo"] == accountNumber
select dr["BalanceColumnName"]).FirstOrDefault();
return id.ToString();
}
EDIT:
This is what worked for Paul as he described in comments below :
public static string FindBalance()
{
DataTable dt = new DataTable();
dt = ds.Tables[0];
var id = (from DataRow dr in dt.Rows
where Convert.ToString(dr["AccountNo"]) == accountNumber
select dr["BalanceColumnName"]).FirstOrDefault();
return id.ToString();
}
You need to use id.FirstOrDefault(); in this particular case instead of id.ToString(); as return value since FirstOrDefault actualy gets data from your linq query and ToString is just query internal string representation.
In other words, value of variable id is not a result of query you've wrote, it is query itself. And FirstOrDefault retrieves first result form this query.
The problem is that id is an enumeration which is a collection of values, looks like you are expecting single value.
Assuming 6th column of your DataTable represents balance,
public static string FindBalance()
{
DataTable dt = new DataTable();
dt = ds.Tables[0];
var id = (from DataRow dr in dt.Rows
where (string)dr["AccountNo"] == accountNumber
select dr[5].ToString()).FirstOrDefault();
//return dt.Rows[rowNum][columName].ToString();
return id== null? "" : id;
}
You need to obtain the result from the collection, using something like FirstOrDefault()
return Convert.ToString(id.FirstOrDefault());
You are actually calling ToString on the iterator, and not actually obtaining the result.
I got this working based off of the solution by vedbhawsar.
By changing the code on the BankData.cs class to:
var id = (from DataRow dr in dt.Rows
where Convert.ToString(dr["AccountNo"]) == accountNumber
select dr["Balance"]).FirstOrDefault();
return id.ToString();
It allows my to display the balance of the user who has logged in.
Thanks a lot; now I just need to replicate this to take the Minimum Remaining Balance. Expect me back when it doesn't work lol.
Thanks again everyone who helped me come to this conclusion.

set column default value of data table when filed with mysqldataadapter

this is my code right now:
private static MySqlConnection conn = null;
private static MySqlDataAdapter AccountsDa = null;
private static MySqlCommandBuilder AccountsCb = null;
AccountsDa = new MySqlDataAdapter("SELECT * FROM accounts", conn);
AccountsCb = new MySqlCommandBuilder(AccountsDa);
Accounts = new DataTable();
AccountsDa.Fill(Accounts);
I'm trying to figure out how to define the column default values without having to do it by hand
if I do like this:
DataColumn col = new DataColumn();
col.ColumnName = "id";
col.AllowDBNull = false;
col.DataType = System.Type.GetType("System.Int32");
col.DefaultValue = 0;
Accounts.Columns.Add(col);
for every colum it works fine but how do I have it automatically set the default values from the database when the table is filled. I'm hoping I don't have to define 30 columns by hand.
I tried the Accountsda.FillSchema(Accounts, SchemaType.Source);
which sets up the allow nulls and auto increments but not default values
the problem arrises when adding a row to the data table later sometimes I only need to set the value for one column and let the rest of the columns resort to their default value.
I could put 180 lines of code to manually define the default values for inserting rows but there has to be a way to grab that from the database when creating/filling the data table
I'm using in memory data tables because there are times where data will only exist for example 2 minutes and then be deleted again as this is for a dedicated server for an online rts game. so to save hits on the database I'm using data tables and manipulating them and flushing them every 10 minutes so that I only have 1,000 hits to the database every 10 mins instead of possibly 40,000 hits
well according to the msdn gurus after finally getting a response on their forums its not possible to get the default values. all you can do is load wether the value is allowed to be null and wether its autoincrememnt but then you stll have to set the seed and step on auto incrememnt it doesn't get that from the database either but they gave a shorthand version that cuts it down to 30 lines of code instead of 180
after calling fillschema and then filling the data table can simply do like this wich cuts it down to one line instead of the six
Cities.Columns["wood"].DefaultValue = 0;
after a few replies there is even a much easier way to do this not the way I wanted but maybe it will help someone else down the same road instead of one line for each column this does them all in 3 lines
foreach (DataColumn col in Cities.Columns) {
if (col.ColumnName != "id") col.DefaultValue = 0;
}
id is the primary key and can't set a default value
So I was trying to do something similar to you (except I have no idea how to get the information about auto increment) - I got the idea from https://stackoverflow.com/a/12731310/222897
private void AssignMandatoryColumns([NotNull] DataTable structure, string tableName)
{
// find schema
string[] restrictions = new string[4]; // Catalog, Owner, Table, Column
restrictions[2] = tableName;
DataTable schemaTable = _dbCon.GetSchema("Columns", restrictions);
if (schemaTable == null) return;
// set values for columns
foreach (DataRow row in schemaTable.Rows)
{
string columnName = row["COLUMN_NAME"].ToString();
if (!structure.Columns.Contains(columnName)) continue;
if (row["IS_NULLABLE"].ToString() == "NO") structure.Columns[columnName].AllowDBNull = false;
//if (structure.Columns[columnName].AutoIncrement) continue; // there can be no default value
var valueType = row["DATA_TYPE"];
var defaultValue = row["COLUMN_DEFAULT"];
try
{
structure.Columns[columnName].DefaultValue = defaultValue;
if (!structure.Columns[columnName].AllowDBNull && structure.Columns[columnName].DefaultValue is DBNull)
{
Logger.DebugLog("Database column {0} is not allowed to be null, yet there is no default value.", columnName);
}
}
catch (Exception exception)
{
if (structure.Columns[columnName].AllowDBNull) continue; // defaultvalue is irrelevant since value is allowed to be null
Logger.LogWithoutTrace(exception, string.Format("Setting DefaultValue for {0} of type {1} {4} to {2} ({3}).", columnName, valueType, defaultValue, defaultValue.GetType(), structure.Columns[columnName].AllowDBNull ? "NULL" : "NOT NULL"));
}
}
}
The function takes the DataTable you want to set the values for (I get mine by querying the DB) and the name of the table.
For some reason the timestamp and date columns don't like their default value no matter what I do.

How to copy all the rows in a datatable to a datarow array?

I have two tables:
tbl_ClassFac:
ClassFacNo (Primary Key)
,FacultyID
,ClassID
tbl_EmpClassFac:
EmpID, (Primary Key)
DateImplement, (Primary Key)
ClassFacNo
I want to know all the Employees who are on a specific ClassFacNo. ie. All EmpID with a specific ClassFacNo... What I do is that I first search tbl_EmpClassFac with the EmpID supplied by the user. I store these datarows. Then use the ClassFacNo from these datarows to search through tbl_ClassFac.
The following is my code.
empRowsCF = ClassFacDS.Tables["EmpClassFac"].Select("EmpID='" + txt_SearchValueCF.Text + "'");
int maxempRowsCF = empRowsCF.Length;
if (maxempRowsCF > 0)
{
foundempDT = ClassFacDS.Tables["ClassFac"].Clone();
foreach (DataRow dRow in empRowsCF)
{
returnedRowsCF = ClassFacDS.Tables["ClassFac"].Select("ClassFacNo='" + dRow[2].ToString() + "'");
foundempDT.ImportRow(returnedRowsCF[0]);
}
}
dataGrid_CF.DataSource = null;
dataGrid_CF.DataSource = foundempDT.DefaultView;
***returnedRowsCF = foundempDT.Rows;*** // so NavigateRecordsCF can be used
NavigateRecordsCF("F"); // function to display data in textboxes (no importance here)
I know the code is not very good but that is all I can think of. If anyone has any suggestions please please tell me. If not tell me how do I copy all the Rows in a datatable to a datarow array ???
"How to copy all the rows in a datatable to a datarow array?"
If that helps, use the overload of Select without a parameter
DataRow[] rows = table.Select();
DataTable.Select()
Gets an array of all DataRow objects.
According to the rest of your question: it's actually not clear what's the question.
But i assume you want to filter the first table by a value of a field in the second(related) table. You can use this concise Linq-To-DataSet query:
var rows = from cfrow in tbl_ClassFac.AsEnumerable()
join ecfRow in tbl_EmpClassFac.AsEnumerable()
on cfrow.Field<int>("ClassFacNo") equals ecfRow.Field<int>("ClassFacNo")
where ecfRow.Field<int>("EmpId") == EmpId
select cfrow;
// if you want a new DataTable from the filtered tbl_ClassFac-DataRows:
var tblResult = rows.CopyToDataTable();
Note that you can get an exception at CopyToDataTable if the sequence of datarows is empty, so the filter didn't return any rows. You can avoid it in this way:
var tblResult = rows.Any() ? rows.CopyToDataTable() : tbl_ClassFac.Clone(); // empty table with same columns as source table

Autonumber and datatable with dbnull exception

i was doing some work on a datatable i filled with a oledbdataadapter made from an access database. and i stumbled upon this error:
Turns out that my table has this structure:
ID --> autonumber(PK)
lazos_>text
Asociaciones->text
and when i fill my datatable all values pass to it without any problems with all the correct values. I insert a new row like shown on the "insert row" part.
i do this asumming that my pk will instert the "autonumber" on row creation, but apparently it is not doing it because when i loop trought the rows i get a "invalid cast exception" with a Object cannot be cast from DBNull to other types."
I COULD insert an id value to the column, but when i update my dt to my database wont it create an error, because i have no way of knowing wich was the last row created?, or do i?
for example lets say in my datatable the last ID is 50, but on the database y previously made a record with id "51" but then erased it, if i inserted 51 based on my dt info, it would give an error right?
//// INSERT ROW
DataRow newRow = Tabla_Cods_Proy.NewRow();
newRow["Lazos"] = textBox1.Text ;
newRow["Asociaciones"] = textBox2.Text;
Tabla_Cods_Proy.Rows.Add(newRow);
MessageBox.Show("Enhorabuena!");
//CHECK ID's
for (int i = 0; i < Tabla_Cods_Proy.Rows.Count; i++)
{
if (Tabla_Cods_Proy.Rows[i].RowState != DataRowState.Deleted)
{
if (Tabla_Cods_Proy.Rows[i]["Lazos_asociados"].ToString() == "")
{
listBox7.Items.Add(Tabla_Cods_Proy.Rows[i]["Cod_Cliente"]);
listBox8.Items.Add(Tabla_Cods_Proy.Rows[i]["Cod_Inelectra"]);
ID_Cods_Proy_Sin_Asociar.Add(Convert.ToInt32(Tabla_Cods_Proy.Rows[i]["ID"]));
}
else
{
listBox3.Items.Add(Tabla_Cods_Proy.Rows[i]["Cod_Cliente"]);
listBox4.Items.Add(Tabla_Cods_Proy.Rows[i]["Cod_Inelectra"]);
ID_Cods_Proy_Asociados.Add(Convert.ToInt32(Tabla_Cods_Proy.Rows[i]["ID"]));
}
}
I had once similiar problem. What you need to do is that you retrieve the new identity ##IDENTITY of this column once you insert it into table. You can do that by using RowUpdated event.
Here is quick example from MSDN page (similiar to your case, see bottom of the page):
public static void Main()
{
//...connecting to access db and getting data to datatable...
// ...
// Adding a new row to datatable.
DataRow newRow = catDS.Tables["Categories"].NewRow();
newRow["CategoryName"] = "New Category";
catDS.Tables["Categories"].Rows.Add(newRow);
// Include an event to fill in the Autonumber value.
catDA.RowUpdated += new OleDbRowUpdatedEventHandler(OnRowUpdated);
}
protected static void OnRowUpdated(object sender, OleDbRowUpdatedEventArgs args)
{
// Include a variable and a command to retrieve the identity value from the Access database.
int newID = 0;
OleDbCommand idCMD = new OleDbCommand("SELECT ##IDENTITY", nwindConn);
if (args.StatementType == StatementType.Insert)
{
// Retrieve the identity value and store it in the CategoryID column.
newID = (int)idCMD.ExecuteScalar();
args.Row["CategoryID"] = newID;
}
}

ASP.NET DATA TABLE Traverse Problem

I am getting the Data table a output from my DataAccess Layer.
In My Datatable I am getting users Name,Number,Qualification
I want to assign a users name,number,qualification to a textbox for a particular user id,
How can i do that.
Help
Suppose you got datatable dt from the DAL
Then
var row = from t in dt
where t["userId"]='userid'
select t;
since you got row related to a user now you can use it to assign to the textboxs
txtName.Text = row["Name"]
assuming the datatable has 1 row:
DataRow row = table.Rows[0];
textbox1.text = row["Name"];
textbox2.text = row["Number"];
and so on
if there are multiple rows in the datatable, you need to select the row with that particular used id
DataRow row = table.Select("ID=" + UserID.ToString());
You have to pay attention to NULL values and number of rows.
if (table.Rows.Count == 1)
{
DataRow row = table.Rows[0];
NameTextBox.Text = row.IsNull("name") ? string.Empty : row["name"];
NumberTextBox.Text = row.IsNull("number") ? string.Empty : row["number"];
}
else
{
// Deal with no rows from DL
}
Using ternary operator makes you sure, you delete content of TextBoxes in case you reload row within already filled up page.
Also you may consider to used typed dataset, and access to columns will be generated by xsd.exe.

Categories