DataTable.Select string function in where clause - c#

I'm having problems with a DataTable.Select() where the matching values might contain leading spaces and need to be trimmed correctly to return the correct amount of records.
Currently my code is returning less records as the matching fails because of unwanted characters.
How do you handle DataTable.Select as the example SQL below suggests?
SELECT * FROM Table WHERE LTRIM(FullName) = ' Joe Smith'
I' tried
dataTable.Select("LTRIM(FullName) = ' Joe Smith'");
but it failed.
Any ideas?

I would suggest to use Linq-To-DataSet instead, it makes it a lot clearer and easier to maintain:
var rows = from row in dataTable.AsEnumerable()
where row.Field<string>("FullName").Trim() == "Joe Smith"
select row;
If you want to use LTRIM instead, you just have to replace Trim with TrimStart.
if you want an array or list, use ToArray or ToList, e.g.
DataRow[] rowsArray = rows.ToArray();
or a DataTable
dataTable = rows.CopyToDataTable();
Edit: if you insist on using DataTable.Select or you can't use linq, this should work(LTRIM is not supported):
rowsArray = dataTable.Select("TRIM(FullName) = 'Joe Smith'");

Give this a try:
string searchTerm = " Joe Smith";
string expression = String.Format("TRIM(FullName) = '{0}'", searchTerm.Trim());
dataTable.Select(expression);

DataTable excelData = objGetExcelData.DataExcel(objEntities.StrFilePath, ConfigSettings.GetAppConfigValue("select * from sheet1"));
StringBuilder strInput = new StringBuilder();
DataView view = new DataView(excelData);
DataTable distinctValues = view.ToTable(true, "GROUP_NAME");
if (distinctValues.Rows.Count > 0)
{
for (int i = 0; i < distinctValues.Rows.Count; i++)
{
strGroupName = Convert.ToString(distinctValues.Rows[i]["GROUP_NAME"]);
foreach (DataRow item in excelData.Select("GROUP_NAME = '" + strGroupName + "'"))
{
strInput.Append(Convert.ToString(item[0]));
strInput.Append("~");
strInput.Append(Convert.ToString(item[1]));
strInput.Append(",");
strDasID = Convert.ToString(item[0]);
}
}
}

Related

Convert DataTable to dictionary and combine columns with lowercase

I have a datatable like this:
Column1
Column2
Column3
Name1
Code111
12550
Name2
Code112
12551
Name3
Code113
12552
Name4
Code114
12553
I want to convert it to a dictionary where the first column is the key. The combination of the second column with lowercase letters and the third column is the value.
Expected result:
key
value
Name1
code111_12550
Name2
code112_12551
Name3
code113_12552
Name4
code114_12553
This is my code:
DataTable dt = new DataTable();
dt.Columns.Add("Column1");
dt.Columns.Add("Column2");
dt.Columns.Add("Column3");
dt.Rows.Add("Name1", "Code111", 12550);
dt.Rows.Add("Name2", "Code112", 12551);
dt.Rows.Add("Name3", "Code113", 12553);
dt.Rows.Add("Name4", "Code114", 12554);
Dictionary<string,string> dic = new Dictionary<string,string>();
for (int i = 0; i < dt.Rows.Count; i++)
{
string _k = dt.Rows[i][0].ToString();
string _v = dt.Rows[i][1].ToString().ToLower()+ "_" +
dt.Rows[i][2].ToString();
dic.Add(_k, _v);
}
Is there a better way to convert a datatable to a dictionary? For example, can I use Linq?
You can use the linq.enumerable.todictionary to convert the DataTable into a Dictionary:
var _dic = dt.AsEnumerable()
.ToDictionary<DataRow, string, string>(row => row.Field<string>(0),
row => row.Field<string>(1).ToLower() + "_" + row.Field<string>(2) );
Since .NET 6 is the oldest supported .NET Core version, one can use ranges to reduce the code to this :
var dict=dt.AsEnumerable().ToDictionary(
r=>r[0],
r=>String.Join('_',r.ItemArray[1..]));
If the values need to be lowercase, ToLower() can be called after joining.
var dict=dt.AsEnumerable().ToDictionary(
r=>r[0],
r=>String.Join('_',r.ItemArray[1..])?.ToLower());

Exploding a lambda expression

The code below works for me however, I would like to add a condition before it is added to the table. What I need is - if the "Scan Time" is between two dates, then it should be added to the "table" if not, then it should be disregarded.
This is for selecting the file..
private void btnSelectFile_Click(object sender, EventArgs e)
{
OpenFileDialog ofd = new OpenFileDialog()
{
Title = "Select the file to open.",
Filter = "DAT (*.dat)|*.dat|TXT (*.txt)|*.txt|All Files (*.*)|*.*",
InitialDirectory = Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments)
};
if (ofd.ShowDialog() == DialogResult.OK)
{
txtFilePath.Text = ofd.FileName;
loadDataToGridview();
}
}
This is for reading the file then adding it to the datagridview
private void loadDataToGridview()
{
DataTable table = new DataTable();
table.Columns.Add("Emp ID");
table.Columns.Add("Scan Time");
table.Columns.Add("Undefined 1");
table.Columns.Add("Undefined 2");
table.Columns.Add("Undefined 3");
table.Columns.Add("Undefined 4");
var lines = File.ReadAllLines(txtFilePath.Text).ToList();
lines.ForEach(line => table.Rows.Add(line.Split((char)9)));
return table;
}
I got the loadDataToGridview method from here but I do not know how to explode the
lines.ForEach(line => table.Rows.Add(line.Split((char)9)));
lambda expression to include the condition that I need. Let's assume that the name of the datepickers are dateFrom and dateTo.
Your help is greatly appreciated.
Do not use ReadAllLines method because it will load the entire file into memory. In other words, why load the entire file if only 1 line is between your dates.
Use the ReadLines method instead. Why? See my answer here.
var lines = File.ReadLines("").Select(x => Split(x)).Where(x => IsBetweenDates(x[1]));
lines.ForEach(row => table.Rows.Add(row));
dataGridView1.DataSource = table;
You should add your own error handling here as per your needs. I have added a few for you:
private bool IsBetweenDates(string value)
{
var dateValue = DateTime.ParseExact(value, "yyyy-MM-dd HH:mm:ss", CultureInfo.InvariantCulture);
return dateValue >= fromDate.Value && dateValue <= toDate.Value;
}
private string[] Split(string line)
{
if (string.IsNullOrWhitespace(x))
{
// There is nothing in this line. Is this allowed in your case?
// If yes do whatever you need to do here. For example, log it or something.
}
var splits = line.Split((char)9);
if (splits.Length != 6)
{
// This line does not have 6 fields so what do you want to do?
}
return splits;
}
Use Where() as suggested by #CodingYoshi, but split the line first (so you don't have to do it twice), using a Select() statement:
var lines = File.ReadAllLines(txtFilePath.Text).Select(line => line.Split(';')).Where(fields => fields[1] >= fromDate && fields[1] <= toDate).ToList();
lines.ForEach(row => table.Rows.Add(row));
You may also want to consider using something like CsvHelper instead of parsing the file manually
You can probably also use Select Method which in turns gets an array of all DataRow objects that match the filter criteria.
DataTable table = new DataTable();
table.Columns.Add("Emp ID");
//Add All your columns
var lines = File.ReadAllLines(txtFilePath.Text).ToList();
lines.ForEach(line => table.Rows.Add(line.Split((char)9)));
//Till the data has been already there in your DataTable.
//Create a new DataTable for Filtered Records.
DataTable FilteredTable = new DataTable();
//The Statement works like a SQL Statement which is equal to
//Select * from TableName Where DateColumn Between two dates.
DataRow[] rows = table.Select("date >= #" + from_date + "# AND date <= #" + to_date + "#");
//Now add all rows to the new Table.
foreach (DataRow dr in rows)
{
FilteredTable.ImportRow(dr);
}
dataGridView1.DataSource = FilteredTable;
if from_date and to_date is a DateTime and not a string, you need to use the ToString(your date format) to get the correct sql statement

Eliminate comma(,) from a column of a Data Table using LINQ

I have a DataTable as shown below:
After using below LINQ Expression on above DT:
if (dt.AsEnumerable().All(row => string.IsNullOrEmpty(row.Field<string>("SameReferences"))))
BindOldReferences(dt);
else
{
var grps = from row in dt.AsEnumerable()
let RefID = row.Field<string>("ReferenceID")
let RefDescription = row.Field<string>("ReferenceDescription")
let ReferenceUrl = row.Field<string>("ReferenceUrl")
let SortOrder = row.Field<int>("sortOrder")
group row by new { RefDescription, ReferenceUrl, SortOrder } into groups
select groups;
dt = grps.Select(g =>
{
DataRow first = g.First();
if (first.Field<string>("SameReferences") != null)
{
string duplicate = first.Field<int>("SortOrder").ToString();
first.SetField("SameReferences", string.Format("{0},{1}", duplicate, first.Field<string>("SameReferences")));
}
return first;
}).CopyToDataTable();
}
After applying above LINQ to DT it becomes :
Expected DT as below : eliminate (,) comma when there is single value in column Samereferences. So what changes i have to make to LINQ to get the expected below output.
Please help..!
You can use String.Trim method like this:-
first.SetField("SameReferences", string.Format("{0},{1}", duplicate,
first.Field<string>("SameReferences")).Trim(','));
It will remove all the trailing comma.
Try this:
if (first.Field<string>("SameReferences") != null)
{
string duplicate = first.Field<int>("SortOrder").ToString();
string sameReference = first.Field<string>("SameReferences");
if (String.IsNullOrEmpty(sameReference))
first.SetField("SameReferences", duplicate);
else
first.SetField("SameReferences", string.Format("{0},{1}", duplicate, sameReference));
}

How can I secure SQL parameters with entity framework SqlQuery?

I have this method that should take an unknown amount of id's.
I got this method almost done but it isnt secure yet for obvious reasons, i know i could write my own method to strip the parameters but i would be more comfortable by using some build in method for this.
Here is the method
public static List<LocationModel> FetchCitiesByAreas(IEnumerable<string> areas)
{
using (var db = new BoligEnt())
{
var sqlQuery = new StringBuilder();
var first = true;
sqlQuery.Append("SELECT DISTINCT a.city AS City, a.zip AS Zip ");
sqlQuery.Append("FROM zip_city AS a ");
sqlQuery.Append("WHERE country = 1 ");
foreach (var d in areas)
{
if (first)
{
sqlQuery.Append("AND a.area_id = '" + d + "'");
first = false;
}
else
{
sqlQuery.Append("OR a.area_id = '" + d + "'");
}
}
return db.Database.SqlQuery<LocationModel>(sqlQuery.ToString()).ToList();
}
}
i know it have this function built in but as i stated earlier i dont know the exact amount of ids that will come in
db.Database.SqlQuery<LocationModel>("SELECT * FROM table WHERE id = #p0 ;", id).ToList();
Thanks
While I completely agree with paqogomez, in that you should just use LINQ to do the query, the .SqlQuery has the ability to take a parameter array. You could change your statement to look like this:
var sqlQuery = new StringBuilder();
sqlQuery.Append("SELECT DISTINCT a.city AS City, a.zip AS Zip ");
sqlQuery.Append("FROM zip_city AS a ");
sqlQuery.Append("WHERE country = 1 ");
for (int i = 0; i < areas.Count; i++)
{
if (i == 0)
{
sqlQuery.Append("AND (a.area_id = #p" + i.ToString());
}
else
{
sqlQuery.Append(" OR a.area_id = #p" + i.ToString());
}
}
sqlQuery.Append(")");
var results = db.Database.SqlQuery<LocationModel>(sqlQuery.ToString(), areas.ToArray()).ToList();
I added the missing parenthesis needed to your query to correctly filter out the OR results as well. I've also taken the assumption that areas is something like a List, or at least something you can easily get the count from.
Why dont you just use Linq?
var locations = (from zip in db.zip_city
where areas.Contains(zip.area_id) && zip.Country == 1
select new LocationModel{
City = zip.City,
Zip = zip.Zip
})
.Distinct()
.ToList();
If you still want to parameterize your query, you need to use EntityCommand
Also note that your query will fail because you havent put parenthesis around your OR statements.
I suggest structuring your sql like this:
string sqlQuery =
#"SELECT DISTINCT a.city AS City, a.zip AS Zip
FROM zip_city AS a
WHERE country = 1 AND (1=0 "
for (int i = 0; i < areas.Count; i++)
{
sqlQuery.Append("OR a.area_id = #d" + i.ToString() + " ");
}
sqlQuery.Append(")");

InvalidCastException Error caused by DB row?

I have the following C# code:
private DataSet GetSummaryData(DataSet ds)
{
DataSet dsSum = new DataSet();
DataTable dtSum = new DataTable();
DataTable dataTable = ds.Tables[0];
if (dataTable != null)
{
if (dataTable.Rows.Count > 0)
{
if (dataTable.Columns.Count > 1)
{
dtSum.Columns.Add("Line Number", typeof(int));
dtSum.Columns.Add("Throughput", typeof(int));
dtSum.Columns.Add("Lost Time", typeof(int));
dtSum.Columns.Add("Pounds Made", typeof(int));
dtSum.Columns.Add("Pounds Lost", typeof(int));
dtSum.Columns.Add("Yearly Potential", typeof(int));
//Getting the Subtotal of PoundsMade based on the Line Number column
//C# linq query
var query = from row in dataTable.AsEnumerable()
group row by row.Field<int>("Linenumber") into grp
orderby grp.Key
select new
{
Linenumber = grp.Key,
TotalPoundsMade = grp.Sum(r => r.Field<int>("Pounds Made")),
AvgThroughput = grp.Average(r => r.Field<int>("Throughput")),
TotalLostTime = grp.Sum(r => r.Field<int>("Lost Time")),
AvgPercDown = grp.Average(r => r.Field<int>("% Down")),
TotalPoundsLost = grp.Sum(r => r.Field<int>("Pounds Lost")),
TotalYearlyPotential = grp.Sum(r => r.Field<int>("Yearly Potential")),
};
foreach (var grp in query)
{
dtSum.Rows.Add(grp.Linenumber, grp.TotalPoundsMade,grp.AvgThroughput,grp.TotalLostTime,
grp.AvgPercDown, grp.TotalPoundsLost, grp.TotalYearlyPotential);
string strXML = null;
strXML = strXML + "<set name='" + grp.Linenumber + "' value='" + grp.TotalPoundsMade + "'/>";
}
}
}
}
dsSum.Tables.Add(dtSum);
return dsSum;
}
This code, as you can see, uses Linq to access my database. The SQL is:
SELECT
PDT.LineNumber,
SUM(prdt.PoundsMade) as 'Pounds Made',
CAST(ROUND(SUM(CAST(prdt.PoundsMade as DECIMAL))/ (MIN(LSA.AvailableHRS) - SUM(PDT.DownTimeHrs)),0,0) as int)
as 'Throughput',
SUM(PDT.DownTimeHrs) as 'Lost Time',
Str(ROUND(CAST(SUM(PDT.DownTimeHrs) as DECIMAL)/CAST(MIN(LSA.AvailableHRS) as DECIMAL) * 100,0), 3,0) + '%'
as '% Down',
CAST((ROUND(SUM(CAST(prdt.PoundsMade as DECIMAL))/ (MIN(LSA.AvailableHRS) - SUM(PDT.DownTimeHrs)),0,0)) *
(SUM(PDT.DownTimeHrs)) as int) as 'Pounds Lost',
CAST(ROUND(SUM(CAST(prdt.PoundsMade as DECIMAL))/ (MIN(LSA.AvailableHRS) - SUM(PDT.DownTimeHrs)),0,0) as int) *
24 * 365 as 'Yearly Potential'
FROM
rpt_Line_Shift_ProdDownTime AS PDT
LEFT OUTER JOIN rpt_Line_Shift_Prod AS Prdt
ON PDT.LineNumber = Prdt.LineNumber
and PDT.ShiftNumber = Prdt.ShiftNumber
and PDT.WorkDate = Prdt.WorkDate
INNER JOIN rpt_Line_Shift_AvailableHrs AS LSA
ON PDT.LineNumber = LSA.LineNumber
and PDT.ShiftNumber = LSA.ShiftNumber
WHERE
PDT.WorkDate BETWEEN #p_From_Date and #p_Through_Date
GROUP BY
PDT.LineNumber, PDT.ShiftNumber
ORDER BY
PDT.LineNumber, PDT.ShiftNumber
The hangup seems to be that the '% Down' row is not casting correctly. It is a decimal type in SQL, and it makes sense that it would therefore be castable to type int in C#. Unfortunately, when I run the program, I receive a "InvalidCastException" message. Note that, if I comment out the following C# snippets:
"AvgPercDown = grp.Average(r => r.Field("% Down"))," and "grp.AvgPercDown,"
the code "works". Of course, that's not a fix. /shrug
Any thoughts? How can I fix this? I have tried casting the '% Down' row (in the C# code) as something other than int (such as double), but it doesn't like that.
Actually, it has been my experience that Oracle decimal does not correctly convert to C# Int, when using OracleDataReaders I have to get the value as a decimal and convert that to Int. Some newer versions of the ODAC seem to have corrected parts of this issue.
looks like "% Down" is a decimal value. In which case you should use:
AvgPercDown = grp.Average(r => r.Field<decimal>("% Down")),

Categories

Resources