LINQ join tables and count groups - linq

I'm trying to write a linq query that performs this SQL for my Entity Framework connection:
SELECT
l.Site_Name, COUNT(*)
FROM
vw_Subnet s
INNER JOIN
vw_Lab_Space l ON l.Lab_Space_id = s.Lab_Space_Id
WHERE
s.Lab_Space_Id IS NOT NULL
AND s.MM_Space_Id IS NOT NULL
GROUP BY
l.Site_Name
ORDER BY
2 DESC
but I can't figure out the right syntax. Without the WHERE clause I had tried this, but it's not actually doing the GROUP BY part like all the examples I've seen imply it should be:
from l in db.vw_Lab_Space.AsNoTracking()
join s in db.vw_Subnet.AsNoTracking()
on l.Lab_Space_Id equals s.Lab_Space_Id into joined
select new SiteCount {
Site = l.Site_Name,
Count = joined.Count()
};
I'm getting back a SiteCount with repetitive Site values.

You forgot to write the specification about what you want to achieve, but it seems to be the following.
You have a sequence of vwLabSpaces, where every vwLabSpace has a LabSpaceId and a SiteName.
You also have a sequence of vwSubnets where every vwSubnet has a LabSpaceId
You want to join these two sequences on common LabSpaceId. Then you want to group the joined elements into groups of element sthat have the same value for SiteName (so every element in one group has the same SiteName)
The end result should be a list of the common siteNames of each group and the number of elements in each groups.
In baby steps:
// your two sequences:
IQueryable<VwLabSpace> vwLabSpaces = myDbContext.VwLabSpaces;
IQueryable<VwSubNet> vwSubNets = myDbContext.VwSubNext;
// join on common LabSpaceId
var joinedItems = vwLabSpaces.Join(vwSubNets, // join the two sequences
vwLabSpace => vwLabSpace.LabSpaceId, // from every vwLabSpace take the LabSpaceId
vwSubNet => vwSubNet.LabSpaceId, // from every vwSubNet take the LabSpaceId
(vwLabSpace, vwSubNet => new // when they match make a new
{ // joined object containing
VwLabSpace = vwLabSpace, // both matching objects
VwSubNet = vwSubNet,
});
// make groups of joinedItems that have same SiteName
var groupsWithSameSiteName = joinedItems
.GroupBy(joinedItem => joinedItem.VwLabSpace.SiteName);
// finally: get the common SiteName (= Key) and count the elements of each group
var result = groupsWithSameSiteName
.Select(group => new // from every group make one new object
{ // with two properties:
SiteName = group.Key, // the common SiteName of all elements in the group
Count = group.Count(), // and the number of elements in the group
});
TODO: if desired make one big LINQ statement. Because of lazy execution this won't make a difference.
As you only use the SiteName of each joined item, you don't have to combine the complete joining objects:
// join on common LabSpaceId
var joinedItems = vwLabSpaces.Join(vwSubNets, // join the two sequences
vwLabSpace => vwLabSpace.LabSpaceId, // from every vwLabSpace take the LabSpaceId
vwSubNet => vwSubNet.LabSpaceId, // from every vwSubNet take the LabSpaceId
(vwLabSpace, vwSubNet) => // when they match
vwLabSpace.SiteName); // keep the SiteName

Related

Using LINQ to SELECT the SUM() of a subquery

I am trying to learn how to use LINQ to perform a query that yields the same result as this:
SELECT (
SELECT SUM(point)
FROM communitymemberpointfeature
WHERE communitymemberpointfeature.communitymemberid = communitymember.id
) AS points, communitymember.*
FROM communitymember
After browsing around the Internet, I constructed the following statement:
var list = (from pointFeature in communityMemberPointFeatureList
join member in communityMemberList on pointFeature.CommunityMemberId equals member.Id
group pointFeature by new { pointFeature.CommunityMemberId }
into grouping
select new
{
grouping,
points = grouping.Sum(row => row.Point)
}).ToList();
But this yielded a result like
[
{
points:7200,
grouping:[
{Id:1,Point:5000,FeatureId:1,CommunityMemberId:1},
{Id:2,Point:2200,FeatureId:1,CommunityMemberId:1},
],
}
...
]
What I really want is a result set like:
[
{points:7200,CommunityMemberId:1,firstname:'john',lastname:'blah' ....},
...
]
Can someone tell me what I did wrong?
Edit after comment added to the end
I can imagine you have problems translating your SQL into LINQ. When trying to write LINQ statements it is usually a lot easier to start from your requirements, instead of starting from a SQL statement.
It seems to me that you have a table with CommunityMembers. Every CommunityMember has a primary key in property Id.
Furthermore, every CommunityMember has zero or more CommunityMemberPointFeatures, namely those CommunityMemberPointFeatures with a foreign key CommunityMemberId that equals the primary key of the CommunityMember that it belongs to.
For example: CommunityMember [14] has all CommunityMemberPointFeatures that have a value CommunityMemberId equal to 14.
Requirement
If I look at your SQL, it seems to me that you want to query all CommunityMembers, each with the sum of property Point of all CommunityMemberPointFeatures of this CommunityMember.
Whenever you want to query "items with their zero or more subitems", like "Schools with their Students", "Customers with their Orders", "CommunityMembers with their PointFeatures", consider using GroupJoin.
A GroupJoin is in fact a Left Outer Join, followed by a GroupBy to make Groups of the Left item with all its Right items.
var result = dbContext.CommunityMembers // GroupJoin CommunityMembers
.GroupJoin(CommunityMemberPointFeatures, // With CommunityMemberPointFeatures
communityMember => communityMember.Id, // from every CommunityMember take the Id
pointFeature => pointFeature.CommunityMemberId, // from every CommunityMemberPointFeature
// take the CommunityMemberId
// Parameter ResultSelector: take every CommunityMember, with all its matching
// CommunityMemberPointFeatures to make one new object:
(communityMember, pointFeaturesOfThisCommunityMember) => new
{
// Select the communityMember properties that you plan to use:
Id = communityMember.Id,
Name = communityMember.Name,
...
// From the point features of this CommunityMember you only want the sum
// or property Point:
Points = pointFeaturesOfThisCommunityMember
.Select(pointFeature => pointFeature.Point)
.Sum(),
// However, if you want more fields, you can use:
PointFeatures = pointFeaturesOfThisCommunityMember.Select(pointFeature => new
{
Id = pointFeature.Id,
Name = pointFeature.Name,
...
// not needed, you know the value:
// CommunityMemberId = pointFeature.CommunityMemberId,
})
.ToList(),
});
Edit after comment
If you want, you can omit Selecting the values that you plan to use.
// Parameter ResultSelector:
(communityMember, pointFeaturesOfThisCommunityMember) => new
{
CommunityMember = communityMember,
PointFeatures = pointFeaturesOfThisCommunityMember.ToList(),
),
However, I would strongly advise against this. If CommunityMember [14] has a thousand PointFeatures, then every PointFeature will have a foreign key with a value 14. So you are transporting this value 14 1001 times. What a waste of processing power, not to mention all the other fields you plan not to use.
Besides: if you do this you violate against information hiding: whenever your tables changes internally, the result of this function changes. Is that what you want?

Linq orderby and distinct

I have a table set up as follows:
Section SectionOrder
Sect1 1
Sect2 2
Sect3 3
Sect3 3
Sect1 1
Sect2 2
I need to pull out distinct sections in correct section order. Here is the linq that I'm using, but it's not putting them in the correct order for some reason.
var myVar = (from x in context.Documents
orderby x.SectionOrder ascending
select x.Section).Distinct();
I then want to be able to loop through myVar and put each item in a list as follows:
foreach (var t in myVar)
{
listOfDocTypeSections.Add(t);
}
The ordering of OrderBy and Distinct matters: while OrderBy produced an ordered sequence, Distinct doesn't. You need to put Distinct first, and then use OrderBy. However, since you take Distinct on one attribute, and order on the other attribute, you need to do it differently:
var myVar = context
.Documents
.GroupBy(x => x => x.Section) // This replaces Distinct()
.OrderBy(g => g.FirstOrDefault().SectionOrder) // There will be no default
.Select(g => g.Key);
This approach replaces Distinct with GroupBy, and orders on the first SectionOrder item of a group. You can change this sorting strategy to sort on some other item within the Section, say, Min or Max value of SectionOrder:
var myVar = context
.Documents
.GroupBy(x => x => x.Section)
.OrderBy(g => g.Max(x => x.SectionOrder))
.Select(g => g.Key);

dynamic asc desc sort

I am trying to create table headers that sort during a back end call in nhibernate. When clicking the header it sends a string indicating what to sort by (ie "Name", "NameDesc") and sending it to the db call.
The db can get quite large so I also have back end filters and pagination built into reduce the size of the retrieved data and therefore the orderby needs to happen before or at the same time as the filters and skip and take to avoid ordering the smaller data. Here is an example of the QueryOver call:
IList<Event> s =
session.QueryOver<Event>(() => #eventAlias)
.Fetch(#event => #event.FiscalYear).Eager
.JoinQueryOver(() => #eventAlias.FiscalYear, () => fyAlias, JoinType.InnerJoin, Restrictions.On(() => fyAlias.Id).IsIn(_years))
.Where(() => !#eventAlias.IsDeleted);
.OrderBy(() => fyAlias.RefCode).Asc
.ThenBy(() => #eventAlias.Name).Asc
.Skip(numberOfRecordsToSkip)
.Take(numberOfRecordsInPage)
.List();
How can I accomplish this?
One way how to achieve this (one of many, because you can also use some fully-typed filter object etc or some query builder) could be like this draft:
Part one and two:
// I. a reference to our query
var query = session.QueryOver<Event>(() => #eventAlias);
// II. join, filter... whatever needed
query
.Fetch(#event => #event.FiscalYear).Eager
var joinQuery = query
.JoinQueryOver(...)
.Where(() => !#eventAlias.IsDeleted)
...
Part three:
// III. Order BY
// Assume we have a list of strings (passed from a UI client)
// here represented by these two values
var sortBy = new List<string> {"Name", "CodeDesc"};
// first, have a reference for the OrderBuilder
IQueryOverOrderBuilder<Event, Event> order = null;
// iterate the list
foreach (var sortProperty in sortBy)
{
// use Desc or Asc?
var useDesc = sortProperty.EndsWith("Desc");
// Clean the property name
var name = useDesc
? sortProperty.Remove(sortProperty.Length - 4, 4)
: sortProperty;
// Build the ORDER
order = order == null
? query.OrderBy(Projections.Property(name))
: query.ThenBy(Projections.Property(name))
;
// use DESC or ASC
query = useDesc ? order.Desc : order.Asc;
}
Finally the results:
// IV. back to query... call the DB and get the result
IList<Event> s = query
.List<Event>();
This draft is ready to do sorting on top of the root query. You can also extend that to be able to add some order statements to joinQuery (e.g. if the string is "FiscalYear.MonthDesc"). The logic would be similar, but built around the joinQuery (see at the part one)

LINQ: order results of join based on inner collection

Is it possible to order the results of a LINQ join operation based on the inner collection order?
Say I have two collections:
var names = new[]{"John", "Mary", "David"};
var persons= new[]{ new Person{Name="John", Title"Prof"}, new Person{Name="Mary", Title="Accountant"}, new Person{Name="David", Title="Mechanic"}, new Person{Name="Peter", Title="Homeless"}}
if I do a LINQ join to get a subset of persons as follows:
var taxPayers =
persons
.Join(names , p => p.Name, n => n, (p, n) => p)
.Select(f => new KeyValuePair<string, object>(f.Name, f.Title));
The result is ordered based on the persons array.
It is possible using LINQ, to order taxPayers based on the order of names? Or is this not a LINQable operation?
TIA.
Simply reversing the join should work. Instead of joining names to persons, join persons to names.

Max sequence from a view containing multiple record using Linq lambda

I've been at this for a while. I have a data set that has a reoccurring key and a sequence similar to this:
id status sequence
1 open 1
1 processing 2
2 open 1
2 processing 2
2 closed 3
a new row is added for each 'action' that happens, so the various ids can have variable sequences. I need to get the Max sequence number for each id, but I still need to return the complete record.
I want to end up with sequence 2 for id 1, and sequence 3 for id 2.
I can't seem to get this to work without selecting the distinct ids, then looping through the results, ordering the values and then adding the first item to another list, but that's so slow.
var ids = this.ObjectContext.TNTP_FILE_MONITORING.Select(i => i.FILE_EVENT_ID).Distinct();
List<TNTP_FILE_MONITORING> vals = new List<TNTP_FILE_MONITORING>();
foreach (var item in items)
{
vals.Add(this.ObjectContext.TNTP_FILE_MONITORING.Where(mfe => ids.Contains(mfe.FILE_EVENT_ID)).OrderByDescending(mfe => mfe.FILE_EVENT_SEQ).First<TNTP_FILE_MONITORING>());
}
There must be a better way!
Here's what worked for me:
var ts = new[] { new T(1,1), new T(1,2), new T(2,1), new T(2,2), new T(2,3) };
var q =
from t in ts
group t by t.ID into g
let max = g.Max(x => x.Seq)
select g.FirstOrDefault(t1 => t1.Seq == max);
(Just need to apply that to your datatable, but the query stays about the same)
Note that with your current method, because you are iterating over all records, you also get all records from the datastore. By using a query like this, you allow for translation into a query against the datastore, which is not only faster, but also only returns only the results you need (assuming you are using Entity Framework or Linq2SQL).

Resources