Make group by in outter join LINQ to entity - entity-framework

I use Entity Framework-6.
I have this code(outter join, LINQ to entity):
var inspectionSitesConjection = (from st in sites
join ir in inspectionReview on st.Id equals ir.SiteId into g
from subsite in g.DefaultIfEmpty()
select new GeneralReportViewModel
{
siteName = subsite.Site.Name,
address = subsite.Site.Description,
inspectionDate = subsite.DateReview,
siteType = subsite.Site.SiteType.Description,
frequency = subsite.InspectionFrequency.Name,
status = subsite.IsNormal,
}).AsNoTracking();
I need to make group by siteName and frequency.
Is it pussable to make group by inside LINQ above?

Here is a starting point:
var grouped = inspectionSitesConjection
.GroupBy(item => new { item.siteName, item.frequency });
But note that the result (as you may see in Queryable.GroupBy documentation) is no more IQueryable<GeneralReportViewModel> but IQueryable<IGrouping<Key, GeneralReportViewModel>> where Key is anonymous type having siteName and frequency properties.
I'm providing this just because you specifically asked. It's not quite clear what are you trying to achieve with that query. Also, once you decided to use explicit join, then use the joined table instead of navigation property, and take into account that subsite can be null due to outer join.
from st in sites
join ir in inspectionReview on st.Id equals ir.SiteId into g
from subsite in g.DefaultIfEmpty()
select new GeneralReportViewModel
{
siteName = st.Name,
address = st.Description,
siteType = st.SiteType.Description,
inspectionDate = subsite.DateReview, // problem if subsite == null
frequency = subsite.InspectionFrequency.Name, // problem if subsite == null
status = subsite.IsNormal, // problem if subsite == null
})

Related

LINQ to Entities does not recognize the method with Let Statement

I am in the process of converting an application that uses LINQ to SQL over to LINQ to Entities. I use a repository pattern and I have run in a problem that works in LINQ to SQL but not Entities.
In my data layer, I use LINQ statements to fill my object graph so that none of my database entities are exposed anywhere else. In this example, I have a Lookup Respository that returns a list of Categories. It looks like this:
public IQueryable<Entities.DomainModels.Category> getCategories()
{
return (from c in Categories
where !c.inactive
orderby c.categoryName
select new Entities.DomainModels.Category
{
id = c.categoryID,
category = c.categoryName,
inactive = c.inactive
});
}
Later, I want to put the categories into a sub query and it looks like this:
var d = from p in Programs
let categories = (from pc in p.Categories
join c in getCategories() on pc.categoryID equals c.id
select c)
select new
{
id = p.id,
title = p.title
categories = categories.ToList()
};
When I run this, I get the following error:
LINQ to Entities does not recognize the method 'System.Linq.IQueryable`1[Entities.DomainModels.Category] getCategories()' method, and this method cannot be translated into a store expression.
For reference, the following works though it doesn't return the data I need (it's basically a join):
var q = from p in Programs
from pc in p.Categories
join c in getCategories() on pc.categoryID equals c.id
select new
{
id = p.id,
category = c
};
I understand what the error means in concept however LINQ to SQL would make it work. I have this pattern throughout my data layer and I really want to keep it. Should this be working? If not, how can I modify it without mixing my layers.
You cant pass getCategories() to EF.
The query must be destructible to expression tree.
Calculate getCategories() first.
eg
var simpleList = getCategories().Select(id).Tolist;
then use a contains
where(t=> simpleList.Contains(t.CatId) // or the query syntax equivalent

joining a table with more than one field in same table

I try to create a join query in Linq. I want to join a table more than one field with same
table. Please see my code below.
var roles = (from ords in _orderRepository.Table
join customers in _customerRepository.Table on ords.CustomerId equals customers.Id
join ordprvrnts in _orderProductVariantRepository.Table on ords.Id equals ordprvrnts.OrderId
join prdvrnts in _productVariantRepository .Table on ordprvrnts.ProductVariantId equals prdvrnts.Id
**join cstevntrle in _customerEventRoleRepository.Table on
new{ customers.Id equals cstevntrle.CustomerId } &&
new { cstevntrle.EventId == model.Event}**
orderby customers.Email ascending
select new CustomerEventRolesModel
{
Customer = customers.Email,
CUstomerId =customers.Id
});
I try to filter customerEventRoleRepository.Table with CustomerId and EventId
how can i do this in this join query.
Please Help.
you have boolean comparisons in your anonymous type definitions...
change your on clause to the following:
join cstevntrle in _customerEventRoleRepository.Table on
new { CustomerId = customers.Id, EventId = model.Event.EventId } equals
new { CustomerId = cstevntrle.CustomerId, EventId = cstevntrle.EventId }
I don't see "model" defined anywhere, so I'm not sure this is going to work, but it should be enough to demonstrate how joins based on multiple fields works - each anonymous class contains the fields from one "side" of the join.

.Include in following query does not include really

var diaryEntries = (from entry in repository.GetQuery<OnlineDiary.Internal.Model.DiaryEntry>()
.Include("DiaryEntryGradeChangeLog")
.Include("DiaryEntryAction")
join diary in repository.GetQuery<OnlineDiary.Internal.Model.OnlineDiary>()
on entry.DiaryId equals diary.Id
group entry
by diary
into diaryEntriesGroup
select new { Diary = diaryEntriesGroup.Key,
DiaryEntry = diaryEntriesGroup.OrderByDescending(diaryEntry => diaryEntry.DateModified).FirstOrDefault(),
});
This query does not include "DiaryEntryGradeChangeLog" and "DiaryEntryAction" navigation properties, what is wrong in this query?
I have removed join from the query and corrected as per below, and still it populates nothing
var diaryEntries = from entry in repository.GetQuery<OnlineDiary.Internal.Model.DiaryEntry>()
.Include("DiaryEntryGradeChangeLog").Include("DiaryEntryAction")
.Where(e => 1 == 1)
group entry
by entry.OnlineDiary
into diaryEntryGroups
select
new { DiaryEntry = diaryEntryGroups.OrderByDescending(diaryEntry => diaryEntry.DateModified).FirstOrDefault() };
It will not. Include works only if the shape of the query does not change (by design). If you use this query it will work because the shape of the query is still same (OnlineDiary.Internal.Model.DiaryEntry):
var diaryEntries = (from entry in repository.GetQuery<OnlineDiary.Internal.Model.DiaryEntry>()
.Include("DiaryEntryGradeChangeLog")
.Include("DiaryEntryAction");
But once you use manual join, grouping or projection (select new { }) you have changed the shape of the query and all Include calls are skipped.
Edit:
You must use something like this (untested) to get related data:
var diaryEntries = from entry in repository.GetQuery<OnlineDiary.Internal.Model.DiaryEntry>()
group entry by entry.OnlineDiary into diaryEntryGroups
let data = diaryEntryGroups.OrderByDescending(diaryEntry => diaryEntry.DateModified).FirstOrDefault()
select new {
DiaryEntry = data,
GradeChangeLog = data.DiaryEntryGradeChangeLog,
Action = data.DiaryEntryAction
};
or any similar query where you manually populate property for relation in projection to anonymous or unmapped type.

EF Left joining a table on two properties combined with a case statement

I'm trying to write a query for a database that will left join a table to a look up table and the results will be returned based on a case statement.
In normal SQL the query would look like this:
SELECT chis_id, chis_detail, cilt.mhcatID, cilt.mhtID, 'TheFileName' =
CASE
WHEN cilt.mhcatID IS NOT NULL AND cilt.mhtID IS NOT NULL THEN chis_linked_filename
END
FROM chis
LEFT JOIN cilt on cilt.mhcatID = chis.mhcat_id AND cilt.mhtID = chis.mht_id
WHERE cch_id = 50
chis is the table being queried, cilt is a look-up table and does not contain any foreign key relationships to chis as a result (chis has existing FK's to mht and mhcat tables by the mhtID and mhcatID respectively).
The query will be used to return a list of history updates for a record. If the join to the cilt lookup table is successful this means that the caller of the query will have permission to view the filename of any associated files for the history updates.
Whilst during my research I've found various posts on here relating on how to do case statements and left joins in Linq to Entity queries, I've not been able to work out how to join on two different fields. Is this possible?
You need to join on an anonymous type with matching field names like so:
var query = from x in context.Table1
join y in context.Table2
on new { x.Field1, x.Field2 } equals new { y.Field1, y.Field2 }
select {...};
A full working example using the an extra from instead of a join would look something like this:
var query = from chis in context.Chis
from clit in context.Clit
.Where(x => x.mhcatID = chis.mhcat_id)
.Where(x => x.mhtID = chis.mht_id)
.DefaultIfEmpty()
select new
{
chis.id,
chis.detail,
cilt.mhcatID,
cilt.mhtID,
TheFileName = (cilt.mhcatID != null && cilt.mhtID != null) ? chis.linked_filename : null
};
Based on what Aducci suggested, I used a group join and DefaultIsEmpty() to get the results I wanted. For some reason, I couldn't get DefaultIfEmpty() didn't work correctly on its own and the resulting SQL employed an inner join instead of a left.
Here's the final code I used to get the left join working:
var query = (from chis in context.chis
join cilt in context.cilts on new { MHT = chis.mht_id, MHTCAT = chis.mhcat_id } equals new { MHT = cilt.mhtID, MHTCAT = cilt.mhcatID } into tempCilts
from tempCilt in tempCilts.DefaultIfEmpty()
where chis.cch_id == 50
select new {
chisID = chis.chis_id,
detail = chis.chis_detail,
filename = chis.chis_linked_filename,
TheFileName = (tempCilt.mhcatID != null && tempCilt.mhtID != null ? chis.chis_linked_filename : null),
mhtID = chis.mht_id,
mhtcatID = chis.mhcat_id
}).ToList();

Converting T-SQL to Linq

I am using Entitry Framework 4.1 and I am struggling to understand how the convert the below query which uses joins and aggregate methods to a Linq to Entities call in the DomainService.
SELECT tblTime.Period As Timeline, COUNT(tblEngineeringDashboard_ItemList.ID) AS Items
FROM tblEngineeringDashboard_ItemList INNER JOIN
tblTime ON tblEngineeringDashboard_ItemList.TimeID = tblTime.ID
GROUP BY tblTime.Period
ORDER BY tblTime.Period
Can anyone provide help.
Possible Solution
Dim var = From i In ObjectContext.tblEngineeringDashboard_ItemList
Join t In ObjectContext.tblTimes On i.TimeID Equals t.ID
Group By i.TimeID Into Group
Select DateStart = (From n In ObjectContext.tblTimes Where n.ID = TimeID Select n.Period), PartCount = Group.Count
Phil
The first thing which comes to mind is:
var q = from t in Context.Time
group t by t.Period into g
orderby g.Key
select new
{
Timeline = g.Key,
Items = (from ti in g
from il in ti.ItemList // or whatever the property for the navigation to tblEngineeringDashboard_ItemList is called
select il).Count()
};
However, the original SQL had an INNER JOIN, which would reject tblTime records without any matching records in tblEngineeringDashboard_ItemList. So you may want:
var q = from t in Context.Time
where t.ItemList.Any()
group t by t.Period into g
orderby g.Key
select new
{
Timeline = g.Key,
Items = (from ti in g
from il in ti.ItemList // or whatever the property for the navigation to tblEngineeringDashboard_ItemList is called
select il).Count()
};
You can also flip the query around:
var q = from i in Context.EngineeringDashboardItemList
where i.Time != null
group i by i.Time.Period into g
orderby g.Key
select new
{
Timeline = g.Key,
Items = g.Count()
};
Does this work?
tblEngineeringDashboard_ItemList
.Join(tblTime,ed => ed.TimeID ,t => t.ID, (ed,t) => new{ed,t})
.GoupBy(g => g.t.Period)
.Select(s => new
{
Timeline = s.Key,
Items = s.Count()
}
)
.OrderBy(o => o.Timeline)
While converting from sql to linq isn't an ideal approach (you should think directly in linq, translating your need to a linq query), the query you posted is rather simple.
var grouped = tblTime.OrderBy(c => c.Period).GroupBy(c => c.Period).Select(c =>
new {
timeline = c.Key,
count = c.SelectMany(x => x.tblEngineeringDashboard).Count()
});
*Edit: There, fixed. Everything on the L2E engine.
This provided that there are correct foreign keys between the tables (thus you don't have to declare the join manually).