c# Using LiNQ to remove multiple rows in Datatable - c#

This should be simple but i am not able to figure this out. I have a Datatable that has Name, Age where there are rows that have student names, but then multiple rows of special students' Name 'Chris's, and John's. the end result should have a table with all the rows except Chris and John to be combined into one row called Other with the sum of their age.
ie.
NAME | AGE
Tom | 20
John | 15
Peter | 5
John | 2
Tom | 33
Chris | 20
End Result:
NAME | AGE
Tom | 20
Peter | 5
Tom | 33
Other | 37
This is what im doing right now:
var others = table.AsEnumerable().Where(x => x["NAME"].ToString().Equals("Chris") || x["NAME"].ToString().Equals("John"));
var tmpOthers = 0;
foreach (DataRow otherRow in others)
tmpOthers += decimal.Parse(otherRow["AGE"].ToString());
table = table.AsEnumerable().Where(x => !x["NAME"].ToString().Equals("Chris") || !x["NAME"].ToString().Equals("John")).CopyToDataTable();
table.Add(//OTHER-ROW-Here)
This works but i know there must be an easier way to do this in a single LiNQ statement. also i could not put the table.Rows.Remove in the same for loop because of iteration change.

I'd do that using Linq + delegate. Why? A solution with delegate is more elegant and more specific for such of case, because a name may starts with big or small letter. Check below solution:
//delegate to return "Other" for {"Chris","John"} - ignoring case
Func<string, string> GetOtherName = delegate(string s)
{
if(string.Equals(s, "Chris", StringComparison.OrdinalIgnoreCase) ||
string.Equals(s, "John", StringComparison.OrdinalIgnoreCase))
return "Other";
else
return s;
};
//get total age for "Other"
var otherAge = dt.AsEnumerable()
.Where(x=>GetOtherName(x.Field<string>("NAME")) == "Other")
.Select(x=>x.Field<int>("AGE")).Sum();
//remove "Other" rows
dt.AsEnumerable()
.Where(x=>GetOtherName(x.Field<string>("NAME")) == "Other")
.ToList()
.ForEach(r=>r.Delete());
//finally add "other" row
dt.Rows.Add(new object[]{"Other", otherAge});
Result:
NAME AGE
Tom 20
Peter 5
Tom 33
Other 37
For further details, please see: Func Delegate

Well you can use a for loop for that purpose like
for(int i=0; i<others.Count(); i++)
table.Rows.Remove(others[i]);

Related

Return certain record based on criteria (2)

I asked this question previously, but missed a vital part of my problem.
Return certain record based on criteria
Take this list of results
Client | Date | YESorNO
-------------------------------
A1 | 01/01/2001 | NO
A1 | 01/01/2002 | NO
A1 | 01/01/2003 | YES
A1 | 01/01/2004 | NO
A1 | 01/01/2005 | NO
A1 | 01/01/2006 | NO
A1 | 01/01/2007 | YES
A1 | 01/01/2008 | YES
A1 | 01/01/2009 | YES
A2 | 01/01/2001 | NO
A2 | 01/01/2002 | NO
A2 | 01/01/2003 | YES
A2 | 01/01/2004 | NO
A2 | 01/01/2005 | YES
A2 | 01/01/2006 | YES
A3 | 01/01/2001 | NO
...etc...
The list is ordered chronologically and I cannot sort this is any other way other than descending / ascending.
I cannot sort for Yes | NO and find the First() or Last() as this won't give me the required value.
I want to be able to return the first 'YES' after all 'NO's have been accounted for, per Client.
In the above example for Client[A1] row 7 is the record I want returned (on 01/01/2007).
Client[A2] - row 5 (01/01/2005) ..etc
My code is as follows
var query =
(
from m in db.MyTable
where m.Criteria == XYZ
select new
{
Client = m.Client,
Date = m.Date,
YESorNO = m.YESorNO
}
).OrderBy(x => x.Date);
Using .FirstOrDefault(x => x.YesOrNO == "YES") returns the 3rd record.
User #RenéVogt advised that
var result = query.AsEnumerable()
.TakeWhile(x => x.YESorNO == "YES")
.LastOrDefault();
would get the job done and it does, but I forgot to add that the query will be returning many Clients and I need the first 'YES' for each Client, therefore the above code won't suffice.
Iterating over my results would be hugely time consuming and whilst that is a solution I would prefer this logic to be within the database query itself (if possible)
Many thanks
What you have to do is grouping by client,and then find the last YES of each one starting from the end. Something like this (ClientList is a List<>, you may have to change it depending on where is your data):
var query = ClientList.OrderBy(x => x.client).ThenBy(x => x.date).GroupBy(x => x.client);
foreach (var client in query)
{
var lastYES=client.Reverse().TakeWhile(x => x.YESorNO == "YES")
.LastOrDefault();
Console.WriteLine(String.Format("{0} {1}",client.Key,lastYES.date));
}
//Output: A1 01/01/2007 0:00:00
// A2 01/01/2005 0:00:00
Edit
Mansur Anorboev rightly suggested ordering by descending date, thus eliminating the need of Reverse, so the code would be:
var query = ClientList.OrderBy(x => x.client).ThenByDescending(x => x.date).GroupBy(x => x.client);
foreach (var client in query)
{
var lastYES=client.TakeWhile(x => x.YESorNO == "YES")
.LastOrDefault();
Console.WriteLine(String.Format("{0} {1}",client.Key,lastYES.date));
}
Edit 2
I still was not completly happy with my solution, as it is using a foreach. This does everything in one Linq command:
var query = ClientList.OrderBy(x => x.client)
.ThenByDescending(x => x.date)
.GroupBy(x => x.client, (key, g) => g.TakeWhile(x => x.YESorNO == "YES").LastOrDefault())
.ToList();
This returns a list with one element per client and with the correct date.
I can provide a little sql query
;WITH cte AS (
SELECT *, ROW_NUMBER() OVER (ORDER BY Client DESC) AS rn
FROM [dbo].[tblSkaterhaz]
)
,gte AS (
SELECT Client,max(rn) mx FROM cte
WHERE YesOrNo = 'NO'
GROUP BY Client
)
SELECT cte.* FROM gte
INNER JOIN cte on cte.Client = gte.Client and cte.rn = gte.mx + 1
Although it is not the required solution, but it yields the required result. You can create a stored proc and use it in your code.
NOTE: This is tested against the same table (and data) mentioned in question above
I hope this will be helpful for you.

C# LINQ : Dynamically create property and values that includes sum of a value from a grouped query

My Title is slightly confusing so I will explain it here.
This is my sample query and what I wish to achieve:
var results = from row in toSearch.AsEnumerable()
group by row.Field<String>("Somename") into grp
select new
{
GroupMe = grp.Key,
foreach (var k in someDic.Keys){
TODOPROPERTYNAMEFROMk = grp.sum(x => grp.k);
}
};
This is the general idea of what I want to achieve. Based on a dictionary of keys, I want to create property names from it while summing up the data from the grouped query. The sum will be based off the value of the keys.
toSearch is a datatable and all of the keys of the dictionary makes up some of the headers of the datatable. This to me seems impossible and I have searched up a few sources and one of it (I lost the author sorry....) gave me this solution:
//For generating dynamic objects fr
public static object DynamicProjection(object input, IEnumerable<string> properties)
{
var type = input.GetType();
dynamic dObject = new System.Dynamic.ExpandoObject();
var dDict = dObject as IDictionary<string, object>;
foreach (var p in properties)
{
var field = type.GetField(p);
if (field != null)
dDict[p] = field.GetValue(input);
var prop = type.GetProperty(p);
if (prop != null && prop.GetIndexParameters().Length == 0)
dDict[p] = prop.GetValue(input, null);
}
return dObject;
}
I tried to use it like this
var results = from row in toSearch.AsEnumerable()
group by row.Field<String>("Somename") into grp
select grp;
var test = results.Select( x => DynamicProjection(x,someDic.Keys));
This gave me an object which i could not utilise or at least call properties out from. I would really appreciate any help in achieving what i was originally after and sorry for the long post.
Edit
Expected input is a datatable eg:
Fee | Fi | Fo | Fum
cat | ds | 2 | 93
cow | ff | 5 | 120
cat | ds | 9 | 33
cow | jk | 1 | 133
Group by header: Fee
Headers Received: Fo, Fum
Expected output:
cat | 11 | 126
cow | 6 | 253
Winner: Cow , so adopt a cow *Just joking lol
I'm not quite familiar with ExpandoObject (my arguments may wrong), you could work with Dictionary<columnname, sum>.
I would suggest return a Dictionary with the headers and respective sum.
// ex -- Given following header
List<string> headers = new List<string>();
headers.Add("fo");
headers.Add("fum");
DataTable dt; // Assign correct data source.
var results =dt.AsEnumerable()
.GroupBy(g=>g.Field<string>("fee"))
.Select(x=> new
{
x.Key,
dict = headers.ToDictionary(h=>h, h=>x.Sum(s=>s.Field<int>(h)))
})
.ToList();
This query groups on fee column and calculates sum for a given header columns.
Check this Demo
Output:
Key=cat, column fo , Sum 11, column fum , Sum 126
Key=cow, column fo , Sum 6, column fum , Sum 253

NHibernate C# e SQL Server

Good Morning,
I wonder if there's any method within the NHIBERNATE to which I can retrieve the first row of the table?
For example:
Line | ID | Name |Last Name |
1 | 0 | Test | of Information |
2 | 1 | Mauricio | Silva |
If I want the first line or the line 1 of the table
You can use Linq to create queries with nHibernate. There is a method called FirstOrDefault() which takes only the first record. If the query return empty, the FirstOrDefault method will return null, so, remember to check the result before using, for sample:
var firstItem = session.Query<Entity>().FirstOrDefault();
if (firstItem != null)
{
string name = firstItem.Name;
// use object
}
NHibernate does support paging, so we can select "any" record using the .Take() and .Skip(). In our case we can do it like this:
var list = session
.QueryOver<Person>()
.Take(1) // this will take just a first record
//.Skip(0) // we can even skip some, to get next page
;
Then the resulting list will contain 1 or none row...
var person = list.FirstOrDefault();
Also, we can never be sure, what order will be used by DB engine, so we should use explicit ORDER BY:
var list = session
.QueryOver<Contact>()
.OrderBy(p => p.ID)
.Asc
.Take(1)
;
Now we can be sure, that the first result will be with ID == 0

Filter DataTable in c#

I have a datatable like:
ID | ID2
--------
1 | 2
1 | 3
12 | 2
15 | 3
I need to filter data table with ID2(I know two ID2 values in this case 2 and 3) in such a way that i should get output as
ID | ID2
--------
1 | 2
1 | 3
That means ID which have two ID2(2 and 3) should be selected.
dt.Select("ID2=2 AND ID2=3"); is not working in this case.
Thanks
It is not clear what are you searching for.
If you want to extract all the rows in which the same value for the ID column appears two or more times then
DataRow[] rows = dt.Select("Count(ID) > 1)")
You were right, this doesn't work on a datatable without relationships.
I have found a solution usig Linq,
// Returns an IGrouping<int,DataRow> for every ID that appears more than one time
var result = dt.AsEnumerable().GroupBy(z => z.Field<int>("ID"))
.Select(q=> new{Count = q.Count(), ID=q.Key})
.Where(x => x.Count > 1);
// Now we could extract the rows in the usual way
foreach(var l in result)
{
DataRow[] r = dt.Select("ID=" + l.ID);
r.Dump();
}
I don't know how efficient is this, but at least this seems to work
You can use LINQ to DataSet
var rows = table.AsEnumerable().Where(r => r.Field<int>("ID") == 1);
BTW according to desired result you should filter by ID.

Slow Performance of Linq Where statement

I have a List of Objects (roughly 100k) that is must iterate upon in order to produce a Dictionary.
however the code is performing very slowly, specifically on one line
public class Item{
public int ID;
public int Secondary_ID;
public string Text;
public int Number;
}
Data Looks something like (100k lines)
ID | Secondary_ID | Text | Number
1 | 1 | "something" | 3
1 | 1 | "something else"| 7
1 | 1 | "something1" | 4
1 | 2 | "something2" | 344
2 | 3 | "something3" | 74
2 | 3 | "something4" | 1
and i would like it to look like this when finished. (any collection will do to be honest)
Dictionary<int, string>
Key | Value
(secondary_ID) | (Text : Number)
1 | "Something : 3, Something else : 7, Something1 : 4"
2 | "Something2 : 344"
3 | "Something3 : 74, Something4 : 1"
My code currently works like this ListAll contains all data.
var Final=new Dictionary<int, string>();
var id1s=ListAll.Select(x => x.ID).Distinct().ToList();
foreach(var id1 in id1s) {
var shortList=ListAll.Where(x => x.ID==id1).ToList(); //99% of time spent is here
var id2s=shortList.Select(x => x.Secondary_ID).Distinct().ToList();
foreach(var id2 in id2s) {
var s=new StringBuilder();
var items=shortList.Where(x => x.Secondary_ID==id2).ToList();
foreach(var i in items) {
s.Append(String.Format("{0} : {1}", i.Text, i.Number));
}
Final.Add(id2, s.ToString());
}
}
return Final;
now the output is correct however as stated in the above comment, this takes an incredibly long time to process (90 seconds - certainly more than i am comfortable with) and was wondering if there is a faster way of achieving this.
This code is only going to be used once so is not really a normal usage and normally I would ignore it for that reason, but was wondering for learning purposes.
Here's what I would do (untested, but hopefully you get the idea):
var final = ListAll.GroupBy(x => x.Secondary_ID)
.ToDictionary(x => x.Key, x => String.Join(", ",
x.Select(y => String.Format("{0} : {1}",
y.Text, y.Number)))
This first groups by Secondary_ID using GroupBy, then puts the result into a dictionary using ToDictionary.
The GroupBy will group your data into the following groups:
Key = 1:
ID | Secondary_ID | Text | Number
1 | 1 | "something" | 3
1 | 1 | "something else"| 7
1 | 1 | "something1" | 4
Key = 2:
ID | Secondary_ID | Text | Number
1 | 2 | "something2" | 344
Key = 3:
ID | Secondary_ID | Text | Number
2 | 3 | "something3" | 74
2 | 3 | "something4" | 1
Then the .ToDictionary method:
Selects the key as x.Key (the key we grouped on, i.e. Secondary_ID).
Selects the result of a String.Join operation as the value. What is being joined is the collection of "Text : Number" from the elements inside that group - x.Select(y => String.Format("{0} : {1}", y.Text, y.Number).
A much more efficient (and even easier to write) method of grouping the items by ID is to use GroupBy.
var query = ListAll.GroupBy(x => x.Secondary_ID)
.ToDictionary(group => group.Key,
group => string.Join(", ",
group.Select(item => string.Format("{0} : {1}",item.Text , item.Number))),
//consider refactoring part of this line out to another method
});
As for the reason that your code is so slow, you're searching through the entire list for each distinct ID. That's an O(n^2) operation. GroupBy doesn't do that. It uses a hash based structure internally, based on whatever you're grouping on, so that it can quickly (in O(1) time) find the bucket that any given item belongs in, as opposed to the O(n) time it takes your method.
First, remove everywhere ToList(), it should becomes faster; because ToList() performs eager evaluation.
I think what your code expects to do is:
var Final=new Dictionary<int, string>();
foreach(var x in ListAll)
if(Final.ContainsKey(x.Secondary_ID))
Final[x.Secondary_ID]+=String.Format(", {0} : {1}", x.Text, x.Number);
else
Final.Add(x.Secondary_ID, String.Format("{0} : {1}", x.Text, x.Number));
return Final;
A Dictionary cannot contain a duplicate key, so it's no matter here you use ID or Secondary_ID, if your Secondary_ID must be in the range of existing ID; and you even do not need Distinct() in the code.
By doing some simplification, original code would be:
foreach(var id1 in ListAll.Select(x => x.ID).Distinct()) {
foreach(var id2 in ListAll.Where(x => x.ID==id1).Select(x => x.Secondary_ID).Distinct()) {
var s=new StringBuilder();
foreach(var i in ListAll.Where(x => x.ID==id1).Where(x => x.Secondary_ID==id2)) {
s.Append(String.Format("{0} : {1}", i.Text, i.Number));
}
Final.Add(id2, s.ToString());
}
}

Categories

Resources