I have two tables: categories and articles. One category can have many articles. I want to display overview of new articles separated by categories. But I want to display only first five articles per each category. How should I write query to solve this?
My first idea was something like this:
var cat = from c in ctx.categories
where c.IsPublic == true
select c;
But this contains all articles and I'm not sure how to write something like c.articles.Take(5) to query. Is it possible?
Oh, and it is ASP.NET MVC 2 with Entity Framework.
This worked for me. Important here is to switch off lazy loading to avoid that all articles are loaded when you iterate through the collection:
ctx.ContextOptions.LazyLoadingEnabled = false; // assuming you are in EF 4.0
var query = ctx.categories
.Where(c => c.IsPublic)
.Select(c => new {
Category = c,
Articles = c.Articles.Take(5)
}).ToList().Select(x => x.Category);
foreach (var category in query)
{
foreach (var article in category.Articles)
{
// runs only 5 times through this loop
}
}
Edit
If you don't dispose ctx and work with the context more after the code snippet above you probably better reenable lazy loading again ...
ctx.ContextOptions.LazyLoadingEnabled = true;
... before you get into trouble with other queries if your application mostly relies on lazy loading.
ctx.categories
.Where(c => c.IsPublic)
.Select(c => new
{
Category = c,
Articles = c.Articles.OrderBy(a => a.Whatever).Take(5)
).Select(c => c.Category);
Supposing that your class name is Category with an EntityCollection called Articles, this should work.
Related
I have a Verse class which has a list of VerseTranslation.
When I fetch the verse I only want some of the verse translations. Is there a way to do something like this?
var desiredTranslationCodes = new List<string> { "Code1", "Code2" };
var result = Context.Verses.Where(v => v.Chapter == 1 && v.VerseNumber == 3)
.Include(v => v.Translations, t => desiredTranslationCodes.Contains(t.TranslatorCode))
I am only going to convert these to a view model. It seems a big waste to load all VerseTranslations when I might only want 2 of the 10 translations.
I think you can use projections to anonymous or new type. Try the following. Hope to help, my friend :))
var result = Context.Verses.Select(c => new
{
Data = c,
Translations = c.Translations.OrderBy(p => p.Name).Take(2)
});
Our current system is using Lazyloading by default (it is something I am going to be disabling but it can't be done right now)
For this basic query I want to return two tables, CustomerNote and Note.
This is my query
using (var newContext = new Entities(true))
{
newContext.Configuration.LazyLoadingEnabled = false;
var result = from customerNotes in newContext.CustomerNotes.Include(d=>d.Note)
join note in newContext.Notes
on customerNotes.NoteId equals note.Id
where customerNotes.CustomerId == customerId
select customerNotes;
return result.ToList();
}
My result however only contains the data in the CustomerNote table
The linked entities Customer and Note are both null, what am I doing wrong here?
I got it working with the following which is much simpler than what I've found elsewhere
Context.Configuration.LazyLoadingEnabled = false;
var result = Context.CustomerNotes.Where<CustomerNote>(d => d.CustomerId == customerId)
.Include(d=>d.Note)
.Include(d=>d.Note.User);
return result.ToList();
This returns my CustomerNote table, related Notes and related Users from the Notes.
That is callled eager loading you want to achieve.
var customerNotes = newContext.CustomerNotes.Include(t=> t.Node).ToList();
This should work, i don't really understand the keyword syntax.
If the code above doesn't work try this:
var customerNotes = newContext.CustomerNotes.Include(t=> t.Node).Select(t=> new {
Node = t.Node,
Item = t
}).ToList();
Spent a lot of time, but still cann't understand how to avoid caching in DbContext.
I attached below entity model of some easy case to demonstrate what I mean.
The problem is that dbcontext caching results. For example, I have next code for querying data from my database:
using (TestContext ctx = new TestContext())
{
var res = (from b in ctx.Buildings.Where(x => x.ID == 1)
select new
{
b,
flats = from f in b.Flats
select new
{
f,
people = from p in f.People
where p.Archived == false
select p
}
}).AsEnumerable().Select(x => x.b).Single();
}
In this case, everything is fine: I got what I want (Only persons with Archived == false).
But if I add another query after it, for example, query for buildings that have people that have Archived flag set to true, I have next things, that I really cann't understand:
my previous result, that is res, will be added by data (there
will be added Persons with Archived == true too)
new result will contain absolutely all Person's, no matter what Archived equals
the code of this query is next:
using (TestContext ctx = new TestContext())
{
var res = (from b in ctx.Buildings.Where(x => x.ID == 1)
select new
{
b,
flats = from f in b.Flats
select new
{
f,
people = from p in f.People
where p.Archived == false
select p
}
}).AsEnumerable().Select(x => x.b).Single();
var newResult = (from b in ctx.Buildings.Where(x => x.ID == 1)
select new
{
b,
flats = from f in b.Flats
select new
{
f,
people = from p in f.People
where p.Archived == true
select p
}
}).AsEnumerable().Select(x => x.b).Single();
}
By the way, I set LazyLoadingEnabled to false in constructor of TestContext.
Does anybody know how to workaround this problem? How can I have in my query what I really write in my linq to entity?
P.S. #Ladislav may be you can help?
You can use the AsNoTracking method on your query.
var res = (from b in ctx.Buildings.Where(x => x.ID == 1)
select new
{
b,
flats = from f in b.Flats
select new
{
f,
people = from p in f.People
where p.Archived == false
select p
}
}).AsNoTracking().AsEnumerabe().Select(x => x.b).Single();
I also want to note that your AsEnumerable is probably doing more harm than good. If you remove it, the Select(x => x.b) will be translated to SQL. As is, you are selecting everything, then throwing away everything but x.b in memory.
have you tried something like:
ctx.Persons.Where(x => x.Flat.Building.Id == 1 && x.Archived == false);
===== EDIT =====
In this case I think you approach is, imho, really hazardous. Indeed you works on the data loaded by EF to interpret your query rather than on data resulting of the interpretation of your query. If one day EF changes is loading policy (for example with a predictive pre-loading) your approach will "send you in then wall".
For your goal, you will have to eager load the data you need to build your "filterd" entity. That is select the building, then foreach Flat select the non archived persons.
Another solution is to use too separate contexts in an "UnitOfWork" like design.
I have something like this:
var tmp =_forumsDb.Threads
.Where(t => t.Id == variable)
.Select(t => new { Thread = t, Posts = t.Posts.Take(1) })
.Single();
Now, i expect tmp.Thread.Posts.Count(); to be 1, but it takes all posts that i have in database. Is it possible to use projection that takes explicit amount of posts, do it in a single query without turning off lazy loading?
Edit:
I tried doing something like this, but it does not work either:
var tmp =_forumsDb.Threads
.Where(t => t.Id == variable)
.Select(t => new { Thread = t, Posts = t.Posts.OrderBy(p => p.DateCreated).Take(1) })
.Select(t => t.Thread)
.Single();
tmp.Thread.Posts is the navigation property for which lazy loading is configured. Since it isn't yet loaded, accessing it loads all the remaining posts.
tmp.Posts is not a navigation property. That's the one you should be able to access without triggering another query.
I asked a question about this previously but my database structure has changed, and while it made other things simpler, now this part is more complicated. Here is the previous question.
At the time, my EF Context had a UsersProjects object because there were other properties. Now that I've simplified that table, it is just the keys, so all my EF context knows about is Users and Projects and the M2M relationship between them. There is no more UsersProjects as far as EF knows.
So my goal is to say "show me all the users who are working on projects with me."
in SQL, this would go something like:
SELECT * FROM Users INNER JOIN UsersProjects ON Users.ID=UsersProjects.UserID
WHERE ProjectID IN (SELECT ProjectID FROM UsersProjects WHERE UserID=#UserID)
and I started in EF with something like this:
var myProjects =
(from p in edmx.Projects
where p.Users.Contains(edmx.Users.FirstOrDefault(u => u.Email == UserEmail))
orderby p.Name
select p).ToList();
var associatedUsers =
(from u in edmx.Users
where myProjects.Contains(?????????)
//where myProjects.Any(????????)
select u);
The trick is finding what to put in the ????????. Anyone help here?
var me = context
.Users
.First(user => user.Email = "me#example.com");
// Note that there is no call to ToList() or AsEnumerable().
var myProjects = context
.Projects
.Where(project => project.Users.Contains(me));
var associatedUsers = context
.Users
.Where(user => myProjects.Any(project => user.Project.Contains(project)));
But there are several other possible solutions. For example
var associatedUsers = myProjects
.SelectMany(project => project.Users)
.Distinct();
which I would prefer.
Further note that it is much easier to obtain myProjects using a navigation property instead of using Contains().
var myProjects = me.Projects;
Daniel, I tried what you had and ran into some issues. Can you explain what these errors mean?
I tried:
// Doesn't work.
using (var edmx = new MayflyEntities())
{
var me = edmx.Users.First(user => user.Email == UserEmail);
var myProjects = edmx.Projects.Where(project => project.Users.Contains(me));
var associatedUsers = myProjects.SelectMany(project => project.Users).Distinct();
}
but got the two following exceptions:
Unable to create a constant value of type 'DomainModel.User'. Only primitive types ('such as Int32, String, and Guid') are supported in this context.
and
The ObjectContext instance has been disposed and can no longer be used for operations that require a connection.
So, I moved some things around and this works fine, but now I'm curious as to why? In SQL Profiler, it all executes in one query, so why does it show that the context has been disposed? Also, why can it not use the me object instead of the lambda?
// Works fine
var edmx = new MayflyEntities();
var myProjects = edmx.Projects.Where(project => project.Users.Contains(edmx.Users.First(user => user.Email == UserEmail)));
var associatedUsers = myProjects.SelectMany(project => project.Users).Distinct();