I'm just getting up to speed with asp.net mvc and I'm wondering how one would go about getting relational data more than one level deep from the entity specified in the from clause. Using the following domain model as an example:
A Blog has many posts. Posts have many comments.
How would I write a LINQ query to return entities down to the Blog.Posts.Comments level?
The only (not so elegant) solution I came up with was to use a LINQ query to get Blog and Posts and then a foreach to get comments.
var blog = (from b in _db.BlogSet.Include("Posts")
select b);
foreach (Post p in blog.Posts)
{
var comments = (from c in _db.CommentSet
where c.PostId = p.Id
select c);
p.Comments = comments;
}
A Blog has many posts. Posts have many comments.
How would I write a LINQ query to return entities down to the Blog.Posts.Comments level?
I believe, you could do the following to achieve this:
var blog = (from b in _db.BlogSet.Include("Posts.Comments")
select b);
In this case, for each blog, the posts and their comments will be fetched.
Marc
You can just use two from statements:
var comments=from post in blog
from comment in blog.comments
where comment.PostId==post.Id
select comment;
Related
I have a many-to-many relationship:
class Project {
Set<PrincipalInvestigator> pis
:
static hasMany = [ pis:PrincipalInvestigator ]
}
class PrincipalInvestigator {
String name
:
}
I want a query that returns a unique and sorted list of PIs that belong to a pre-defined list of projects.
A naive approach is to iterate thru the projects, and iterate thru their list of PIs, while removing the dupes. The code to do this is trivial, but it is slow.
So far, the best working solution I could come up with is:
def pi_ids = Project.createCriteria().list{ // find unique list of PI IDs
// project filters here, not relevant to the question
createAlias('pis', 'aka_pis', JoinType.LEFT_OUTER_JOIN)
isNotNull('aka_pis.id')
projections {
distinct('aka_pis.id')
}
}
def pi_list = PrincipalInvestigator.createCriteria().list{ // get PIs from list of IDs
inList('id', pi_ids)
order('name', 'asc')
}
My solution is one order of magnitude faster, but it's still 2 distinct queries. Is there a way to get the same result in a single query?
Using executeQuery makes queries as this alot easier. Something along the following should work:
PrincipalInvestigator.executeQuery("select distinct p.pis from Project p where p.id in :projectIds",
[projectIds: [1,2,3]])
The solution to my problem is this HQL:
PrincipalInvestigator.executeQuery(
"select distinct pi from Project p inner join p.pis as pi where p.id in :projectIds order by pi.name",
[projectIds:[1,2,3]])
This solution allows for sorting of the distinct results and the inner join trims all the null instances. Thanks to cfrick for putting me on the right track.
from this example that I stole from the net:
Company company = context.Companies
.Include("Employee.Employee_Car")
.Include("Employee.Employee_Country")
.FirstOrDefault(c => c.Id == companyID);
if I want a where clause in the include of Employee_Car for example, how do I do that? Say I just want it to retrieve blue cars.
Thanks
Short answer is you can't do it just through using the include. You will need to do a bit of joining.
So taking this tip post and the SO answer you could do something like along these lines. (Note not exactly your return type but the closest)
var companyBlueCars = from company in context.Companies
where company.Id == companyID
select new
{
company,
blueCars = from employee in company.Employees
where employee.Employee_Car.Colour == "blue"
select employee.Employee_Car
};
(I did make a couple guesses about the data structure but the idea is there.)
You will not do that because include doesn't support filtering or sorting. You must execute two separate queries to load companies and cars with their filters.
If you have a Car model with 20 or so properties (and several table joins) for a carDetail page then your LINQ to SQL query will be quite large.
If you have a carListing page which uses under 5 properties (all from 1 table) then you use a CarSummary model. Should the CarSummary model be populated using the same query as the Car model?
Or should you use a separate LINQ to SQL query which would be more precise?
I am just thinking of performance but LINQ uses lazy loading anyway so I am wondering if this is an issue or not.
Create View Models to represent the different projections you require, and then use a select projection as follows.
from c in Cars
select new CarSummary
{
Registration = c.Registration,
...
}
This will create a query that only select the properties needed.
relationships will be resolved if they are represented in the data context diagram (dbml)
select new CarSummary
{
OwnerName = c.Owner.FirstName
}
Also you can nest objects inside the projection
select new CarSummary
{
...
Owner = new OwnerSummary
{
OwnerName = c.Owner.FirstName,
OwnerAge = c.Owner.Age
}
...
}
If you are using the same projection in many places, it man be helpful to write a method as follows, so that the projection happens in one place.
public IQueryable<CarSummary> CreateCarSummary(IQueryable<Car> cars)
{
return from c in cars
select new CarSummary
{
...
}
}
This can be used like this where required
public IQueryable<CarSummary> GetNewCars()
{
var cars = from c in Cars
select c;
return CreateCarSummary(cars);
}
I think that in your case lazy loading doesn't bring much benefit as you are going to use 1 property from each table, so sooner or later to render the page you will have to perform all the joins. In my opinion you could use the same query and convert from a Car model to a CarSummary model.
If performance is actually a concern or an issue currently, you should do a separate projection linq query so that the sql query only selects the 5 fields you need to populate your view model instead of returning all 20 fields.
I really hope you can help me as I am about to just throw my MVC/Entity Framework project in the bin and start a MVC/Linq project.
I am building a forum as a project just to get to know MVC/Entity, and I have 4 tables which are all related.
Forum_Category,
Forum,
Topic,
Reply
so there is a 1 to many on Category_ID between Forum_Category and Forum.
1 to many on Forum_ID between forum and topic.
and a 1 to many on Topic_ID between Topic and Reply.
I understand that you can load up related data using
Dim F = (From forum in _DataContext.Forum.Include("Forum_Category") _
Where forum.Forum_ID = 1 _
Select forum).First
but what if I wanted to get the data for all the replys to a single topic, then load up the topic's forum and then the category?
I managed to get slightly there with the code:
Dim FT = (From F In _dataContext.Topic.Include("Forum") _
Where F.TOPIC_STATUS = ForumSettings.FORUM_STATUS.Active _
Select F).First()
Dim TRs = (From F In _dataContext.Topic_Reply _
Where F.Topic.TOPIC_ID = TopicID _
Select F).ToList()
For Each TR As Topic_Reply In TRs
FT.Topic_Reply.Add(TR)
Next
Return FT
But then when I tried to add a new Reply, I got the error:
An object with the same key already exists in the ObjectStateManager. The ObjectStateManager cannot track multiple objects with the same key.
I am totally lost now.
Dim FT = (From T In _dataContext.Topic.Include("Replies").Include("Forum") _
Where T.TOPIC_ID = TopicID _
Select T).First()
...presuming the property is called "Replies" and not "Replys". :)
Now you can look at FT.Replies to see the reply and FT.Forum to see the Forum.
Don't use Add. That's to do an INSERT into your DB, and of course those records are already there. That's what the error means.
The key insight here is that all relationships are two-way. You can see the related object from either side of the relationship.
I am sure this is a stupid question, but how would I get a pagedlist of filtered items?
Here is how I ended up doing it:
PagedList<Company> company = Company.GetPaged(1, 10);
var list = Company.Find(x => x.CompanyName.ToLower().Contains(query.ToLower()));
company .Clear();
foreach (var x in list)
company .Add(x);
return View(company );
In other words I want to do a find on the table, and return a paged list of the results.
Thanks!
There is a fantastic example of this in NerdDinner.
If you have trouble finding it then let me know and I'll post the code for ya.
Actually it's fairly easy to get the paged list and you said the answer in your question
In other words I want to do a find on
the table, and return a paged list of
the results.
Create your LINQ query (do a find on the table)
New up a SubSonic.Schema.PagedList(of T) (return a paged list)
For example:
var list = Company.Find(x => x.CompanyName.ToLower().Contains(query.ToLower()));
var paged = new SubSonic.Schema.PagedList<Company>(list,1,10)