Group and Count using LINQ - c#

I have a sql db that every time a device goes bad, it creates a record. Based on what happens, a different FaultCode is assigned.
I want to group and count the number of times an FaultCode exists.
Example:
FaultCode Count
1 6
2 20
I've written most of the code, I can query the db and execute a linq query. However, I can only return a list of Fault codes of the Counts. But not both.
Here is the code:
private static DataTable FaultCodeByCluster(DataTable referenceDt)
{
DataTable output = new DataTable();
foreach (DataColumn dtColum in (InternalDataCollectionBase)referenceDt.Columns)
output.Columns.Add(new DataColumn(dtColum.ColumnName, dtColum.DataType));
var query = from results in referenceDt.AsEnumerable()
group results by new
{
FaultCode = results.Field<int>("FaultCode"),
}
into newFaultCodes
orderby newFaultCodes.Key.FaultCode
select newFaultCodes.Count(); <--- count of fault codes
//select newFaultCodes.Key.FaultCode; <--- list out fault codes by group
foreach (var newFaultCodes in query)
{
Console.WriteLine("Value is {0}", newFaultCodes);
}
return output;
}

I haven't tested it, but try using something like this as your select statement:
select new { FaultCode = newFaultcodes.Key, Count = newFaultcodes.Count()};

Another way using Linq..
Adding assemblySystem.Data.DataSetExtensions, you can convert DataTable to List
var listOfFaultCode = dt1.Rows
.OfType<DataRow>()
.Select(dr => dr.Field<int>("FaultCode")).ToList();
var faultCodeGroupedByCount = listOfFaultCode.GroupBy(x => x);
foreach (var item in faultCodeGroupedByCount)
{
Console.WriteLine("FaultCode:" + item.Key + " FaultCount:" + item.Count());
}

Related

how to send List<int> to IN operator my sql

I have int list and I need to convert it so it will be possible to send it to IN operator from c# code.
I tried a lot of manipulate but got error every time.
string idsList = "(" + string.Join(",", ids.Select(id => $"'{id.ToString()}'")) +")";
var sql = #"SELECT * from user
WHERE user.id IN (#idsList))";
using (var connection = OpenConnection())
{
var results = await connection.QueryAsync<UserQueryResponse>(sql, new
{
idsList= idsList,
});
return results.Select(raw =>raw.CreateModel());
}
}
The column type of user.id is string
I assume you are using Dapper. Have a look at this answer. Try deleting the parenthesis in your query.
var sql = #"SELECT * from user WHERE user.id IN #idsList";
using (var connection = OpenConnection())
{
var results = await connection.QueryAsync<UserQueryResponse>(sql, new
{
idsList= idsList,
});
return results.Select(raw =>raw.CreateModel());
}
}
You should not use the parameter delimiter (#) if your query is not parametrized.
Warning: Do note that if the ids list were not of type int you could have had a SQL Injection problem.
List<int> ids = new List<int>{ 7, 14 ,21 };
string idsList = "(" + string.Join(",", ids.Select(id => $"'{id.ToString()}'")) +")";
/* Your idsList variable will contain "('7','14','21')" */
var sql = #"SELECT * from user WHERE user.id IN " + idsList;
A better approach would be using parameters:
StringBuilder idsList = new StringBuilder("in (");
for (int i=0; i< ids.Count; i++)
{
// Dynamically write the IN clause
if (i > 0) ? idsList.Append(",");
idsList.Append("#param" + i.ToString());
// Add the parameter that was added to the IN
command.Parameters.AddWithValue("#param" + i.ToString(), idsList[i])
}
idsList.Append(")");
/* idsList will cointain "in (#param0, #param1, #param2)" */

Dapper query with dynamic list of filters

I have a c# mvc app using Dapper. There is a list table page which has several optional filters (as well as paging). A user can select (or not) any of several (about 8 right now but could grow) filters, each with a drop down for a from value and to value. So, for example, a user could select category "price" and filter from value "$100" to value "$200". However, I don't know how many categories the user is filtering on before hand and not all of the filter categories are the same type (some int, some decimal/double, some DateTime, though they all come in as string on FilterRange).
I'm trying to build a (relatively) simple yet sustainable Dapper query for this. So far I have this:
public List<PropertySale> GetSales(List<FilterRange> filterRanges, int skip = 0, int take = 0)
{
var skipTake = " order by 1 ASC OFFSET #skip ROWS";
if (take > 0)
skipTake += " FETCH NEXT #take";
var ranges = " WHERE 1 = 1 ";
for(var i = 0; i < filterRanges.Count; i++)
{
ranges += " AND #filterRanges[i].columnName BETWEEN #filterRanges[i].fromValue AND #filterRanges[i].toValue ";
}
using (var conn = OpenConnection())
{
string query = #"Select * from Sales "
+ ranges
+ skipTake;
return conn.Query<Sale>(query, new { filterRanges, skip, take }).AsList();
}
}
I Keep getting an error saying "... filterRanges cannot be used as a parameter value"
Is it possible to even do this in Dapper? All of the IEnumerable examples I see are where in _ which doesn't fit this situation. Any help is appreciated.
You can use DynamicParameters class for generic fields.
Dictionary<string, object> Filters = new Dictionary<string, object>();
Filters.Add("UserName", "admin");
Filters.Add("Email", "admin#admin.com");
var builder = new SqlBuilder();
var select = builder.AddTemplate("select * from SomeTable /**where**/");
var parameter = new DynamicParameters();
foreach (var filter in Filters)
{
parameter.Add(filter.Key, filter.Value);
builder.Where($"{filter.Key} = #{filter.Key}");
}
var searchResult = appCon.Query<ApplicationUser>(select.RawSql, parameter);
You can use a list of dynamic column values but you cannot do this also for the column name other than using string format which can cause a SQL injection.
You have to validate the column names from the list in order to be sure that they really exist before using them in a SQL query.
This is how you can use the list of filterRanges dynamically :
const string sqlTemplate = "SELECT /**select**/ FROM Sale /**where**/ /**orderby**/";
var sqlBuilder = new SqlBuilder();
var template = sqlBuilder.AddTemplate(sqlTemplate);
sqlBuilder.Select("*");
for (var i = 0; i < filterRanges.Count; i++)
{
sqlBuilder.Where($"{filterRanges[i].ColumnName} = #columnValue", new { columnValue = filterRanges[i].FromValue });
}
using (var conn = OpenConnection())
{
return conn.Query<Sale>(template.RawSql, template.Parameters).AsList();
}
You can easily create that dynamic condition using DapperQueryBuilder:
using (var conn = OpenConnection())
{
var query = conn.QueryBuilder($#"
SELECT *
FROM Sales
/**where**/
order by 1 ASC
OFFSET {skip} ROWS FETCH NEXT {take}
");
foreach (var filter in filterRanges)
query.Where($#"{filter.ColumnName:raw} BETWEEN
{filter.FromValue.Value} AND {filter.ToValue.Value}");
return conn.Query<Sale>(query, new { filterRanges, skip, take }).AsList();
}
Or without the magic word /**where**/:
using (var conn = OpenConnection())
{
var query = conn.QueryBuilder($#"
SELECT *
FROM Sales
WHERE 1=1
");
foreach (var filter in filterRanges)
query.Append($#"{filter.ColumnName:raw} BETWEEN
{filter.FromValue.Value} AND {filter.ToValue.Value}");
query.Append($"order by 1 ASC OFFSET {skip} ROWS FETCH NEXT {take}");
return conn.Query<Sale>(query, new { filterRanges, skip, take }).AsList();
}
The output is fully parametrized SQL, even though it looks like we're doing plain string concatenation.
Disclaimer: I'm one of the authors of this library
I was able to find a solution for this. The key was to convert the List to a Dictionary. I created a private method:
private Dictionary<string, object> CreateParametersDictionary(List<FilterRange> filters, int skip = 0, int take = 0)
{
var dict = new Dictionary<string, object>()
{
{ "#skip", skip },
{ "#take", take },
};
for (var i = 0; i < filters.Count; i++)
{
dict.Add($"column_{i}", filters[i].Filter.Description);
// some logic here which determines how you parse
// I used a switch, not shown here for brevity
dict.Add($"#fromVal_{i}", int.Parse(filters[i].FromValue.Value));
dict.Add($"#toVal_{i}", int.Parse(filters[i].ToValue.Value));
}
return dict;
}
Then to build my query,
var ranges = " WHERE 1 = 1 ";
for(var i = 0; i < filterRanges.Count; i++)
ranges += $" AND {filter[$"column_{i}"]} BETWEEN #fromVal_{i} AND #toVal_{i} ";
Special note: Be very careful here as the column name is not a parameter and you could open your self up to injection attacks (as #Popa noted in his answer). In my case those values come from an enum class and not from user in put so I am safe.
The rest is pretty straight forwared:
using (var conn = OpenConnection())
{
string query = #"Select * from Sales "
+ ranges
+ skipTake;
return conn.Query<Sale>(query, filter).AsList();
}

deleting rows from datatable causes error

I am trying to remove rows that are not needed from a DataTable. Basically, there may be several rows where the itemID is identical. I want to find the rows where the column "failEmail" = "fail", and using the itemID of those rows, remove all rows from the emails DataTable that have the same itemID.
Here is what I have tried:
System.Diagnostics.Debug.Print(emails.Rows.Count.ToString() + " emails!");
// create a list of the email IDs for records that will be deleted
List<DataRow> rows2Delete = new List<DataRow>();
foreach (DataRow dr in emails.Rows)
{
if (dr["failEmail"].ToString().ToLower() == "fail")
{
rows2Delete.Add(dr);
}
}
foreach (DataRow row in rows2Delete)
{
DataRow[] drRowsToCheck =emails.Select("itemID ='" + row["itemID"].ToString() +"'");
foreach (DataRow drCheck in drRowsToCheck)
{
emails.Rows.RemovedDrCheck);
emails.AcceptChanges();
}
}
Here is the error message I am getting on the second pass:
This row has been removed from a table and does not have any data.
BeginEdit() will allow creation of new data in this row
How can I do what I need to without throwing errors like that? Is there a better way like using a LiNQ query?
The problem is that when the same itemID has multiple entries with 'fail', you are trying to remove them multiple times.
// 1. Find the Unique itemIDs to remove
var idsToRemove = emails.Select("failEmail = 'fail'").Select (x => x["itemID"]).Distinct();
// 2. Find all the rows that match the itemIDs found
var rowsToRemove = emails.Select(string.Format("itemID in ({0})", string.Join(", ", idsToRemove)));
// 3. Remove the found rows.
foreach(var rowToRemove in rowsToRemove)
{
emails.Rows.Remove(rowToRemove);
}
emails.AcceptChanges();
this is what I ended up doing, based on an answer I got from MSDN c# Forums:
create an extension on DataTable to enable LINQ euering of the Datatable:
public static class DataTableExtensions
{
public static IEnumerable<DataRow> RowsAsEnumerable ( this DataTable source )
{
return (source != null) ? source.Rows.OfType<DataRow>() : Enumerable.Empty<DataRow>();
}
}
then modified my code as below:
//Get IDs to delete
var deleteIds = from r in emails.RowsAsEnumerable()
where String.Compare(r["failEmail"].ToString(), "fail", true) == 0
select r["itemID"];
//Get all rows to delete
var rows2Delete = (from r in emails.RowsAsEnumerable()
where deleteIds.Contains(r["itemID"])
select r).ToList();
//Now delete them
foreach (var row in rows2Delete)
emails.Rows.Remove(row);
emails.AcceptChanges();
and now it works, just wish I could do it the normal way successfully.
foreach (DataRow rowFail in emails.Select("failEmail = 'fail'"))
{
DataRow[] rowsItem = emails.Select(String.Format("itemID = '{0}'", rowFail["itemID"]));
for (int i = rowsItem.Length - 1; i >= 0; i--)
{
rowsItem[i].Delete();
}
}
emails.AcceptChanges();
DataTable.Select returns an array of all DataRow objects that match the filter criteria.

Implement C# Linq Distinct Help in order to group a column in table

private void GetRecDept()
{
cmbDept.Items.Clear();
ListViewMeasurements.Items.Clear();
SFCDataContext SFC = new SFCDataContext();
try
{
var query = (from i in SFC.Systems_SettingsMeasurements
orderby i.RowID descending
select new {
RowID = i.RowID,
Measure = i.Measurement,
Target = i.TargetPercentage,
Dept = i.Department_ID
});
foreach (var w in query)
{
ListViewItem List = new ListViewItem(w.RowID.ToString());
List.SubItems.Add(w.Measure);
List.SubItems.Add(string.Format("{0:n2}", w.Target));
List.SubItems.Add(w.Dept);
ListViewMeasurements.Items.AddRange(new ListViewItem[] { List });
}
foreach (var r in query)
{
cmbDept.Items.Add(r.Dept.Distinct());
}
}
catch (Exception ex)
{
MessageBox.Show(ex.Message.ToString());
}
}
Its output on the cmbDept.Items.Add() is showing this kind of string
System.Linq.Enumerable+<DistinctIterator>d__7a1[System.Char]` unlike
in the listview how can i convert it to a string type readable to
users it's in a combo box.
Try this:
var distinctDept = query.Select(x=>x.Dept).Distinct().ToArray();
cmbDept.Items.AddRange(distinctDept)
About your issue, the Dept is string, when you use cmbDept.Items.Add(r.Dept.Distinct()); the added item is an IEnumerable<char> and of course it can't display that as you expected, the ToString() is called before displaying the IEnumerable<char> and gives you the result as you described.

How can I access the result of dynamic linq with dynamic key/value pairs of IEnumerable?

I have a Dynamic Linq query, through which am trying to access grouped data, grouped on two columns, and 1 columns on which aggregation function is used.
var gFieldList = new List<string>() { "Supplier", "Country" };
var sFieldList = new List<string>() { "Sales"};
var gField = string.Join(", ", gFieldList.Select(x => "it[\"" + x + "\"] as " + x));
var sField = string.Join(", ", sFieldList.Select(y => "Sum(Convert.ToDouble(it[\""+y+"\"])) as "+y));
var dQueryResult = dataTable
.AsEnumerable()
.AsQueryable()
.GroupBy("new("+gField+")", "it")
.Select("new("+sField+",it.Key as Key, it as Data)");
To access the data from the dQueryResult am using the following code.
var groupedData = (from dynamic dat in dQueryResult select dat).ToList();
foreach (var group in groupedData)
{
var key = group.Key;
var sum = group.Sales;
MessageBox.Show("Supplier : " + key.Supplier + "Country : " + key.Country + " SALES : "+Sale.ToString());
}
The problem is
var gFieldList = new List<string>() { "Supplier", "Country" };
var sFieldList = new List<string>() { "Sales"};
keeps on changing. They both need to be dynamic, which will not allow me to access the values dynamically from the above mentioned code since for now it's been coded as key.Supplier, key.Country and group.Sales .
How can I make the iteration of the result of the dynamic query as dynamic?
I tried using
var groupedData = (from dynamic dat in dQueryResult select dat).ToList();
foreach (var group in groupedData)
{
var key = group.Key;
var sum = group.Sales;
foreach (var v in gFieldList)
{
MessageBox.Show(key.v);
}
}
But it's throwing an exception stating 'DynamicClass1' does not contain a definition for 'v'
you can use reflection like this
foreach (var v in gFieldList)
{
MessageBox.Show(key.GetType().GetProperty(v).GetValue(key,null));
...

Categories