OrderBy in Include child using EF Core [duplicate] - c#

This question already has answers here:
Filtering on Include in EF Core
(9 answers)
Closed 2 years ago.
In my .NET Core / EF Core application I have a model with a nested list of child objects. When I retrieve an instance, I need the nested list to be ordered by one of the child's properties.
What is the correct way to sort this list so that it always returns sorted correctly from my database?
Right now I do it after loading from database:
public async Task<Parent> GetParent(int id)
{
var result = await context.Parents
.Include(p => p.Children)
.SingleOrDefaultAsync(p => p.Id == id);
result.Children = result.Children.OrderBy(c => c.Sequence).ToList();
return result;
}

Starting with Entity Framework Core 5.0, you can sort (OrderBy) and filter (Where) directly in the Include statement (with some restrictions).
See the Microsoft Documentation.
Your statement can therefore be simplified like this:
public async Task<Parent> GetParent(int id)
{
return await context.Parents
.Include(p => p.Children.OrderBy(c => c.Sequence))
.SingleOrDefaultAsync(p => p.Id == id);
}
This is a nice step forward for EF Core in my opinion.

The result you are trying to return is the ordered list of children. That's not what you want. Instead sort the children then return the parent:
public async Task<Parent> GetParent(int id)
{
var parent = context.Parents
.Include(p => p.Children)
.SingleOrDefaultAsync(p => p.Id == id);
parent.Result.Children = parent.Result.Children.OrderBy(c => c.Sequence).ToList();
return await parent;
}

var result = loadedInMemory.Result.Children.OrderBy(c => c.Sequence).ToList();
You need to add ToList() at the end

Related

How to include all levels of a object in EF? [duplicate]

This question already has an answer here:
loading a full hierarchy from a self referencing table with EntityFramework.Core
(1 answer)
Closed 2 years ago.
I have an object that defines a network structure, I want to send over all children of that object and the children of the children and so on.
Right now I have this:
var Data = await _context.Scans
.Include(c => c.networkDevices)
.ThenInclude(d => d.ports)
.ThenInclude(p => p.Service)
.Include(c => c.root)
.ThenInclude(d => d.children).ThenInclude(p => p.children).ThenInclude(c => c.children)
.ToListAsync();
return Data;
This code will get most levels but if a network has many different layers it won't get all of them.
How can i make it so that all layers get included.
I think there is no built in way to load "all" layers because in theory it'd be possible that you have cyclic references.
This snippet will create a query for the number of layers
namespace System.Data.Entity
{
using Linq;
using Linq.Expressions;
using Text;
public static class QueryableExtensions
{
public static IQueryable<TEntity> Include<TEntity>(this IQueryable<TEntity> source,
int levelIndex, Expression<Func<TEntity, TEntity>> expression)
{
if (levelIndex < 0)
throw new ArgumentOutOfRangeException(nameof(levelIndex));
var member = (MemberExpression)expression.Body;
var property = member.Member.Name;
var sb = new StringBuilder();
for (int i = 0; i < levelIndex; i++)
{
if (i > 0)
sb.Append(Type.Delimiter);
sb.Append(property);
}
return source.Include(sb.ToString());
}
}
}
Usage:
var children = await DbContext.Roots
.Include(3, a => a.Children)
.SingleOrDefaultAsync(a => a.Id == 5);
I think the easiest way to accomplish this is by lazy loading,
take a look at this post

EF Core: Lambda expression used inside Include is not valid

I'm trying to get all my active Products (GetByCategory), including all active ProductSpecifications, with no success.
If I execute the commented block I get the Exception
System.InvalidOperationException: Lambda expression used inside Include is not valid
My solution was to get all active Products, then load all active ProductSpecifications using the method
LoadAllActiveSpecifications(products)
The problem to use LoadAllActiveSpecifications is that I need to run a for loop to load all specifications.
Is it possible to load all active ProductSpecification implicitly?
Here is my code. Could someone please, tell me what I'm missing?
public async Task<IEnumerable<Product>> GetByCategory(string code)
{
/*
// Following the example from Microsoft: https://learn.microsoft.com/en-us/ef/core/querying/related-data
return await _context
.Products
.Include(p => p.Category)
.Include(product => product
.Specifications
.Where(specification => specification.IsActive))
.Where(product => product.IsActive)
.Where(p => p.Category.Code == code)
.ToListAsync();
*/
var products = await _context
.Products
.Include(p => p.Category)
//.Include(p => p.Specifications)
.Where(p => p.IsActive)
.Where(p => p.Category.Code == code)
.ToListAsync();
await LoadAllActiveSpecifications(products);
return products;
}
private async Task LoadAllActiveSpecifications(List<Product> products)
{
for (int index = 0; index < products.Count; index++)
{
var product = products[index];
await LoadSpecActiveAsync(product);
}
}
private async Task LoadSpecActiveAsync(Product product)
{
await _context
.Entry(product)
.Collection(p => p.Specifications)
.Query()
.Where(s => s.IsActive)
.LoadAsync();
}
Thanks in advance
Cheers
EF by definition will load a complete graph of entities. You can assign global query filters in EF Core (https://learn.microsoft.com/en-us/ef/core/querying/filters) to handle rules for things like soft deletes which will allow EF to return just "Active" rows for applicable entities. EF6 had something similar in Dynamic Queries.

Net Core 3.1 Razor Pages Multiple Nested Many to Many queries with Linq

I'm using .NET Core 3.1, which has some differences to pre .NET Core 3.0 around Linq queries from what I can gather: https://learn.microsoft.com/en-us/ef/core/what-is-new/ef-core-3.0/breaking-changes#linq-queries-are-no-longer-evaluated-on-the-client.
There seems to be a lot of information out there for earlier versions of .Net core that don't quite work for me.
All join tables and entities should be configured correctly for automatic creation, so I won't add them all unless someone needs to see them.
In light of that link I also need to be careful that I don't download the entire notes database, which could be millions of lines eventually. Some advice on tracking or notracking, firstordefaultasync etc might be helpful to if it makes a difference.
#Rena helped me with this earlier to get this example working for a single many to many query, thanks heaps Rena:
public async Task<List<Note>> GetAllNotesForNoteTypeAsync(string notetype)
{
var noteData = await context.Note
.Include(n => n.NoteNoteTypeJoins)
.ThenInclude(t => t.NoteType)
.ToListAsync();
var noteDataWithTypes = noteData.Where(i => i.NoteNoteTypeJoins.Any(x => x.NoteType.Type.ToString() == notetype))
.ToList();
return noteDataWithTypes;
}
I have another query I need, which goes one level deeper. These two attempts below obviously don't work but they explain what I am trying to do, trying to filter notes by notetype group text, which is four tables removed via two many to many relationships:
public async Task<List<Note>> GetAllNotesForNoteTypeGroupAsync(string notetypegroup)
{
var noteData = await context.Note
.Include(n => n.NoteNoteTypeJoins)
.ThenInclude(t => t.NoteType)
.ThenInclude(gt => gt.NoteTypeNoteTypeGroupJoins)
.ThenInclude(g => g.NoteTypeGroup)
.Where(g => g.NoteTypeGroup.Group == notetypegroup)
.ToListAsync();
return noteData;
}
or:
public async Task<List<Note>> GetAllNotesForNoteTypeGroupAsync(string notetypegroup)
{
var noteData = await context.Note
.Select(note => new
{
mytypejoin = note.NoteNoteTypeJoins
.Select(notetypejoin => new
{
mynotetype = notetypejoin.NoteType
.Select(notetype => new
{
mynotetype = notetype.NoteTypeNoteTypeGroupJoins
.Select(notetypegroupjoins => new
{
mytypegroup = notetypegroupjoins
.Where(i => i.NoteTypeNoteTypeGroupJoins
.Any(x => x.NoteTypeGroup.Group.ToString() == notetypegroup)
}),
}),
}),
});
return noteData;
Any help would be greatly appreciated, I'm fairly new to this, Thank you!
Here is the answer compliments of Chris:
var noteData = await context.Note.Where(n =>
n.NoteNoteTypeJoins.Any(ntj =>
ntj.NoteType.NoteTypeNoteTypeGroupJoins.Any(ntg => ntg.NoteTypeGroup.Group == notetypegroup)))
.ToListAsync();
You'd need to use Any recursively to dig into the lower relationships, but it does get ugly.
var noteData = await context.Note.Where(n =>
n.NoteTypeJoins.Any(ntj =>
ntj.NoteType.Any(nt =>
nt.NoteTypeNoteTypeGroupJoins.Any(ntntgj =>
ntntgj.NoteTypeGroup.Group == notetypegroup)))))
.ToListAsync();
You don't need all the Include and ThenInlude, as EF will automatically issue the necessary joins in order to make the query in the first place, since it includes all these relationships.
It's also important to note (and will become more problematic with deeper level queries like this), that it's not going to just return NoteTypes, for example, that are in this group. It's going to return Notes (since that's the base entity you're querying) that have any NoteTypes in this group, with all the related NoteTypes, whether they're in the group or not. You just won't have notes where there are zero associated note types in this group.
If you're looking to filter the actual relationships, you'll have to explicitly load. See: https://learn.microsoft.com/en-us/ef/core/querying/related-data#querying-related-entities

Entity Framework 6 including only child records that have a specific value? [duplicate]

This question already has answers here:
EF: Include with where clause [duplicate]
(5 answers)
Closed 5 years ago.
I'm trying to return a record set that includes child records that meet a specific data requirements. This is my base query, and in this case I am only trying to bring back children where the IsActive field is true.
var result = db.Projects
.Include(p => p.Department)
.Include(p => p.ProjectPhases.Where(pp =>pp.IsActive ))
.Include(p => p.Notes)
.AsQueryable<Project>();
This returns an error:
The Include path expression must refer to a navigation property defined on the type. Use dotted paths for reference navigation properties and the Select operator for collection navigation properties.
Is there simple way to do this?
You can't perform where clause inside of Include. Why don't you just try like this;
var result = db.Projects
.Include(p => p.Department)
.Include(p => p.ProjectPhases)
.Include(p => p.Notes)
.Where(x => x.ProjectPhases.Where(pp =>pp.IsActive))
.AsQueryable<Project>();
Those Includes generate the joins, to filter out use the Where after the Include chain:
var result = db.Projects
.Include(p => p.Department)
.Include(p => p.ProjectPhases)
.Include(p => p.Notes)
.Where(it => it.ProjectPhases.All(a=>a.IsActive))
.AsQueryable<Project>();

Why is Entity Framework 6.1 inconsistently retrieving related entities?

I have the following (subset) in a SQL Server database that has been generated by Entity Framework 6.1 code-first:
I then run the following query
var response = await Context.SurveyResponses
.Include(x => x.Answers)
.SingleOrDefaultAsync(x => x.Client.Id == clientId &&
x.Survey.Id == surveyId &&
!x.CompletedOn.HasValue);
In 99% of cases, the Question object on the Answers collection would be populated. However in rate 1% of cases, it would be null.
This problem was solved by improving the Include statement as follows
var response = await Context.SurveyResponses
.Include(x => x.Answers.Select(y => y.Question))
.SingleOrDefaultAsync(x => x.Client.Id == clientId &&
x.Survey.Id == surveyId &&
!x.CompletedOn.HasValue);
However what I'd like to understand is why the first query ever worked? How did it know to include the Question property of the Answers object? Why did it work in some cases and not others?
EF will auto-fixup what it can. If your context had the data already, it would fill it in for you.
For example, assuming SurveryResponseAnswer with id of 1 has a question of id 1, then:
var db=new Context();
var test=db.Questions.Where(x=>x.id==1);
var result=db.SurveyResponseAnswers.Where(x=>x.id==1);
// result's Question property is filled in
var db=new Context();
var result=db.SurveyResponseAnswers.Where(x=>x.id==1);
// result's Question property is not filled in

Categories

Resources