Write this into one linq query? - c#

This has its root into another question I asked, that solved a part of this problem -
Convert this code to LINQ?
Now I am trying to write the entire logic in the following manner -
var divisions = (
from DataRow row in results.Rows
let section = row["SECTION"].ToString()
orderby section ascending
select section).Distinct();
string result = String.Empty;
foreach (string div in divisions)
{
result = String.Concat(result, div, Environment.NewLine);
var query =
from DataRow row in results.Rows
let remarks = row["REMARKS"].ToString()
let exam = row["EXAM_NAME"].ToString()
let rollno = row["ROLL_NO"].ToString()
let section = row["SECTION"].ToString()
where (remarks == "Passes" || remarks == "Promoted") &&
exam == "TOTAL" && section == div
orderby rollno
select rollno;
result = String.Concat(result,string.Join(" ", query.ToArray()),
Environment.NewLine);
}
Basically, the original datatable has a bunch of rows with various information including Division. I want to create a single string, for which every division appears on a new line, and below that the roll nos for that division are shown in comma separated fashion. Next division on next line, and so on. (here Section and division are interoperable terms).
Is there any elegant way to write this with one linq query, instead of having to loop through the results of the first query?
EDIT:
Data (not mentioning the other columns that are used in filter conditions)
Roll_no Section.. other cols
001 A
002 A
001 B
003 A
004 B
006 B
This is what the output will look like - (roll no is unique only within a division, but that should not affect the logic in any way)
A
001 002 003
B
001 004 006
This will be like 'A\r\n001 002 003\r\nB\r\n001 004 006' when the string is in raw format.
Note, the above code works. I am just looking for a better approach.

There are two separate requirements you want to have implemented, and you should not try to merge them into a single thing. You 1. want to group the results togetter and 2. have specific needs for presentation.
Here is how you can do this:
var query =
from DataRow row in results.Rows
// here the query stuff you already had
select new { rollno, section, exam, remarks };
// 1. Grouping
var groups =
from item in query
group item by item.section into g
select new
{
Section = g.Key,
Rollnos = g.Select(i => i.rollno).ToArray(),
};
// 2. Presentation
foreach (var group in groups)
{
Console.WriteLine(group.Section);
Console.WriteLine(string.Join(" ", group.Rollno));
}
It is possible to write one single query that also does part of the presentation for you, but this query would become very nasty and unreadable.

Related

Add a Lambda in a LINQ query to replace a line in a foreach

I have a situation where I have to match up multiple customers numbers from one system with a single customer number in another system.
So for instance customer number 225, 228 and 223 in system A will all map to customer number 110022 in system B.
Easy enough, I have a matrix setup to do that.
I pull the matrix data in like this:
 var dt_th_matrix = (from m in aDb.Matrix_Datatrac_TopHat select m).ToArray();
So the records would be something like:
customerA: 3 CustomerB: 1001
CustomerA: 4 CustomerB: 1001
CustomerA: 5 Customer: 1002
Then I do a big data pull and step through all the items. For each of the items I go grab the matching customer number from the matrix like this:
foreach (var dt_stop in mainPull)
{
int? th_customerId = (from d in dt_th_matrix
where d.datatrac_customer_no == dt_stop.Customer_No.ToString()
select d.tophat_customer_detail_Id).First();
What I would rather do is to just embed the code to grab the customer numbrer from the matrix directly in my datapull -- the part "Query goes here somehow" will be some type of Lambda I assume. Any help?
I have tried something like this:
th_customerId = (dt_th_matrix.First().tophat_customer_detail_Id.Equals c.Customer_No)
But that is not it (obviously)
var mainPull = (from c in cDb.DistributionStopInformations
join rh in cDb.DistributionRouteHeaders on c.Route_Code equals rh.Route_Code
where c.Company_No == 1 &&
(accountNumbers.Contains(c.Customer_No)) &&
(brancheSearchList.Contains(c.Branch_Id) && brancheSearchList.Contains(rh.Branch_Id)) &&
c.Shipment_Type == "D" &&
(c.Datetime_Created > dateToSearch || c.Datetime_Updated > dateToSearch) &&
rh.Company_No == 1 &&
((rh.Route_Date == routeDateToSearch && c.Route_Date == routeDateToSearch) ||
(rh.Route_Date == routeDateToSearch.AddDays(1) && c.Route_Date == routeDateToSearch.AddDays(1)))
orderby c.Unique_Id_No
select new
{
c.Datetime_Updated,
th_customerId = ("Query goes here somehow")
c.Datetime_Created,
c.Unique_Id_No,
c.Original_Unique_Id_No,
c.Unique_Id_Of_New_Stop,
c.Branch_Id,
c.Route_Date,
c.Route_Code,
c.Sequence_Code,
c.Customer_No,
c.Customer_Reference,
c.Shipment_Type,
c.Stop_Name,
c.Stop_Address,
c.Stop_City,
c.Stop_State,
c.Stop_Zip_Postal_Code,
c.Stop_Phone_No,
c.Stop_Arrival_Time,
c.Stop_Departure_Time,
c.Address_Point,
c.Stop_Special_Instruction1,
c.Stop_Special_Instruction2,
c.Stop_Expected_Pieces,
c.Stop_Expected_Weight,
c.Stop_Signature,
c.Actual_Arrival_Time,
c.Actual_Depart_Time,
c.Actual_Service_Date,
c.Stop_Actual_Pieces,
c.Stop_Exception_Code,
c.Created_By,
rh_Route_Date = rh.Route_Date,
routeHeaderRouteCode = rh.Route_Code,
rh.Actual_Driver,
rh.Assigned_Driver,
rh_routeDate = rh.Route_Date
}).ToArray();
I will try and clarify the above.
What I need is for the Linq query to say :
For each record that I pull I will goto the Array named dt_th_matrix and get the record that matches for this line and use it.
The data in the matrix looks exactly like this:
Record 1: datatrac_customer_no: 227, tophat_customer_detail_Id 1
Record 2: datatrac_customer_no: 228, tophat_customer_detail_Id: 1
Record 3: datatrac_customer_no: 910, tophat_customer_detail_Id: 5
Then for the first record pulled in the mainPull the field c.customer_no == 228 so I need the query in the select new statement to replace th_customerId with 1 (from Record 2 in the Matrix.
Then say the next record pulled in the mainPull the field c.customer_no = 910 the th_customerId would be 5.
That is what the first line of my foreach statement is currently doing. I want to move that logic to inside my LINQ query.
If I understand you correctly, using a dictionary with a key of datatrac_customer_no and a value of tophat_customer_detail_Id would be a good idea here:
var dt_th_matrix = (from m in aDb.Matrix_Datatrac_TopHat select m).ToDictionary(m=>m.datatrac_customer_no,m=>m.tophat_customer_detail_Id);
With this you should be able to replace your "Query goes here somehow" with
dt_th_matrix[c.Customer_No]
Using LINQ would be possible as well, but I don't think it's worth the performance overhead and reduction in readibility.
If you still want to use LINQ for this with your original matrix, this should work as your query:
dt_th_matrix.Single(m => m.datatrac_customer_no == c.Customer_No).tophat_customer_detail_Id
Both expressions will throw an exception if the key is not found or exists multiple times - but if I understand your structure correctly this should not be possible. Otherwise you need to check for this.

mvc Groupby and Orderby nested

mvc beginner
I have a table of lots that contain a property Num_of_steps representing the number of completed steps toward building a house.
I currently use this to retrieve the lot information and am sorting by the lot number.
var ViewModel = new Sub_lot_VM();
ViewModel.Subdivisions = db.Subdivisions
.Include(i => i.Lots)
.ToList();
if (ViewModel.Subdivisions !=null) // if data sort by lot number
{
foreach (var item in ViewModel.Subdivisions)
item.Lots = item.Lots.OrderBy(i => i.LotName).ToList();
}
return View(ViewModel);
}
Now I want to display this information a 3 groups:
first where the count is between 1 and 114 (active),
second where the count is above 115 (or GTE 115?) (finished)( and then orderby lot name) and
third group is count = 0 (not started) also order by lotname.
I've been trying to think of how to add .where and .groupby lambda expressions to my method without luck. Such as.where(I=>i.Lot.Num_of_steps=0).
I also see that I needed a foreach where some LINQ examples did not need the foreach. Still confused on that.
Get the lots first and then use groupby with ranges to get the groups
from x in
(
db.Subdivisions.SelectMany(sd => sd.Lots)
)
group x by x.Num_of_steps == 0 ? 3 : x.Num_of_steps < 115 ? 1 : 2 into g
orderby g.Key
select g.OrderBy(g1 => g1.LotName)
You can give the groups meaningful names in stead of 1, 2 and 3, but you can also postpone that until it's display time. The numbers facilitate correct sorting.

Duplicate values using linq

I have the Input format in following way
S.no Name wages
1 Tom $200
1 Tom $300
1 Tom $400
2 Rob $500
2 Rob $600
Result set should be in the following way
Name OriginalWage DuplicateWage
Tom $200 $300
Tom $200 $400
Rob $500 $600
I should leave the first record and take the duplicate records into count .Here the original wages is the wage of the first distinct record.
How can i accomplish the result set using linq.
This is the one i tried so far
//Get duplicate values
Dim Duplicates = wageRecordList.GroupBy(Function(w) w.serialnumber).Where(Function(d) d.Count > 1)
//load duplicates to a list
lstDuplicateRecords=Duplicates
//Read list--This one is a hard coded sort of thing and works only for one set of duplicate values
lstResult = (From duplicateRecords In lstDuplicateRecords.Skip(1) Select serialnumber= duplicateRecords.serialnumber, Name= duplicateRecords.Name, OriginalWages= CType(lstDuplicateRecords(0).Wages, String), _
DuplicateWages = CType(duplicateRecords.wages, String))
You can make something like this
var groupedUsers = from user in users
group user by user.User into userGroup
select new
{
User = userGroup.Key,
userHobies =
userGroup.Aggregate((a, b) =>
new { User = a.User, Hobby = (a.Hobby + ", " + b.Hobby) }).Hobby
}
;
foreach (var x in groupedUsers)
{
Debug.WriteLine(String.Format("{0} {1}", x.User, x.userHobies));
}
code is not mine and has been taken from: Use LINQ to concatenate multiple rows into single row (CSV property)
this link might be helpful too
EDITED
Sorry, misunderstood you question
something like this can do the trick
var query = from sal in _yourcontext
join salmin in ( from sal1 in _yourcontext
group sal1 by sal1.name into group
select new{
Name = group.Key
MinSal = group.Min(sal1=>sal1.Salary))
}
on sal.Name equals salmin.Name into result
where sal.Salary != salmin.MinSal
select new{ salmin.Name,salmin.MinSal,sal.Salary }
I managed to get the result set you wanted (proved a nice challenge):
It is assuming that the first record it finds for an employee is the original wage however...
var result = from employeeWages in GetEmployees().GroupBy(e => e.Name)
from duplicateWage in employeeWages.Skip(1).Select(e => e.Wage)
select new
{
Name = employeeWages.Key,
OriginalWage = employeeWages.First().Wage,
DuplicateWage = duplicateWage
};
A full LinqPad script is here for testing: http://share.linqpad.net/wgxcns.linq
Example Result

Linq to Sql Chained Intersect

I can't figure out why this isn't intersecting all of the items in the loop, just the last 2. I think it has something to do with IQueryable
var outerquery = db.Employees.Where(x => x.Name = "Smith").Select(x => x.EmployeeID);
foreach(var name in nameList){
var innerQuery = db.Employees.Where(x => x.Name = name).Select(x => x.EmployeeID);
outerquery = outerquery.Intersect(innerQuery);
}
return outerquery.ToList();
EDIT -
A more concrete example. The table has approx 35 million records.
The table has ID, ConceptID, Word. Words can have multiple ConceptIDs & there is 1 word per record. I was to intersect a search string 'shoulder pain chronic' and get all the ConceptIDs that share those 3 words. It should return:
Concept1234 - shoulder
Concept1234 - pain
Concept1234 - chronic
What I am getting (just the last 2):
Concept1234 - pain
Concept1234 - chronic
Doing an OR on 35 million records is rough even with this monster server I have & an intersect is the only way to do it in less than a second.
What I am trying to generate with LINQ to SQL (Entity Framework) is this -
SELECT ConceptID FROM WordTable WHERE Word = 'shoulder'
INTERSECT
SELECT ConceptID FROM WordTable WHERE Word = 'pain'
INTERSECT
SELECT ConceptID FROM WordTable WHERE Word = 'chronic'
You have outerquery inside the foreach loop which gets replaced in each iteration of the loop and you lose previous data.

LINQ: Selecting items from a list (Group By/Select/Sum & Max!)

Just getting my head around Linq and having lots of fun! Can any one aid me with a query for this:
I have a list of data:
Key Value
Aaa 12
AaA 10
AAa 5
BBB 2
Bbb 1
1. I want to group by Key.ToUpper()
2. For every group I need the Max(Value) & Sum(Value)
3. For every group I want to select the entries
There the Value != Max(value)
the final result should be like this:
Key Max Total
AaA 12 27
AAa 12 27
Bbb 2 3
Thanks!
Update, actually I also need the Key from the Maximum entry:
Key Max Total Correct
AaA 12 27 Aaa
AAa 12 27 Aaa
Bbb 2 3 BBB
:)
var results =
from kvp in source
group kvp by kvp.Key.ToUpper() into g
select new
{
Group = g,
Max = g.Max(kvp => kvp.Value),
Total = g.Sum(kvp => kvp.Value)
} into ag
from x in ag.Group //SelectMany
where x.Value != ag.Max
//for the update to the question - note: possibly ambiguous
let correct = ag.Group.Where(y => y.Value == ag.Max).First().Key
select new
{
Key = x.Key,
Max = ag.Max,
Total = ag.Total,
Correct = correct
};
I kinda like the question because of all the little parts (some are rarely used) that are required to make the answer.
Max = g.Max(kvp => kvp.Value),
Total = g.Sum(kvp => kvp.Value)
Performing multiple aggregations on a group is straightforward, yet challenging if you don't know how.
select a into b
This clause takes everything that happened before and starts a new query with the target. Without it, I'd have to start a new query like this:
var A = ... select a
var B = from b in A
It's important to note that the select into clause removes kvp and g from scope.
from b in source
from a in b.A //SelectMany
This "unpacking" of the child collection turns my query about b's into a query about a's. Unlike the default Enumerable.SelectMany overload, it leaves the parent (b) in scope.
where x.Value != ag.Max
Comparing a child's property with a parent's property? Delightful. It's important to remember to break out where anytime you want to filter, even if you just grouped (there is no HAVING).

Categories

Resources