Linq to Entities Left outer join grouped into a collection - c#

from component in Materials.OfType<Container>().Where(m => m.Active)
join segmentFinanceRating in segmentFinanceRatingView on component.Id equals segmentFinanceRating.MaterialId into segmentFinanceRatingGroup
from segmentFinanceRatingWithDefault in segmentFinanceRatingGroup.DefaultIfEmpty()
select new
{
id = component.Id,
name = component.Name,
subType = component.SubType,
size = component.Size,
MaterialIds = component.Materials.Select(x => x.Id),
BrandNames = component.Brands.Select(x => x.Name),
SegmentRatings = segmentFinanceRatingWithDefault
}
I have the above LINQ to Entities query that has a LEFT JOIN to get rating values for 1 or more segments for a given component.
The segmentFinanceRating entity has the properties, { MaterialId, SegmentId, Rating, LowRated }
At the moment the results are not grouped to the relevant component, i.e. the SegmentRatings property is not a single collection of segmentFinanceRating objects, instead I have multiple data rows with 1 segmentFinanceRating object in each.
I have seen some examples of using group x by y into z but I couldn't get it working, possibly due to some of the collections on the component that I need too, I'm not sure.
Any help would be appreciated on how to do this, thanks.

GroupBy in List doesn't work for you?
var list = (from component in Materials.OfType<Container>().Where(m => m.Active)
join segmentFinanceRating in segmentFinanceRatingView on component.Id equals segmentFinanceRating.MaterialId into segmentFinanceRatingGroup
from segmentFinanceRatingWithDefault in segmentFinanceRatingGroup.DefaultIfEmpty()
select new
{
id = component.Id,
name = component.Name,
subType = component.SubType,
size = component.Size,
MaterialIds = component.Materials.Select(x => x.Id),
BrandNames = component.Brands.Select(x => x.Name),
SegmentRatings = segmentFinanceRatingWithDefault
}).ToList().GroupBy(s=> s.SegmentRatings);

In this case it's much easier to do the join in the anonymous type:
from component in Materials.OfType<Container>().Where(m => m.Active)
select new
{
id = component.Id,
name = component.Name,
subType = component.SubType,
size = component.Size,
MaterialIds = component.Materials.Select(x => x.Id),
BrandNames = component.Brands.Select(x => x.Name),
SegmentRatings = (from segmentFinanceRating in segmentFinanceRatingView
where segmentFinanceRating.MaterialId == component.Id
select segmentFinanceRating)
}
You will have an empty collection of SegmentRatings when there are none for a specific component, giving the same effect as outer join.

Related

Distinct method does not work Entity Framework

How do I get randomly similar data
me tried to merge different ones in the first stage but Different function does not work,
var turler= (from x in db.bird_table_ad
join e in db.kus_resim on x.tr_x equals e.kus_tur
where x.aile == item
select new GozlemTurleri
{
id = x.id,
kod = x.kod,
tr_x = x.tr_x,
en_x = x.en_x,
lt_x = x.lt_x,
turfotourl="image_resize.phpad="+e.altDIR+"/"+e.resim+"&yon="+(e.galeri=="fg"?"2":"HD2"),
aile = x.aile,
gfoto = x.gfoto
}).Distinct().ToList();
If you try to get distinct record with regarding to tr_x from database then you can use GroupBy in entity framework.
So your code will be.
.GroupBy(x => x.tr_x).Select(x => x.First()).ToList();
Instead of
.Distinct().ToList();

Linq to SQL left outer join using Lambda syntax and joining on 2 columns (composite join key)

I am trying to make an Inner Join on 2 columns with Linq to SQL as a Lambda expression. The normal query would look like this.
SELECT * FROM participants
LEFT OUTER JOIN prereg_participants ON prereg_participants.barcode = participants.barcode
AND participants.event_id = prereg_participants.event_id
WHERE (participants.event_id = 123)
I am succeeding in making a Left Outer Join on one column with the following code.
var dnrs = context.participants.GroupJoin(
context.prereg_participants,
x => x.barcode,
y => y.barcode,
(x, y) => new { deelnr = x, vi = y })
.SelectMany(
x => x.vi.DefaultIfEmpty(),
(x, y) => new { deelnr = x, vi = y })
.Where(x => x.deelnr.deelnr.event_id == 123)
.ToList();
The problem is that with the above Lambda I get too many results because it is missing the AND participants.event_id = prereg_participants.event_id part. But whatever I try i'm not getting the correct amount of participants.
I looked at the following existing questions, but none solved my problem in writing the correct lambda. And most of the solutions are nog in lambda-format or not a Left outer join on multiple columns.
How to do joins in LINQ on multiple fields in single join
LINQ to SQL - Left Outer Join with multiple join conditions
Group By using more than two columns by Lambda expression
And most of these from this Google search
Query:
var petOwners =
from person in People
join pet in Pets
on new
{
person.Id,
person.Age,
}
equals new
{
pet.Id,
Age = pet.Age * 2, // owner is twice age of pet
}
into pets
from pet in pets.DefaultIfEmpty()
select new PetOwner
{
Person = person,
Pet = pet,
};
Lambda:
var petOwners = People.GroupJoin(
Pets,
person => new { person.Id, person.Age },
pet => new { pet.Id, Age = pet.Age * 2 },
(person, pet) => new
{
Person = person,
Pets = pet,
}).SelectMany(
pet => pet.Pets.DefaultIfEmpty(),
(people, pet) => new
{
people.Person,
Pet = pet,
});
See code, or clone my git repo, and play!
I was able to get this LEFT OUTER JOIN on the composite foreign key pair barcode, event_id working in both Linq2Sql, and Entity Framework, converting to lambda syntax as per this query syntax example.
This works by creating an anonymous projection which is used in match of the left and right hand sides of the join condition:
var dnrs = context.participants.GroupJoin(
context.prereg_participants,
x => new { JoinCol1 = x.barcode, JoinCol2 = x.event_id }, // Left table join key
y => new { JoinCol1 = y.barcode, JoinCol2 = y.event_id }, // Right table join key
...
Notes
This approach relies on the automagic equality given to identical anonymous classes, viz:
Because the Equals and GetHashCode methods on anonymous types are defined in terms of the Equals and GetHashCode methods of the properties, two instances of the same anonymous type are equal only if all their properties are equal.
So for the two projections for the join keys need to be of the same type in order to be equal, the compiler needs to see them as the same anonymous class behind the scenes, i.e.:
The number of joined columns must be the same in both anonymous projections
The field types must be of the same type compatable
If the field names differ, then you will need to alias them (I've used JoinColx)
I've put a sample app up on GitHub here.
Sadly, there's no support yet for value tuples in expression trees, so you'll need to stick to anonymous types in the projections.
If it is a LEFT OUTER JOIN, where the left entity can have zero or maximally one connection with right entity, you can use:
// Let's have enumerables "left" and "right"
// and we want to join both full entities with nulls if there's none on the right.
left.GroupJoin(
right,
l => l.LeftKey,
r => r.RightKey,
(l, r) => new { Left = l, Right = r.FirstOrDefault() });
If you want to join left with just one attribute of right:
// Let's have enumerables "left" and "right"
// and we want to join right's attribute RightId and to set 0 for those having no Id.
left.GroupJoin(
right,
l => l.LeftKey,
r => r.RightKey,
(l, r) => new { Left = l, RightId = r.FirstOrDefault()?.RightId ?? 0 });
You can do this by making use of anonymous types.
Example:
var result = from a in context.participants
join b context.prereg_participants on new { X = a.barcode, Y = a.event_id } equals new { X = b.barcode, Y = b.event_id } into A
from b in A.DefaultIfEmpty()
where a.event_id = 123

Distinct values in linq with join clause

I have this type of code. In Material table it only have one MaterialId. But in Report table it can be multplie MaterialId rows.(One materialId can have multplie rows in Report table).
But in the Material table i do not have any Timestamp. But in Report there are a column as TimeStamp which I want to use to get the latest.
I only want distinct values with the ReportAdminModel as it is right now.
But how do I get it to work like that?
I have tried Distinct() but that does not work.
list = (from material in db.Material
join reports in db.Report on material.MaterialId equals reports.MaterialId
select new ReportAdminModel
{ MaterialId = material.MaterialId, MaterialStatus = material.ProcessApprovalStatus, FlowIndex = material.FlowIndex, ActualStartTime = reports.Timestamp })
.OrderByDescending(x => x.ActualStartTime).Take(item.NumberOfRows.Value).ToList();
If I understood correctly, try this :
list = (from material in db.Material
join reports in (from rep in db.Report
group rep by rep.MaterialId into grp
let latestTimeStamp = grp.Max(o => o.Timestamp)
select new
{
MaterialId = grp.Key,
Timestamp = latestTimeStamp,
//if you need any other field, just do something like :
//SomeField = grp.Where(o => o.Timestamp == latestTimeStamp).Select(o => o.SomeField).FirstOrDefault();
})
on material.MaterialId equals reports.MaterialId
select new ReportAdminModel
{MaterialId = material.MaterialId,
MaterialStatus = material.ProcessApprovalStatus,
FlowIndex = material.FlowIndex,
ActualStartTime = reports.Timestamp,
}).OrderByDescending(x => x.ActualStartTime).ToList();
You could apply group by material.MaterialId, material.ProcessApprovalStatus, material.FlowIndex and retrieve the last Timestamp data for per group item.
list = (from material in db.Material
join reports in db.Report on material.MaterialId equals reports.MaterialId
group new{material,reports} by new { material.MaterialId, material.ProcessApprovalStatus, material.FlowIndex } into materialGrouped
select new ReportAdminModel
{
MaterialId = materialGrouped.Key.MaterialId,
MaterialStatus = materialGrouped.Key.ProcessApprovalStatus,
FlowIndex = materialGrouped.Key.FlowIndex,
ActualStartTime = materialGrouped.Max(x => x.reports.TimeStamp)
})
.OrderByDescending(x => x.ActualStartTime).Take(item.NumberOfRows.Value).ToList();

Linq to SQL join and group

I have two tables in my database:
Town:
userid, buildingid
Building:
buildingid, buildingname
What i want is to populate a GridView like this:
But I don't want the buildings to be shown more than once. Here is my code:
var buildings = dc.Towns
.Where(t => t.userid == userid)
.GroupJoin(dc.Buildings,
t => t.buildingid,
b => b.buildingid,
(Towns, Buildings) => new
{
BuildningName = Buildings.First().buildingname,
Count = Towns.Building.Towns.Count()
});
gvBuildings.DataSource = buildings.ToList();
gvBuildings.DataBind();
New code which works:
var buildings = (from t in dc.Towns
where t.userid == userid
join b in dc.Buildings
on t.buildingid equals b.buildingid
into j1
from j2 in j1.DefaultIfEmpty()
group j2 by j2.buildingname
into grouped
select new
{
buildingname = grouped.Key,
Count = grouped.Count()
});
gvBuildings.DataSource = buildings.ToList();
gvBuildings.DataBind();
var buildings = from t in dc.Towns
join b in dc.Buildings on t.buildingid equals b.buildingid into j1
from j2 in j1.DefaultIfEmpty()
group j2 by b.buildingname into grouped
select new { buildingname = grouped.key, Count = grouped.Count()}
I think this should do it. I have not tested it so it might give error but it will be something like this.
Wouldn't something like this do it?
Users
.Select(User => new {User, User.Building})
.GroupBy(x => x.Building)
.Select(g=> new {Building = g.Key, Count = g.Count()})
According to my experience with Linq to SQL, when the expression is becoming complicated it is better to write a stored procedure and call it with Linq to SQL. In this way you get better maintainability and upgradeability.
Rather than an option to pure SQL, I see “Linqu to SQL” as a tool to get hard typed object representation of SQL data sets. Nothing more.
Hope it helps you.

dynamic linq group by clause

I have multiple linq queries that retrieve the same data just at different grouping levels. (potentially 3 different levels). The linq query currently results in an enumerable list of a custom object. The items I don't understand or wonder if possible (to reduce redundant code):
can I make the following group by clause to be dynamic?
if so, can it dynamically populate my custom object group data when it is grouped at that level.
For instance:
var myReport_GroupProductLevel =
from r in mySum_GroupProductLevel
join pc in _myPlotCount on r.Strata equals pc.Strata
join acr in _myStrataAcres on pc.Strata equals acr.Strata
group new { r, pc, acr } by new { r.Strata, pc.Count, acr.Acres, r.GroupName, r.ProductName } into g
select new DataSummary
{
Strata = g.Key.Strata,
PlotCount = g.Key.Count,
Acres = g.Key.Acres,
ClassName = string.Empty,
GroupName = g.Key.GroupName,
ProductName = g.Key.ProductName,
TPAMEAN = g.Sum(x => x.r.TPA / x.pc.Count),
TPADEV = g.Select(x => x.r.TPA).StdDev(g.Key.Count)
};
If I wanted to group only by "GroupName" instead... I would rewrite the query. Issues I see are, if I'm grouping by a value then I need that value in the query (g.Key.GroupName); but since I'm creating a new custom object the other non-grouped values such as "ClassName" require a value (I used string.Empty above, but that is static).
Thanks for any insight...
if anyone was curious, I got it to work by using a conditional statement... since grouping by empty will make it collapse.
var mySum_ClassGroupProductLevel =
from s in ReportData.myStands
join p in ReportData.myPlots on s.ID equals p.StandID
join t in ReportData.myTrees on p.ID equals t.PlotID
group t by new { s.Strata, p.ID,
ClassName = useClassName ? t.ClassName : string.Empty,
GroupName = useGroupName ? t.GroupName : string.Empty,
ProductName = useProductName ? t.ProductName : string.Empty }
into g
select new
{}

Categories

Resources