Eliminate loop and use Linq instead - c#

Say I have a List as below:
List<R> lstR = GetR();
Now I want a Linq statement to get menus assigned to R, I achieved this by using a loop and then using Linq to get the menus as below:
List<int> ids = new List<int>();
foreach (R r in lstR)
{
ids.Add(r.Id);
}
menu = (from s in db.Menu
where ids.Contains(s.R.Id)
select s.MenuText).Distinct();
Now as far as I know the above is two loop(Linq is using internal loop). would I be able to combine these two statements i.e. not do the first loop to get the ids?

In both lstR and db.Menu are either in-memory data sets (Linq-to-Objects) or IQueryable collections from your database, you can do this:
menu =
(from s in db.Menu
where lstR.Select(r => r.Id)
.Contains(s.R.Id)
select s.MenuText)
.Distinct();
Or this:
menu =
(from s in db.Menu
join r in lstR on s.R.Id equals r.Id
select s.MenuText)
.Distinct();
However, since List<R> exists in memory and db.Menu is an IQueryable, you're options are limited. You could materialize db.Menu into an IEnumerable, so you can process it in memory:
List<R> lstR = GetR();
menu =
(from s in db.Menu.AsEnumerable()
join r in lstR on s.R.Id equals r.Id
select s.MenuText)
.Distinct();
But, this can be costly if there are a lot of records. It's better to do something like this, which admittedly doesn't look much different from what you already have:
List<R> lstR = GetR();
var ids = lstR.Select(r => r.Id).ToList(); // or .ToArray();
menu =
(from s in db.Menu
where ids.Contains(s.R.Id)
select s.MenuText)
.Distinct();
But in truth, the best option is to see if you can refactor GetR so that it returns an IQueryable<R> from your database. That way you can use both of the first two options without needing to materialize any sets into memory first. And by the way, once you've done that and set up navigation properties, you can probably do something like this:
IQueryable<R> lstR = GetR();
menu =
(from r in lstR
from s in r.Menus
select s.MenuText)
.Distinct();

It can be done like.
menu = (from s in db.Menu
where lstR.Select(item => item.Id).Contains(s.R.Id)
select s.MenuText).Distinct();
But i wouldnt combine those two statements, because if you use a HashSet it will speed up:
var ids = new HashSet<int>(lstR);
menu = (from s in db.Menu
where ids.Contains(s.R.Id)
select s.MenuText).Distinct();
This will be faster i guess. The problem with the first one is, every s in db.Menu The list is iterated for creating a list of id's select().

You coud use the linq projection method Select():
ids = lstR.Select(p => p.Id);

menu = db.Menu.Where(s => GetR().Select(r => r.Id).Contains(s.R.Id))
.Select(s => s.MenuText)
.Distinct();
but it will be to complex. It will be better if you'l write like this
var ids = GetR().Select(r => r.Id);
menu = db.Menu.Where(s => ids.Contains(s.R.Id))
.Select(s => s.MenuText)
.Distinct();

Use Join
var result = (from s in db.Menu
join r in lstR on s.Id equals r.ID
select s.MenuText).Distinct();

Related

Converting SQL to LINQ with Multiple Tables and Group

There seem to be lots of questions about SQL to LINQ, but I can't seem to find examples with joined tables and grouping; specifically with a need to get data from multiple tables.
Take this simple SQL:
SELECT
s.showId, s.showName, v.venueName, Min(dateTime) startDate
FROM
shows s
INNER JOIN venues v ON s.venueId = v.venueId
INNER JOIN showDates d ON s.showId = d.showId
GROUP BY
s.showId
The best I can come up with is the following
var ungrouped = (
from s in db.Shows
join v in db.Venues on s.VenueId equals v.VenueId
join d in db.ShowDates on s.ShowId equals d.ShowId
select new { s, v, d }
).ToList();
var grouped = (
from s in ungrouped
group s by s.s.ShowId into grp
select new
{
showId = grp.Key,
name = (from g in grp select g.s.showName).FirstOrDefault(),
venue = (from g in grp select g.v.VenueName).FirstOrDefault(),
startDate = grp.Max(g => g.d.DateTime)
}
);
This works but it feels messy. I don't like:
It being split into two statements
Having to repeatedly write (from g in grp select ...).FirstOrDefault()
Bits like s.s.ShowId
How its vastly more lines of code than the SQL
This example is a simple one, it only gets worse when I have 5+ tables to join and 10+ columns to select.
Question: Is this the best way to do this, and I should just accept it; or is there a better way to write this query?
I am not sure if you are looking for something like this but it's a bit cleaner, it's not split in 2 statements and you might find it helpful. I couldn't use a dbcontext so I used lists to make sure the syntax is correct.
var res = Shows.Join(Venues,
show => show.VenueID,
venue => venue.VenueID,
(show, venue) => new { show, venue })
.Join(ShowDates,
val => val.show.ShowID,
showdate => showdate.ShowID,
(val, showDate) => new { val.show, val.venue, showDates = showDate })
.GroupBy(u => u.show.ShowID)
.Select(grp => new
{
showId = grp.Key,
name = grp.FirstOrDefault()?.show.showName,
venue = grp.FirstOrDefault()?.venue.VenueName,
startDate = grp.Max(g => g.showDates.DateTime)
});
we need to now realation beetwen them one to one or one to many , but not too far from this answer.
var GrouppedResult = Shows.Include(x=>x.Veneu).Include(x=>x.ShowDates)
.Where(x=>x.Veneu.Any()&&x.ShowDates.Any())
.GroupBy(x=>x.ShowId)
.Select(x=>///anything you want);
or
from show in Shows
join veneu in Veneu on veneu.VeneuId equals show.VeneuId
join showDates in ShowDates on showDates.ShowId=show.ShowID
group show by show.Id into grouppedShows
select new { ///what you want };

C# : How to get anonymous type from LINQ result

I need to get NewsImage field and list of categories Ids that associated with the news in Many to Many relationship ... but it gives me error:
The type of one of the expressions in the join clause is incorrect.Type inference failed in the call to 'Join'.
My code looks like this
var Result1 = (from c in db.News
join d in db.Categories
on c.NewsId equals d.News.Select(l => l.NewsId)
where c.NewsId == 1
select new { c.NewsImagePath, d.CategoryId }).ToList();
Assuming you have a navigation property defining the n-n relation I would write:
var result = db.News
.Where(x => x.NewsId == 1)
.SelectMany(x => x.Categories,
(news, category) => new { news.NewsImagePath, category.CategoryId })
.ToList();
The problem is inside the on statement.
on c.NewsId equals d.News.Select( l => l.NewsId )
The Select on the right-hand side will return a IEnumerable of news, which is not what you want.
Something like this would technically work:
on c.NewsId equals d.News.Select( l => l.NewsId ).FirstOrDefault()
But it does not make sense logically.
I suspect the whole query should be built differently. I think you want to join when the category list of news contains the news item. In that case, you can't use the join statement, it would look somewhat like this:
from n in db.News
from c in db.Categories
where c.News.Select( ne => ne.NewsId ).Contains( n.NewsId )
select new { n.NewsImagePath, c.CategoryId }

Linq select with join

I have a basic selection task to be carried out
var query = db.Candidate.Where(.....
but I want the where to be on another table called Tag
in sql it would be
select * from Candidate
join Tag on Tag.candidateId = Candidate.tagId and Tag.tagId = 7
Thus getting all Candidates with tag 7
Im trying to do this directly with a Where, is this possible or do I need to use Linq to SQL.
You can use a Join in LINQ to do that:
var candidates = from candidate in db.Candidates
join on tag in db.Tags
where candidate.CandidateId == tag.TagId
select candidate
Try doing it with .Join as shown below :-
var query = db.Candidate.Join(db.Tags, r => r.tagId , p => p.tagId , (r,p) => new{r.Name});
You didn't show the relations, but it must be possible to do something like:
var query = db.Candidate
.Include(c => c.Tags)
.Where(c => c.Tags.Any(t => t.ID == 7))
Just query the candidates where a candidate has any tag that has the ID you're looking for. This will be written out more or less as the SQL you show.
You can omit the Include() if you have lazy loading enabled.

Group items and select specific item from each group with LINQ

There has got to be a one-liner to do this, and I just can't find it.
Given this query:
from x in new XPQuery<XPContent>(s)
select new { x.Category, x.ContentType, x.Name, x.ContentID, x.Date }
I need to select the record with the greatest date for each distinct ContentID. Can this be done cleverly with LINQ? Right now I'm doing this:
var q = (from x in new XPQuery<XPContent>(s)
select new { x.Category, x.ContentType, x.Name, x.ContentID, x.Date }).ToList();
var r = q.ToLookup(item => item.ContentID);
foreach (var rItem in r) {
var s = rItem.OrderByDescending(a => a.Date).First();
/* do stuff with s */
}
... but the ToLookup feels kind of clunky. Or do I have the best (simplest) solution?
Also, I know I shouldn't be using ToList, but please just ignore that for the time being.
Thanks in advance!
I think you want:
var q = from x in new XPQuery<XPContent>(s)
group x by x.ContentID into g
let latest = g.OrderByDescending(a => a.Date).First()
select new
{
latest.Category, latest.ContentType,
latest.Name, latest.ContentID, latest.Date
};
(Do note that there are more performant ways of finding the 'maximum' element from a group than by sorting it first, for example with a MaxBy operator.)
The query is quite simple; it just groups items by their ContentId, and then from each group, selects an instance of an anonymous type that is produced from the group's latest item.

Filter a LINQ query

Here's my query.
var query = from g in dc.Group
join gm in dc.GroupMembers on g.ID equals gm.GroupID
where gm.UserID == UserID
select new {
id = g.ID,
name = g.Name,
pools = (from pool in g.Pool
// more stuff to populate pools
So I have to perform some filtering, but when I attempt to filter
var filter = query.Where(f => f.pools.[no access to list of columns]
I can't access any of the items within "pools". Does anyone know how I'm able to access that?
What I'd like to do is this:
var filterbyGame = query.Where(f = > f.pools.GameName == "TestGame");
Let me know if that's even possible with thew ay I have this setup.
Thanks guys.
In your query you can't do Where(f => f.pools.GameName)
because f is an IEnumerable<>
Something like this should work:
Where(f => f.pools.Any(p => p.GameName == "TestGame"))
pools is an enumeration, not a single instance. That's why you're not getting column names.
You need to change your filter to something like:
var filterByGame = query.Where(f => f.pools.Any(p => p.GameName == "TestGame"));

Categories