I have a use-case with a deeply nested class hierarchy, for example like this:
public class Parent
{
public int Id { get; set; }
public List<ChildOne> Children { get; set; }
}
public class ChildOne
{
public int Id { get; set; }
public int ParentId { get; set; }
public List<ChildTwo> ChildrenTwo { get; set; }
}
public class ChildTwo
{
public int Id { get; set; }
public int Priority { get; set; }
public int ChildOneId { get; set; }
public List<ChildThree> ChildrenThree { get; set; }
}
public class ChildThree
{
public int Id { get; set; }
public int ChildTwoId { get; set; }
}
If I want to load all parent-objects and their related children levels, I'd do this:
var objects = context.Parent
.Include(parent => parent.Children)
.ThenInclude(childOne => childOne.ChildrenTwo)
.ThenInclude(childTwo => childTwo.ChildrenThree)
.ToList();
But what if I want my ChildrenTwo entities in the eager-loaded navigational property of ChildOne to be ordered by their Priority? I've done some research, and from the links below (and some others), it is apparently not directly possible in EF Core (yet):
https://github.com/aspnet/EntityFrameworkCore/issues/9445
https://github.com/aspnet/EntityFrameworkCore/issues/2919
https://github.com/aspnet/EntityFrameworkCore/issues/9067
So, how can you achieve the ordering of the ChildrenTwo above (by Priority) in a good/clean way that is fast? That probably means most of the work should happen on the DB server and not on the .NET client side. What's the best approach here?
Though it is very late to answer, but it may help the future readers:
I will explain the code:
var authorArticles = await _context.AuthorArticles
.Include(a => a.Author)
.ThenInclude(p => p.Person)
.ThenInclude(pq => pq.Qualifications)
.ThenInclude(q => q.QualificationSubject)
.Include(a => a.Author)
.ThenInclude(p => p.Person)
.ThenInclude(pp => pp.Professions)
.Include(a => a.Author)
.ThenInclude(p => p.Person)
.ThenInclude(pp => pp.Professions)
.ThenInclude(prof => prof.Profession)
.Where(aa => aa.ArticleId == articleId)
.Select(s => new AuthorArticle
{
Author = new Author
{
Affiliation = s.Author.Affiliation,
AvailableAsReviewer = s.Author.AvailableAsReviewer,
Person = new Person
{
Email = s.Author.Person.Email,
FirstName = s.Author.Person.FirstName,
LastName = s.Author.Person.LastName,
MiddleName = s.Author.Person.MiddleName,
Title = s.Author.Person.Title,
FullName = s.Author.Person.FullName,
UserId = s.Author.Person.UserId,
Professions = new Collection<PersonProfession>
{
new PersonProfession
{
// using sorting here!!
Organization = s.Author.Person.Professions
.OrderByDescending(pid => pid.ProfessionId)
.FirstOrDefault().Organization,
Profession = s.Author.Person.Professions
.OrderByDescending(pid => pid.ProfessionId)
.FirstOrDefault().Profession
}
},
Qualifications = new Collection<PersonQualification>
{
new PersonQualification
{
QualificationSubject = s.Author.Person.Qualifications
.OrderByDescending(q => q.QualificationLevelId)
.FirstOrDefault().QualificationSubject,
QualificationLevelId = s.Author.Person.Qualifications
.OrderByDescending(q => q.QualificationLevelId)
.FirstOrDefault().QualificationLevelId
}
}
}
},
IsCorresponding = s.IsCorresponding,
AuthorPosition = s.AuthorPosition
}).ToListAsync();
return authorArticles;
If you simply eager loaded the entities, then at the time of projection; which means when you are selecting the items from the query, you can recreate the object that has already been provided in slightly different way. In my case, I wanted only one profession of the person out of many and same goes for the qualification of the person.
Took help of select from Another SO great answer!
Related
I am trying to flatten out my model and I am coming across an issue I can't seem to understand.
Inspection Item Model:
public class InspectionItem
{
public string InspectionItemName { get; set; }
public ICollection<Inspection> Inspections { get; set; }
}
Inspection Model:
public class Inspection
{
public InspectionItem InspectionItem { get; set; }
public string InspectionItemId { get; set; }
public string PassFail { get; set; }
public ICollection<Answer> Answers { get; set; }
public ICollection<InspectionInputAnswer> InspectionInputAnswers { get; set; }
}
The Query:
So my issue is, if an Inspection does not contain a data for BOTH Answers and InspectionInputAnswers, the whole list returns 0 records? If the Inspection contains a record for both it then returns data. I don't understand why my select many clause is working this way? I am trying to flatten Inspections and both Answers/InspectionInputAnswers.
var inspectionItemInspections = await context.InspectionItems
.Include("Inspections")
.Include("Inspections.Answers")
.Include("Inspections.InspectionInputAnswers")
.Where(a => a.InspectionItemTypeId == idFilter)
.SelectMany(x => x.Inspections
.SelectMany(y => y.Answers
.SelectMany(z => y.InspectionInputAnswers,(answer,inputAnswer) => new { answer, inputAnswer })
.Select(d => new {
Date = y.CreatedAt,
InspectionItemName = x.InspectionItemName,
InspectionItemTypeId = x.InspectionItemTypeId,
InspectedById = y.InspectedById,
PassFail = y.PassFail,
QuestionId = d.answer.QuestionId,
ValueMeasured = d.inputAnswer.ValueMeasured
})
)).ToListAsync();
So how can I write this so that the Inspections relationship does not require an entry for both Answers and InspectionInputAnswers?
Thanks!
I have the following query:
var catInclude = _db.Cat
.Where(x => x.ProvId == request.ProvId)
.Include(x => x.CatItems)
.SingleOrDefault(p => p.Id == request.ProvId
cancellationToken: cancellationToken);
As I don't want to get all properties from CatItems with Include(), I have created the following query:
var catSelect = _db.Cat
.Where(x => x.ProvId == request.ProvId)
.Select(p ==> new
{ Provider = p,
Items = p.CatItems.Select(x => new List<CatItems> { new CatItems
{ Id = x.Id, Name = x.Name, Price = x.Price } }
})})
SingleOrDefault(cancellationToken: cancellationToken);
But something is wrong in the 2nd query because here return _mapper.ProjectTo<CatDto>(cat) I get the following error:
Argument 1: cannot convert from '<anonymous type: Db.Entities.Cat Prov, System.Colletions.Generic.IEnumerable<System.Colletions.Generic.List<Models.CatItems> > Items>' to 'System.Linq.IQueryable'
Here is my CatDto:
public class CatDto
{
public int ProvId { get; set; }
public List<CatItems> CatItems { get; set; }
}
Here are my entities:
public class Prov
{
public int Id { get; set; }
public Cat Cat { get; set; }
}
public class Cat
{
public int Id { get; set; }
public int ProvId { get; set; }
public List<CatItems> CatItems { get; set; }
}
public class CatItems
{
public int Id { get; set; }
public int CatId { get; set; }
public DateTime CreatedOn { get; set; }
}
Is there a way to recreate the 2nd query and use it?
Main difference that instead of returning List of CatItems, your code returns IEnumerable<List<CatItems>> for property Items.
So, just correct your query to project to List:
var catSelect = await _db.Cat
.Where(x => x.ProvId == request.ProvId)
.Select(p => new CatDto
{
ProvId = p.ProvId,
Items = p.CatItems.Select(x => new CatItems
{
Id = x.Id,
Name = x.Name,
Price = x.Price
})
.ToList()
})
.SingleOrDefaultAsync(cancellationToken: cancellationToken);
I mean, even the exception is pretty self-explanatory. Nevertheless:
You are performing a .Select(...). It returns an Anonymous type. So, your catSelect is an anonymous type, thus the AutoMapper fails.
The quickest fix is to just cast (Cat)catSelect before mapping.
Or, you can dig deeper into how does AutoMapper play with anonymous types.
I feel like you can make most of the classes inherent Id and why is public cat CAT {get; set;} i thought you were supposed to initialize some kind of value
I have the following models
public class Person
{
public int PersonId { get; set; }
public string Name { get; set; }
public List<PersonRole> PersonRoles { get; set; }
}
public class RoleInDuty
{
public int roleInDutyId { get; set; }
public string Name { get; set; }
public int typeOfDutyId { get; set; }
public TypeOfDuty typeOfDuty { get; set; }
public List<PersonRole> PersonRoles { get; set; }
}
public class PersonRole
{
public int PersonId { get; set; }
public Person Person { get; set; }
public int RoleInDutyId { get; set; }
public RoleInDuty RoleInDuty { get; set; }
}
And now I can load all people with all their roles using the following code:
var people = _context.Persons
.Include(p => p.PersonRoles)
.ThenInclude(e => e.RoleInDuty).ToList();
But I wantn't load all data to List, I need load PersonRole according entered typeOfDutyId.
I am trying to solve this with the following code
people = _context.Persons
.Include(p => p.PersonRoles
.Where(t=>t.RoleInDuty.typeOfDutyId == Id)).ToList();
But VS throw error
InvalidOperationException: The Include property lambda expression 'p
=> {from PersonRole t in p.PersonRoles where ([t].RoleInDuty.typeOfDutyId == __typeOfDuty_typeOfDutyId_0) select
[t]}' is invalid. The expression should represent a property access:
't => t.MyProperty'. To target navigations declared on derived types,
specify an explicitly typed lambda parameter of the target type, E.g.
'(Derived d) => d.MyProperty'. For more information on including
related data, see http://go.microsoft.com/fwlink/?LinkID=746393.
As I understand I can't access property RoleInDuty.typeOfDutyId because i'm not include it yet.
I solved this problem with the following code
people = _context.Persons
.Include(p => p.PersonRoles)
.ThenInclude(e=>e.RoleInDuty).ToList();
foreach (Person p in people)
{
p.PersonRoles = p.PersonRoles
.Where(e => e.RoleInDuty.typeOfDutyId == Id)
.ToList();
}
Finally, filter in Include with ef core 5. Details in MSDN doc: https://learn.microsoft.com/en-us/ef/core/querying/related-data#filtered-include
Var people = _context.Persons
.Include(p => p.PersonRoles
.Where(t=>t.RoleInDuty.typeOfDutyId == Id))
.ToList();
var blogs = context.Blogs
.Include(e => e.Posts.OrderByDescending(post => post.Title).Take(5)))
.ToList();
devnull show the next How to filter "Include" entities in entity framework?, and there the same problem, I read it, and find the answer. Solve my problem can with the next:
var temp = _context.Persons.Select(s => new
{
Person = s,
PersonRoles= s.PersonRoles
.Where(p => p.RoleInDuty.typeOfDutyId == this.typeOfDuty.typeOfDutyId)
.ToList()
}).ToList();
I convert my DB objects to interface objects when extracting data from the database and found that when I convert objects that have child objects, I see a significant performance impact.
My model
public class NumberRange
{
public NumberRange()
{
ReservedNumbers = new HashSet<ReservedNumber>();
}
[Key]
public Guid Id {get; set;}
public virtual ICollection<ReservedNumber> ReservedNumbers { get; set; }
}
public class ReservedNumber
{
[Key]
public Guid Id {get; set;}
public string Number { get; set; }
public Guid NumberRangeId { get; set; }
public virtual NumberRange NumberRange { get; set; }
}
Establishing the foreign Key as follows
modelBuilder.Entity<NumberRange>()
.HasMany(e => e.ReservedNumbers)
.WithRequired(e => e.NumberRange)
.HasForeignKey(e => e.NumberRangeId);
And the code to convert to interface models (extension methods)
public static Models.NumberRange ToApiObject(this Model.NumberRange obj, bool includeAssociatedLocations = false, bool includeReservedNumbers = true)
{
var apiObj = new Models.NumberRange { Id = obj.Id };
if (includeReservedNumbers)
apiObj.ReservedNumbers = obj.ReservedNumbers.OrderBy(n => n.Number).AsEnumerable().Select(x => x.ToApiObject()).ToList();
else
apiObj.ReservedNumbers = new List<AUDMService.Models.ReservedNumber>();
return apiObj;
}
public static Models.ReservedNumber ToApiObject(this Model.ReservedNumber obj)
{
return new Models.ReservedNumber { Id = obj.Id, Number = obj.Number, NumberRangeId = obj.NumberRangeId };
}
If I now extract a list of NumberRanges with ReservedNumbers
var myList = context.NumberRanges.OrderBy(n => n.Name).AsEnumerable().Select(x => x.ToApiObject(includeDependentElements, true));
I see a long delay with large data sets (I have ranges that have ~1000 reserved numbers). I figured I could include ReservedNumbers
var myList = context.NumberRanges.OrderBy(n => n.Name).Include(n => n.ReservedNumbers).AsEnumerable().Select(x => x.ToApiObject(includeDependentElements, true));
yet, that has no discernible impact.
I then came up with this, which solves the performance issue
var myList = context.NumberRanges.OrderBy(n => n.Name).Include(n => n.ReservedNumbers).AsEnumerable().Select(x => x.ToApiObject(includeDependentElements, false));
addReservedNumbers(myList, context);
private void addReservedNumbers(IEnumerable<Models.NumberRange> ranges, DatabaseContext context)
{
var rangeIds = ranges.Select(x => x.Id);
var reservedNumbers = context.AccessibleReservedNumbers.Where(x => rangeIds.Contains(x.NumberRangeId)).AsEnumerable();
var convertedNumbers = reservedNumbers.Select(x => x.ToApiObject());
foreach (var range in ranges)
range.ReservedNumbers.AddRange(convertedNumbers.Where(x => x.NumberRangeId == range.Id));
}
So I'm wondering.. isn't there a way to get my first approach to run as fast as the second one?
I'm trying to execute a LINQ to Entity query which involves nested members. Below is the Entity schema. I'm keeping the code to a minimum for brevity.
public class NAVSummary
{
public virtual IList<NAVStatus> Statuses { get; set; }
}
public class NAVStatus
{
[Key]
[Column(Order = 0), ForeignKey("NAVSummary")]
public string Portfolio { get; set; }
[Key]
[Column(Order = 1), ForeignKey("NAVSummary")]
public DateTime ValueDate { get; set; }
[Key, Column(Order = 3)]
public int StatusId { get; set; }
[JsonIgnore]
public virtual NAVSummary NAVSummary { get; set; }
[ForeignKey("StatusId")]
public NAVStatusMaster StatusMaster { get; set; }
}
[Table("NAVRec_StatusMaster")]
public class NAVStatusMaster
{
[Key]
public int Id { get; set; }
public string Description { get; set; }
}
The DbContext has DbSet's for all the above Entity's.
I have the following LINQ query:
var navSummariesTemp = dbContext.NAVSummaries
.Include(n => n.Statuses)
.Include(n => n.Comments)
.Include(n => n.Statuses.Select(s => s.StatusMaster))
.Include(n => n.Extracts)
.Join(dbContext.NAVSummaries,
current => new
{
current.Portfolio,
PD = SqlFunctions.DatePart("dw", current.ValueDate) == 2 ? DbFunctions.AddDays(current.ValueDate, -3).Value :
SqlFunctions.DatePart("dw", current.ValueDate) == 1 ? DbFunctions.AddDays(current.ValueDate, -2).Value :
DbFunctions.AddDays(current.ValueDate, -1).Value
},
previous => new { previous.Portfolio, PD = previous.ValueDate },
(outer, inner) => new { outer, inner })
.Where(w => w.outer.Statuses.Count > 0)
.Select(s => new
{
DayOverDayChange = s.outer.DifferencePercent - s.inner.DifferencePercent,
IsChange = s.inner.DifferencePercent == s.outer.DifferencePercent ? false : true,
Statuses = s.outer.Statuses
}).Take(10).ToList();
The above query yields everything except the NAVStatus.StatusMaster property. Although I have included the property at the start of the query using the Include() extension, the public NAVStatusMaster StatusMaster { get; set; } is still null.
However, when I execute the below query, the StatusMaster field does get set from the database record.
var statusResult = dbContext.NAVSummaries
.Include(n => n.Statuses)
.Include(n => n.Comments)
.Include(n => n.Statuses.Select(s => s.StatusMaster))
.Include(n => n.Extracts)
.Where(n => n.Statuses.Count > 0).First();
Really appreciate if someone could help me resolve this issue.
Eager loading only works on the last part of the query. If you use projection after the includes, everything is silently lost. The only solution to your problem is to specifically put the StatusMaster inside your projection:
.Select(s => new
{
DayOverDayChange = s.outer.DifferencePercent - s.inner.DifferencePercent,
IsChange = s.inner.DifferencePercent == s.outer.DifferencePercent ? false : true,
Statuses = s.outer.Statuses,
Statusmaster = s.outer.Statuses.SelectMany(s => s.StatusMaster)
}).Take(10).ToList();
However I don't think you really want the StatusMaster to be a separate list. Perhaps it would work to do your include at the end inside the select:
.Select(s => new
{
DayOverDayChange = s.outer.DifferencePercent - s.inner.DifferencePercent,
IsChange = s.inner.DifferencePercent == s.outer.DifferencePercent ? false : true,
Statuses = s.outer.Statuses.Include(s => s.StatusMaster),
}).Take(10).ToList();
I'm not sure this will work though and can't test it as I have to go be somewhere :) The best option would be in my opinion to just make the StatusMaster virtual to enable lazy loading.
(See this link)