Recursive/Hierarchical Query in LINQ - c#

Scenario is,
im using EntityFrame Work 6. I have a DB have table having structure below.
cat_id geo_id parent_id geo_name
Root 1 NULL Pakistan
Province 2 1 Punjab
District 3 2 Attock
City 4 3 Attock
City 5 3 Fateh Jang
City 6 3 Hasan Abdal
Table have hierarchical data in relational form,as you can see.
I want to traverse this hierarchy, want to specific parent level, If im at geo_id 6 then I want to go parent_id 3 and get value Attock or want to go to parent_id 2 and want to get value Punjab.
Moral of the story is,standing at any child,want traverse till specified parent or grand parent,not entire hierarchy. Below is code I have tried but it gives me only its immediate parent.
More Shortly, Want a LINQ query,which will return name of specified parent or grand parent,For Example. I can ask my query, "hey! im Hasan Abdal(City),tell me my Province"
Province = (from cp in db.geo_hierarchy
join mp in db.geo_hierarchy on cp.parent_id equals mp.geo_id
where cp.geo_name == risk.geo_hierarchy.geo_name
select mp.geo_name).FirstOrDefault()
see full code below,it used in inside LINQ query's select clause
Risklists = (from risk in db.risk_cat_detail
where risk.occurance_date.Value.Year==2014 && risk.occurance_date.Value.Month>=6 && risk.occurance_date.Value.Month<=9
select new risk_cat_detail_contract()
{
cat_id = risk.cat_id,
catdesc = risk.category_main.cat_name,
risk_cat_detail_id = risk.risk_cat_detail_id,
trans_date = risk.trans_date.Value,
occurance_date = risk.occurance_date.Value,
occurance_time = risk.occurance_time,
geo_id = risk.geo_id,
geo_desc = risk.geo_hierarchy.geo_name,
Province = (from cp in db.geo_hierarchy
join mp in db.geo_hierarchy on cp.parent_id equals mp.geo_id
where cp.geo_name == risk.geo_hierarchy.geo_name
select mp.geo_name).FirstOrDefault()
}).ToList<risk_cat_detail_contract>();
Help me out,Thanks in Advance

Try this:
var geo_hierarchy = new []
{
new { cat_id = "Root", geo_id = 1, parent_id = (int?)null, geo_name = "Pakistan", },
new { cat_id = "Province", geo_id = 2, parent_id = (int?)1, geo_name = "Punjab", },
new { cat_id = "District", geo_id = 3, parent_id = (int?)2, geo_name = "Attock", },
new { cat_id = "City", geo_id = 4, parent_id = (int?)3, geo_name = "Attock", },
new { cat_id = "City", geo_id = 5, parent_id = (int?)3, geo_name = "Fateh Jang", },
new { cat_id = "City", geo_id = 6, parent_id = (int?)3, geo_name = "Hasan Abdal", },
};
var map = geo_hierarchy.ToDictionary(x => x.geo_id);
Func<int, string, string> up = null;
up = (geo_id, cat_id) =>
{
var record = map[geo_id];
return
record.cat_id == cat_id
? record.geo_name
: (record.parent_id.HasValue
? up(record.parent_id.Value, cat_id)
: null);
};
var result = up(5, "Province"); // -> "Punjab"

Related

Calculate 2 table where condition - Linq

i have 2 table ,
produk table
id produk batch qty
1 AAA ADADAD 2
2 BBB ADADAD 2
3 BBB AAAAAA 2
...............
and move table,
id produk batch qty
1 BBB ADADAD 1
and i want showing table after qty from stok table minus qty from move table, what i want table
PRODUK BATCH QTY
AAA ADADAD 2
BBB ADADAD 1
BBB AAAAAA 2
and this my query
var obj = _db.produk
.Groupby(a=> new {a.code,a.batch})
.Select(a=> new {
produk = a.key.code,
batch = a.Key.batch,
qty = _db.move.Where(c => a.Any(p => p.code == a.code && p.batch == a.batch)).Sum(a=>a.qty)
}).tolist();
but not working
You have to do LEFT JOIN to grouped move table.
var moves =
from m in _db.move
group m by { m.code, m.batch } into g
select
{
g.Key.code,
g.Key.batch,
qty = g.Sum(x => x.qty)
};
var query =
from p in _db.produk
join m in moves on new { p.code, p.batch } equals { m.code, m.batch } into j
from m in j.DefaultIfEmpty()
select new
{
produk = p.code,
batch = p.batch.
qty = p.qty - (int?)m.qty ?? 0
};
If you prefer method syntax over query syntax then you can write your query as this:
var availableItems = repository
.GroupJoin(purchases,
stock => new { stock.Product, stock.Batch },
move => new { move.Product, move.Batch },
(stock, moves) => new { Stock = stock, Moves = moves })
.SelectMany(
stockAndRelatedMoves => stockAndRelatedMoves.Moves.DefaultIfEmpty(),
(stockAndRelatedMoves, relatedMove) => new
{
stockAndRelatedMoves.Stock.Product,
stockAndRelatedMoves.Stock.Batch,
Quantity = stockAndRelatedMoves.Stock.Quantity - (relatedMove?.Quantity ?? 0)
})
.ToList();
As you can see instead of GroupBy you need to use GroupJoin and instead of simple Select you need SelectMany to retrieve items from the joined records.
Some explanation:
stock => new { stock.Product, stock.Batch }: Anonymous type is used here because multiple fields are used in the join
stockAndRelatedMoves => stockAndRelatedMoves.Moves.DefaultIfEmpty(): it is needed because of left outer join
(relatedMove?.Quantity ?? 0): relatedMove can be null that's why we substitute it with 0
In the above code I've used the following collections:
var repository = new List<Stock>
{
new Stock { Id = 1, Product = "AAA", Batch = "ADADAD", Quantity = 2 },
new Stock { Id = 2, Product = "BBB", Batch = "ADADAD", Quantity = 2 },
new Stock { Id = 3, Product = "BBB", Batch = "AAAAAA", Quantity = 2 },
};
var purchases = new List<Move>
{
new Move { Id = 1, Product = "BBB", Batch = "ADADAD", Quantity = 1 }
};
You could also query the produck table, then, in the Select statement, filter the move table based on the produck's batch and produck properties, then calculate the qty.
Code as below:
List<Produk> produks = new List<Produk>()
{
new Produk(){ id = 1, produk= "AAA", batch="ADADAD", qty = 2},
new Produk(){ id = 2, produk= "BBB", batch="ADADAD", qty = 2},
new Produk(){ id = 3, produk= "BBB", batch="AAAAAA", qty = 2},
};
List<Remove> removes = new List<Remove>()
{
new Remove(){ id=1, produk="BBB", batch="ADADAD", qty=1}
};
var result = (from p in produks
select new Produk
{
id = p.id,
produk = p.produk,
batch = p.batch,
qty = p.qty - removes.Where(c => c.produk == p.produk && c.batch == p.batch).Sum(c => c.qty)
}).ToList();
The result like this:

LINQ Query for sum and aggregate data

I have two model classes of which I have provided the sql table structure
CREATE TABLE [DBO].[TBL_PRODUCTION] ( -- class ProductionModel
PRODUCTION_ID INT IDENTITY(1, 1) NOT NULL
,PRODUCTION_NAME NVARCHAR(200) NOT NULL
,PRODUCTION_TYPE INT NOT NULL
,PRODUCTION_QUANTITY INT
,CONSTRAINT PK_PRODUCTION PRIMARY KEY (PRODUCTION_ID)
)
INSERT INTO [DBO].[TBL_PRODUCTION] VALUES ('SGU',1, 100)
INSERT INTO [DBO].[TBL_PRODUCTION] VALUES ('BGU',1, 100)
INSERT INTO [DBO].[TBL_PRODUCTION] VALUES ('CCGU',2, 150)
CREATE TABLE [DBO].[TBL_DISTRIBUTOR] ( class DistributorModel
DISTRIBUTOR_ID INT IDENTITY(1, 1) NOT NULL
,PRODUCTION_ID INT NOT NULL
,QUARTER_TYPE INT NOT NULL
,DEMAND_QUANTITY INT
,CONSTRAINT PK_DISTRIBUTOR PRIMARY KEY (DISTRIBUTOR_ID)
)
INSERT INTO [DBO].[TBL_DISTRIBUTOR] VALUES (1,555,1,25)
INSERT INTO [DBO].[TBL_DISTRIBUTOR] VALUES (1,555,2,25)
INSERT INTO [DBO].[TBL_DISTRIBUTOR] VALUES (1,655,3,25)
INSERT INTO [DBO].[TBL_DISTRIBUTOR] VALUES (2,555,1,25)
INSERT INTO [DBO].[TBL_DISTRIBUTOR] VALUES (2,745,2,25)
INSERT INTO [DBO].[TBL_DISTRIBUTOR] VALUES (3,745,3,25)
INSERT INTO [DBO].[TBL_DISTRIBUTOR] VALUES (1,745,3,10)
INSERT INTO [DBO].[TBL_DISTRIBUTOR] VALUES (2,745,3,50)
I need to show data about total distribution on quarterly basis. I mean how many total productions are distributed in each quarter and the remaining production quantity. I have made the sql query but how to design LINQ query for the same. I have excluded the remaining quantity in SQL Query but need to show in LINQ Query.
SQL Query
SELECT
A.PRODUCTION_ID
,B.PRODUCTION_NAME
,A.QUARTER_TYPE
,B.PRODUCTION_QUANTITY
,SUM(A.DEMAND_QUANTITY) [TOTAL DISTRIBUTED]
FROM [DBO].[TBL_DISTRIBUTOR] A
INNER JOIN [DBO].[TBL_PRODUCTION] B ON A.PRODUCTION_ID = B.PRODUCTION_ID
GROUP BY
A.PRODUCTION_ID
,B.PRODUCTION_NAME
,B.PRODUCTION_QUANTITY
,A.QUARTER_TYPE
ORDER BY A.PRODUCTION_ID
I am just learning LINQ query so do not have idea how to express this in sql query in LINQ. I have the POCO Class too, now how to proceed further.
public class DistributorViewModels
{
public int PRODUCTION_ID { get; set; }
public string PRODUCTION_NAME { get; set; }
public int QUARTER_TYPE { get; set; }
public int PRODUCTION_QUANTITY { get; set; }
public int TOTAL_DISTRIBUTED { get; set; }
public int REMAINING_QUANTITY { get; set; }
}
Please see the attached image for what is my desired output.
Hoping for positive response. Thank You to all!!!
firstly some thoughts:
Does it make sense to group by production quanitity?
Also does it make sense to group by production name if you have already grouped by production id?
Based on those thoughts I would created following linq statements (the beginning is only data from your desired output):
List<DISTRIBUTOR> tbl_distribution = new List<DISTRIBUTOR>(){
new DISTRIBUTOR{PRODUCTION_ID = 1, QUARTER_TYPE = 1, DEMAND_QUANTITY = 15},
new DISTRIBUTOR{PRODUCTION_ID = 1, QUARTER_TYPE = 1, DEMAND_QUANTITY = 10},
new DISTRIBUTOR{PRODUCTION_ID = 1, QUARTER_TYPE = 2, DEMAND_QUANTITY = 25},
new DISTRIBUTOR{PRODUCTION_ID = 1, QUARTER_TYPE = 3, DEMAND_QUANTITY = 35},
new DISTRIBUTOR{PRODUCTION_ID = 2, QUARTER_TYPE = 1, DEMAND_QUANTITY = 25},
new DISTRIBUTOR{PRODUCTION_ID = 2, QUARTER_TYPE = 2, DEMAND_QUANTITY = 25},
new DISTRIBUTOR{PRODUCTION_ID = 2, QUARTER_TYPE = 3, DEMAND_QUANTITY = 50},
new DISTRIBUTOR{PRODUCTION_ID = 3, QUARTER_TYPE = 3, DEMAND_QUANTITY = 25},
};
List<PRODUCTION> tbl_production = new List<PRODUCTION>{
new PRODUCTION{ PRODUCTION_ID = 1, PRODUCTION_NAME = "SGU", PRODUCTION_QUANTITY = 100},
new PRODUCTION{ PRODUCTION_ID = 2, PRODUCTION_NAME = "BGU", PRODUCTION_QUANTITY = 100},
new PRODUCTION{ PRODUCTION_ID = 3, PRODUCTION_NAME = "CCGU", PRODUCTION_QUANTITY = 150},
};
var temp = from a in tbl_distribution
join b in tbl_production on a.PRODUCTION_ID equals b.PRODUCTION_ID
let totalProductDistributed = tbl_distribution.Where(t => t.PRODUCTION_ID == a.PRODUCTION_ID ).Sum(t => t.DEMAND_QUANTITY)
select new {
PRODUCTION_ID = a.PRODUCTION_ID,
PRODUCTION_NAME = b.PRODUCTION_NAME,
QUARTER_TYPE = a.QUARTER_TYPE,
PRODUCTION_QUANTITY = b.PRODUCTION_QUANTITY,
TOTAL_DISTRIBUTED = totalProductDistributed,
REMAINING_QUANTITY = b.PRODUCTION_QUANTITY - totalProductDistributed
};
var ViewModel = from s in temp group s by new{s.PRODUCTION_ID, s.QUARTER_TYPE} into g
select g.FirstOrDefault();
But you can also group by production id and have a table of quarterly infos. That would be just a single linq statement and you can achive your "merged" rows in one column:
var ViewModel2 = from a in tbl_distribution group a by a.PRODUCTION_ID into g
join b in tbl_production on g.FirstOrDefault().PRODUCTION_ID equals b.PRODUCTION_ID
let quarterInfos = from c in g
group c by c.QUARTER_TYPE into d
select new
{
Quarter = d.FirstOrDefault().QUARTER_TYPE,
DemandQuantity = d.Sum(t => t.DEMAND_QUANTITY),
}
select new{
g.Key,
b.PRODUCTION_NAME,
b.PRODUCTION_QUANTITY,
QuarterInfos = quarterInfos,
RemainingQuantity = b.PRODUCTION_QUANTITY - quarterInfos.Sum(i => i.DemandQuantity)
};

Joint 3 tables in linq and return value of nested table with max id

I have there tables as you can see :
line:
id Linename
1 line1
2 line2
3 line3
joint:
id lineId jointname
1 1 joint1
2 2 joint2
3 1 joint3
fitup:
id jointid fitupdate state
1 1 2012/12/12 acc
2 1 2013/12/12 rej
3 2 2015/12/12 acc
4 2 2016/12/12 rej
Result i need:
id Linename jointname fitupdate state
1 line1 joint1 2013/12/12 rej
2 line2 joint2 2016/12/12 rej
The fitup table has a state I need the final state based on max id.
In the fitup table i have multi rows for each joint but i need the date(string) of max id in the result query .
Here is my query:
var q = from j in _ctx.Joints
join l in _ctx.Lines on j.LineId equals l.Id
join spo in _ctx.Spools on j.SpoolId equals spo.Id
join sup in _ctx.Supports on j.SupportId equals sup.Id
join shee in _ctx.Sheets on j.SheetId equals shee.Id
join Fit in _ctx.FitUpDetails on j.Id equals Fit.JointId into g2
from y2 in g2.DefaultIfEmpty()
join weld in _ctx.WeldDetails on j.Id equals weld.JointId into g
from y1 in g.DefaultIfEmpty()
join end in _ctx.Ends on j.EndId equals end.Id
join basemat in _ctx.BaseMaterials on j.BaseMaterialId equals basemat.Id
join TestPack in _ctx.TestPackages on j.TestPackageId equals TestPack.Id
group new { j, l,y2,y1} by new { shee, j, l, spo, sup, y2, y1, end, basemat, TestPack } into grouping
let maxFitById = grouping.Select(item => item.y2)
.Where(item => item != null)
.OrderByDescending(item => item.Id)
let maxweldById = grouping.Select(item => item.y1)
.Where(item => item != null)
.OrderByDescending(item => item.Id)
select new ViewFront()
{
Id = grouping.Key.j.Id,
LineId = grouping.Key.l.LineNumber,
SubmitDateTime = grouping.Key.j.SubmitDateTime,
JointNumber = grouping.Key.j.JointNumber,
BaseMaterialId = grouping.Key.basemat.Name,
FitUpAccept = maxFitById.FirstOrDefault().StateStep1,
FitUpAcceptMain = maxFitById.FirstOrDefault().StateStep2,
JointClass = grouping.Key.j.JointClass,
End = grouping.Key.end.Name,
JointSize = grouping.Key.j.JointSize,
LeftMaterialItemCode = grouping.Key.j.LeftMaterialItemCode,
LeftMaterialLength = grouping.Key.j.LeftMaterialLength.ToString(),
MagneticTest = grouping.Key.j.MagneticTest,
PenetrationTest = grouping.Key.j.PenetrationTest,
PostWeldHeatTreatment = grouping.Key.j.PostWeldHeatTreatment,
RemarkState = grouping.Key.j.RemarkState,
RightMaterialItemCode = grouping.Key.j.RightMaterialItemCode,
RightMaterialLength = grouping.Key.j.RightMaterialLength.ToString(),
RadiographyTest = grouping.Key.j.RadiographyTest,
SheetId = grouping.Key.shee.SheetNumber,
ShopField = grouping.Key.j.ShopField,
SpoolId = grouping.Key.spo.SpoolNumber,
SupportId = grouping.Key.sup.SupportNumber,
TestPackageId = grouping.Key.TestPack.PackageNumber,
THK = grouping.Key.j.THK,
UltrasonicTest = grouping.Key.j.UltrasonicTest,
WeldAccept = maxweldById.FirstOrDefault().StateStep1,
WeldAcceptMain = maxweldById.FirstOrDefault().StateStep2
};
In this query FitUpAccept is the state .
Joint table data
weld :
fitup:
result:
The following code does what you need. Now some explanations:
I kept only the tables relevant for the described output data just to keep it simpler.
When grouping by - If you select the entire objects as you did then you will always get "groups" of a single record - I group the wanted data just by the key - In this case the non aggregated fields. Make sure that you are not grouping by the same fields that you want to actually do aggregation operations by them ( like y2 )
Because it is a left join to the FitUpDetails I must make sure that I remove all the null records and whenever I access a property of that object to make sure it is not null - c# 6.0 syntax of ?..
Now for the By max id part - if I put it into words: "Grouping the data by X, and then for each group ordering it by Y, take first record -> its properties"
So to the code:
var result = (from j in Joints
join l in Lines on j.LineId equals l.Id
join f in FitUpDetails on j.Id equals f.JointId into g2
from y2 in g2.DefaultIfEmpty()
group new { j, l, y2 } by new { j.Id, l.LineName, j.JointName } into grouping
let maxFitById = grouping.Select(item => item.y2)
.Where(item => item != null)
.OrderByDescending(item => item.Id)
.FirstOrDefault()
select new
{
Id = grouping.Key.Id,
LineName = grouping.Key.LineName,
JointName = grouping.Key.JointName,
FitUpdate = maxFitById?.FitUpdate,
State = maxFitById?.State
}).ToList();
Used this for testing it:
List<dynamic> Joints = new List<dynamic>
{
new { Id = 1, LineId = 1, JointName = "joint1" },
new { Id = 2, LineId = 2, JointName = "joint2" },
new { Id = 3, LineId = 1, JointName = "joint3" },
};
List<dynamic> Lines = new List<dynamic>
{
new { Id = 1, LineName = "line1" },
new { Id = 2, LineName = "line2" },
new { Id = 3, LineName = "line3" },
};
List<dynamic> FitUpDetails = new List<dynamic>
{
new { Id = 1, JointId = 1, FitUpdate = new DateTime(2012,12,12), State = "acc" },
new { Id = 2, JointId = 1, FitUpdate = new DateTime(2013,12,12), State = "rej" },
new { Id = 1, JointId = 2, FitUpdate = new DateTime(2015,12,12), State = "acc" },
new { Id = 4, JointId = 2, FitUpdate = new DateTime(2016,12,12), State = "rej" },
};

Complicated Linq to SQL Query

I'm struggling to work out what the Linq-to-SQL syntax is for a particular query. I could do this easily in SQL, but I can't quite get the correct syntax in Linq.
I have parent and child records in two database tables, linked by a foreign key. I want my result to return rows based on these rules:
Return exactly 1 row per parent regardless of how many children exist.
Return null/zero values if the child doesn't exist.
Return related data from a child with a null condition. If more than one exists with a null condition then return just the first one.
Return a count of the number of child records with a non-null condition.
I have been playing around with this in .NET Fiddle for a while and I can't get it right. This is what I have so far (ignore the random descriptions!):
IEnumerable<Parent> parents = new Parent[] {
new Parent { ID = 1, Description = "Apple" },
new Parent { ID = 2, Description = "Orange" },
new Parent { ID = 3, Description = "Pear" },
new Parent { ID = 4, Description = "Banana" } };
IEnumerable<Child> children = new Child[] {
new Child { ID = 1, ParentID = 2, Description = "Mercury", Condition = null },
new Child { ID = 2, ParentID = 3, Description = "Venus", Condition = null },
new Child { ID = 3, ParentID = 3, Description = "Earth", Condition = null },
new Child { ID = 4, ParentID = 4, Description = "Mars", Condition = null },
new Child { ID = 5, ParentID = 4, Description = "Saturn", Condition = "> 5" } };
/// What goes here...?
var query = from p in parents
join c in children on p.ID equals c.ParentID into jc
from subchildren in jc.DefaultIfEmpty()
select new Item {
ParentID = p.ID,
Description = p.Description,
PrimaryChildID = subchildren == null ? 0 : subchildren.ID,
SubDescription = subchildren == null ? null : subchildren.Description,
ConditionalCount = 0};
foreach (var item in query)
Console.WriteLine("{0} {1} {2} {3} {4}",
item.ParentID,
item.PrimaryChildID,
item.Description,
item.SubDescription,
item.ConditionalCount);
The output I get from this is:
1 0 Apple 0
2 1 Orange Mercury 0
3 2 Pear Venus 0
3 3 Pear Earth 0
4 4 Banana Mars 0
4 5 Banana Saturn 0
but I want this:
1 0 Apple 0
2 1 Orange Mercury 0
3 2 Pear Venus 0
4 4 Banana Mars 1
Can anyone help me with the correct syntax for this query?
You don't need a left join in your case, you need a group join instead.
According to MSDN:-
The group join is useful for producing hierarchical data structures.
It pairs each element from the first collection with a set of correlated elements
from the second collection.
Do it like this:-
var query = from p in parents
join c in children
on p.ID equals c.ParentID into g
let firstNullElement = g.FirstOrDefault(x => x.Condition == null)
select new
{
ParentID = p.ID,
PrimaryChildID = firstNullElement != null ? firstNullElement.ID : 0,
Description = p.Description,
SubDescription = firstNullElement!= null ? firstNullElement.Description
: String.Empty,
ConditionalCount = g.Count(x => x.Condition != null)
};
Just to explain it properly, here is what will be generated before we project our actual required data using select new { }, (justifies the definition of Group Join):-
ParentId g
----------------------------------------------
1 null
2 ID = 1, ParentID = 2, Description = "Mercury", Condition = null
3 ID = 2, ParentID = 3, Description = "Venus", Condition = null
ID = 3, ParentID = 3, Description = "Earth", Condition = null
4 ID = 4, ParentID = 4, Description = "Mars", Condition = null
ID = 5, ParentID = 4, Description = "Saturn", Condition = "> 5"
Now, since g is holding an IEnumerable of Child elements we can apply filter, project data, count or do whatever we want, as we did in our final statement using select. And also, as we can see there is no point of data coming from different child element.
Here is the complete Working Fiddle.
This should do the thing
var query = (from p in parents
select new
{
ParentID = p.ID,
Description = p.Description,
PrimaryChildID = children.Where(c => c.ParentID == p.ID && c.Condition == null).Count() == 0 ? 0 : children.OrderBy(c=>c.ID).FirstOrDefault(c => c.ParentID == p.ID && c.Condition == null).ID,
SubDescription = children.Where(c => c.ParentID == p.ID && c.Condition == null).Count() == 0 ? null : children.OrderBy(c => c.ID).FirstOrDefault(c => c.ParentID == p.ID && c.Condition == null).Description,
ConditionalCount = children.Where(c => c.ParentID == p.ID && c.Condition != null).Count()
}).ToList();
This is my variant of query:
var query = from p in parents
join c in children on p.ID equals c.ParentID into jc
from subchildren in jc.DefaultIfEmpty()
select new
{
Parent = p,
Subchildren = subchildren
} into itemData
group itemData by itemData.Parent into g
select new Item
{
ParentID = g.Key.ID,
Description = g.Key.Description,
PrimaryChildID = g.Select(_ => _.Subchildren == null ? 0 : _.Subchildren.ID).FirstOrDefault(),
SubDescription = g.Select(_ => _.Subchildren == null ? null : _.Subchildren.Description).FirstOrDefault(),
ConditionalCount = 0
};

Help with this Linq query (many-to-many join)

I have three domain objects:
Child, Classroom and ChildClassroom. Here are lists of each:
var childrens = new List<Child>() {
new Child() { ChildId = 1, FirstName = "Chris" },
new Child() { ChildId = 2, FirstName = "Jenny" },
new Child() { ChildId = 3, FirstName = "Dave" },
};
var classrooms = new List<Classroom>() {
new Classroom() { ClassroomId = 1, FullName = "Kindergarten" },
new Classroom() { ClassroomId = 2, FullName = "Elementary" },
new Classroom() { ClassroomId = 3, FullName = "Secondary" },
};
var childclassrooms = new List<ChildClassroom>() {
new ChildClassroom() { ClassroomId = 1, ChildId = 1 },
new ChildClassroom() { ClassroomId = 2, ChildId = 1 },
new ChildClassroom() { ClassroomId = 3, ChildId = 2 },
};
What I want is:
var childClassroomRelationships = new object[] {
new {
childid = 1,
classrooms = new object[] {
new { classroomId = 1, occupied = true },
new { classroomId = 2, occupied = true },
new { classroomId = 3, occupied = false }
},
...
};
What's the way to go about this in Linq?
You could do this:
var childClassroomRelationships = (
from child in children
select {
childid = child.ChildId,
classrooms = (
from classroom in classrooms
select new {
classroomId = classroom.ClassroomId,
occupied = childclassrooms.Any(
cc => cc.ChildId == child.ChildId),
// Since you wanted an array.
}).ToArray()
// Since you wanted an array.
}).ToArray();
What's very important here is that a join should not be used here, if it was, you would get inner join semantics, which would cause children who are not in any classrooms to not show up (which it seems you don't want from the example you gave).
Note that this will materialize all sequences because of the calls to ToArray.
Also, it is slightly inefficient, in that to check the occupancy, it has to reiterate the entire childclassroms sequence every time.
This can be improved by "indexing" the childclassrooms map for efficient lookup, like so:
IDictionary<int, HashSet<int>> classroommap = (
from mapping in childclassrooms
group mapping.ClassroomId by mapping.ChildId into g
select g).ToDictionary(g => g.Key, g => new HashSet<int>(g));
This will give you a map of HashSet<int> instances which you can look up the child in once you know the classroom. With that, the first query becomes:
var childClassroomRelationships = (
from child in children
select {
childid = child.ChildId,
classrooms = (
from classroom in classrooms
select new {
classroomId = classroom.ClassroomId,
occupied = classroommap.ContainsKey(child.ChildId) &&
classroommap[child.ChildId].
Contains(classroom.ClassroomId),
// Since you wanted an array.
}).ToArray()
// Since you wanted an array.
}).ToArray();
var kidsInClass = (
from kid in childrens
from c in classrooms
select new {
ChildID = kid.ChildId,
classrooms = (
from cc in childclassrooms
select new {
ClassroomID = c.ClassroomId,
Occupied = cc.ChildId == kid.ChildId
}).ToArray()
}).ToArray();

Categories

Resources