How do I retrieve only certain subobjects in LINQ? - linq

I have an entity object (Company) which has 1 or more subobjects (CompanyRevision) represented as a non-null FK relationship in the database.
Using LINQ, I want to get all the Companies from the database, but I also only want the latest CompanyRevision for each company.
This is how I do it today, but I have a feeling this could be done using one query.
IEnumerable<Company> companyList = from p in ctx.Company.Include("CompanyRevisions")
select p;
foreach(Company c in companyList)
{
CompanyRevision cr = (from p in c.CompanyRevisions
orderby p.Timestamp descending
select p).First();
// Do something with c and cr...
}
As you can see, I would like to add this second LINQ query (the one that gets the latest CompanyRevision) into the first one, so that companyList[i].CompanyRevisions is basicly a list with just one entry (the latest one). I can't for the life of my figure out how to do this. Please help!
Thanks in advance

how about this: mixing the linq language and extension methods:
var results = from p in ctx.Company.Include("CompanyRevisions")
select new {Company = p,
Revision = p.CompanyRevisions.OrderByDescending(cr => cr.Timestamp).First()
}
Each result now has a Company and Revision member.
It's possible that you could also do this -
var results = from p in ctx.Company.Include("CompanyRevisions")
select new {Company = p,
Revision = (from pcr in p.CompanyRevisions
orderby pcr.Timestamp descending
select pcr).First()
}
To give the same results.
Although that's a guess - I haven't labbed that one out; but it's how I would try it first.

Related

Combining LINQ Queries to reduce database calls

I have 2 queries that work, I was hoping to combine them to reduce the database calls.
var locations = from l in db.Locations
where l.LocationID.Equals(TagID)
select l;
I do the above because I need l.Name, but is there a way to take the above results and put them into the query below?
articles = from a in db.Articles
where
(
from l in a.Locations
where l.LocationID.Equals(TagID)
select l
).Any()
select a;
Will I actually be reducing any database calls here?
This seems a bit complicated because Locations appears to be a multi-value property of Articles and you want to only load the correct one. According to this answer to a similar question you need to use a select to return them separately in one go so e.g.
var articles = from a in db.Articles
select new {
Article = a,
Location = a.Locations.Where(l => l.LocationId == TagId)
};
First failed attempt using join:
var articlesAndLocations = from a in db.Articles
join l in a.Locations
on l.LocationID equals TagID
select new { Article = a, Location = l };
(I usually use the other LINQ syntax though so apologies if I've done something stupid there.)
Could you not use the Include() method here to pull in the locations which are associated with each article, then select both the article and location object? or the properties you need from each.
The include method will ensure that you don't need to dip into the db twice, but will allow you to access properties on related entities.
You would need to use a contains method on an IEnumerable I believe, something like this:
var tagIdList = new List() { TagID };
var articles = from a in db.Articles.Include("Locations")
where tagIdList.Contains(from l in a.Locations select l.LocationID)
select new { a, a.Locations.Name };
(Untested)

Linq to SQL - Many to Many Predicates

I'm familiar with doing simple Many-to-Many relationships (i.e. simple joins) in Linq to SQL, but I'm having a hard time thinking right now.
I have three tables (and so, entities in my Linq-to-SQL model) representing a taxonomic system. Standard-issue really:
Products - ProductTags - Tags
I'm writing a method that returns a set of Products where the Tag they're in matches a query. So if someone searches for "foo" then all products assigned with the tags "foobar" or "fooqux" (but not "bazbar") would be returned.
I know I have to structure the query into two parts: first to get the matching Tags, and then to get the Products that have those tags. It's the second part I'm stumped on.
Here's what I've got so far:
var tags = from t in db.Tags
where t.Name.Contains( tagSearchQuery )
select t;
var products = from p in db.Products
// then a miracle happens
select p;
Assistance much appreciated :)
You can do it in one query if you just start with the ProductTags table. You'll probably also need a Distinct to avoid duplicate products matching multiple tags.
var products = (from pt in db.ProductTags
where pt.Tag.Name.Contains( tagSearchQuery )
select pt.Product).Distinct();
or here's another way:
var products = from p in db.Products
from pt in p.ProductTags
where pt.Tag.Name.Contains( tagSearchQuery )
select p
IQueryable<Tag> tags =
from t in db.Tags
where t.Name.Contains( tagSearchQuery )
select t;
IQueryable<Product> products =
from p in db.Products
where p.ProductTags.Any(pt => tags.Contains(pt.Tag))
select p;
OR
IQueryable<Product> products =
from p in db.Products
from pt in p.ProductTags
let t = pt.Tag
where t.Name.Contains( tagSearchFragment )
group t by p into g
select g.Key;

ef and linq extension method

I have this sql that i want to have written in linq extension method returning an entity from my edm:
SELECT p.[Id],p.[Firstname],p.[Lastname],prt.[AddressId],prt.[Street],prt.[City]
FROM [Person] p
CROSS APPLY (
SELECT TOP(1) pa.[AddressId],a.[ValidFrom],a.[Street],a.[City]
FROM [Person_Addresses] pa
LEFT OUTER JOIN [Addresses] AS a
ON a.[Id] = pa.[AddressId]
WHERE p.[Id] = pa.[PersonId]
ORDER BY a.[ValidFrom] DESC ) prt
Also could this be re-written in linq extension method using 3 joins?
Assuming you have set the Person_Addresses table up as a pure relation table (i.e., with no data besides the foreign keys) this should do the trick:
var persons = model.People
.Select(p => new { p = p, a = p.Addresses.OrderByDescending(a=>a.ValidFrom).First() })
.Select(p => new { p.p.Id, p.p.Firstname, p.p.LastName, AddressId = p.a.Id, p.a.Street, p.a.City });
The first Select() orders the addresses and picks the latest one, and the second one returns an anonymous type with the properties specified in your query.
If you have more data in your relation table you're gonna have to use joins but this way you're free from them. In my opinion, this is more easy to read.
NOTE: You might get an exception if any entry in Persons have no addresses connected to them, although I haven't tried it out.

NHIbernate Linq group by count

I have the version 3.0.0.1001 nhibernate.
My objects are basically modeiling a lineup at an event. So I have a StageSet object which represents one slot in the schedule for a stage.
Each StageSet object has a Stage and an Act property.
It also has many Users - people who have favorited the set.
I'm trying to ascertain the most popular sets that have been favorited using the following linq:
var topStars = from s in Db.StageSets
group s by s.Act.Id into g
select new { SetKey = g.Key, Count = g.Count() };
However this just fails with a Could not execute query[SQL: SQL not available] error
Should I be able to do this?
w://
in case someone comes here. The following should work with NH 3.1
var topStars = from s in Db.StageSets
group s by s.Act.Id into g
select new { SetKey = g.First().Act.Id, Count = g.Count() }
You've specified the query correctly in linq. NHibernate is refusing to translate it.
I just copied your query with a slightly different domain and it worked. But that will count StageSets by Act, NOT favorites.

Stuck on a subquery that is grouping, in Linq`

I have some Linq code and it's working fine. It's a query that has a subquery in the Where clause. This subquery is doing a groupby. Works great.
The problem is that I don't know how to grab one of the results from the subquery out of the subquery into the parent.
Frst, here's the code. After that, I'll expplain what piece of data i'm wanting to extract.
var results = (from a in db.tblProducts
where (from r in db.tblReviews
where r.IdUserModified == 1
group r by
new
{
r.tblAddress.IdProductCode_Alpha,
r.tblAddress.IdProductCode_Beta,
r.tblAddress.IdProductCode_Gamma
}
into productGroup
orderby productGroup.Count() descending
select
new
{
productGroup.Key.IdProductCode_Alpha,
productGroup.Key.IdProductCode_Beta,
productGroup.Key.IdProductCode_Gamma,
ReviewCount = productGroup.Count()
}).Take(3)
.Any(
r =>
r.IdProductCode_Alpha== a.IdProductCode_Alpha&&
r.IdProductCode_Beta== a.IdProductCode_Beta&&
r.IdProductCode_Gamma== a.IdProductCode_Gamma)
where a.ProductFirstName == ""
select new {a.IdProduct, a.FullName}).ToList();
Ok. I've changed some field and tables names to protect the innocent. :)
See this last line :-
select new {a.IdProduct, a.FullName}).ToList();
I wish to include in that the ReviewCount (from the subquery). I'm jus not sure how.
To help understand the problem, this is what the data looks like.
Sub Query
IdProductCode_Alpha = 1, IdProductCode_Beta = 2, IdProductCode_Gamma = 3, ReviewCount = 10
... row 2 ...
... row 3 ...
Parent Query
IdProduct = 69, FullName = 'Jon Skeet's Wonder Balm'
So the subquery grabs the actual data i need. The parent query determines the correct product, based on the subquery filters.
EDIT 1: Schema
tblProducts
IdProductCode
FullName
ProductFirstName
tblReviews (each product has zero to many reviews)
IdProduct
IdProductCode_Alpha (can be null)
IdProductCode_Beta (can be null)
IdProductCode_Gamma (can be null)
IdPerson
So i'm trying to find the top 3 products a person has done reviews on.
The linq works perfectly... except i just don't know how to include the COUNT in the parent query (ie. pull that result from the subquery).
Cheers :)
Got it myself. Take note of the double from at the start of the query, then the Any() being replaced by a Where() clause.
var results = (from a in db.tblProducts
from g in (
from r in db.tblReviews
where r.IdUserModified == 1
group r by
new
{
r.tblAddress.IdProductCode_Alpha,
r.tblAddress.IdProductCode_Beta,
r.tblAddress.IdProductCode_Gamma
}
into productGroup
orderby productGroup.Count() descending
select
new
{
productGroup.Key.IdProductCode_Alpha,
productGroup.Key.IdProductCode_Beta,
productGroup.Key.IdProductCode_Gamma,
ReviewCount = productGroup.Count()
})
.Take(3)
Where(g.IdProductCode_Alpha== a.IdProductCode_Alpha&&
g.IdProductCode_Beta== a.IdProductCode_Beta&&
g.IdProductCode_Gamma== a.IdProductCode_Gamma)
where a.ProductFirstName == ""
select new {a.IdProduct, a.FullName, g.ReviewCount}).ToList();
While I don't understand LINQ completely, but wouldn't the JOIN work?
I know my answer doesn't help but it looks like you need a JOIN with the inner table(?).
I agree with shahkalpesh, both about the schema and the join.
You should be able to refactor...
r => r.IdProductCode_Alpha == a.IdProductCode_Alpha &&
r.IdProductCode_Beta == a.IdProductCode_Beta &&
r.IdProductCode_Gamma == a.IdProductCode_Gamma
into an inner join with tblProducts.

Resources