EF 6 complex query with Include not including relations - c#

i have a somewhat complex structure i wont get into,
but what i try doing is:
Get all ShopItems, who's SourceItem has changed,
Get and update them according to their Source/Shop data.
i conjured the following:
var query = _ctx.ShopItems
.Include(si => si.Shop)
.Include(si=>si.SourceItem)
.Include(si => si.SourceItem.Source)
.Include(si=>si.Shop.ShopType)
.GroupBy(i => i.SourceItem)
.Where(g => g.Key.LastUpdate > lastUpdate)
.OrderBy(g => g.Key.LastUpdate)
.Take(updateCountLimit);
the query seems to work, but when itterating the Groups:
groupItem.Key.Source is null.
I somewhat solved it by Removing the Include()s, saving the Entities to an Array, and explicitly loading the references using
_ctx.Entry(updatedSourceItem.Key).Reference(src=>src.Source).Load();
How can i perform the query i want without round-tripping the DB for explicit loading ?

Not sure, but it's backwards to start with ShopItems and then group by SourceItem. Try just starting with SourceItem, something like
:
var query = _ctx.SourceItems
.Include(i => i.ShopItems)
.Include(i => i.Source)
.Include(i => i.ShopItems.Select( si => si.Shop))
.Include(i => i.ShopItems.Select( si => si.Shop).ShopType)
.Where(i => i.LastUpdate > lastUpdate)
.OrderBy(i => i.LastUpdate)
.Take(updateCountLimit);
//or
var query = _ctx.SourceItems
.Include("ShopItems")
.Include("Source")
.Include("ShopItems.Shops")
.Include("ShopItems.Shops.ShopType")
.Where(i => i.LastUpdate > lastUpdate)
.OrderBy(i => i.LastUpdate)
.Take(updateCountLimit);

Related

The LINQ expression cannot be translated on Grouping

I have LINQ expression to take top 15 most frequently used answers
Here is expression
var latestAnswers = await _dbContext.TextAnswers.Include(x => x.CompanySurvey).ThenInclude(x => x.Survey)
.Where(x => x.CompanySurvey.Survey.FiscalYear == 2022)
.GroupBy(x => x.Answer)
.OrderByDescending(g => g.Count())
.Take(15)
.ToListAsync();
But I get this error
The LINQ expression 'DbSet() .Include(x => x.CompanySurvey) .ThenInclude(x => x.Survey) .Where(x => x.CompanySurvey.Survey.FiscalYear == (int?)2022) .GroupBy(x => x.Answer) .OrderByDescending(g => g .AsQueryable() .Count()) .Take(__p_0)' could not be translated. Either rewrite the query in a form that can be translated, or switch to client evaluation explicitly by inserting a call to 'AsEnumerable', 'AsAsyncEnumerable', 'ToList', or 'ToListAsync'. See https://go.microsoft.com/fwlink/?linkid=2101038 for more information.
I can't understood why it cant be translated.
How I can fix this?
EF Core 6 has to support such queries, but looks like full implementation support is moved to EF Core 7
After conversation looks like it is not needed to get grouped records from database, but just grouping key and Count
var latestAnswers = await _dbContext.TextAnswers
.Where(x => x.CompanySurvey.Survey.FiscalYear == 2022)
.GroupBy(x => x.Answer)
.Select(g => new { Answer = g.Key, Count = g.Count() })
.OrderByDescending(x => x.Count)
.Take(15)
.ToListAsync();
I rewrite my code like this
var latestAnswersQuery = await _dbContext.TextAnswers
.Include(x => x.CompanySurvey).ThenInclude(x => x.Survey)
.Where(x => x.CompanySurvey.Survey.FiscalYear == 2022)
.ToListAsync();
var latestAnswers = latestAnswersQuery.GroupBy(x => x.Answer).OrderByDescending(g => g.Count()).Take(15);
return latestAnswers;
And now everything great

How to include multiple entities with one many-to-many in between

I have 3 tier entities. T <-> TI -> E also TI <- R (where <-> is m..n and -> 1..m)
I need to select a single T with the TI, E and R
without E it works:
var t = dbContext.T
.Include(i => i.TIs)
.ThenInclude(i => i.I)
.ThenInclude(i => i.R)
.Single(i => i.Id == id);
is it possible to include E without resorting to a for loop?
EDIT thanks to both answers from Canolyb and EspressoBins, although not exactly working, but pushed me to the right track. So the working code is:
var t = dbContext.T
.Include(i => i.TIs)
.ThenInclude(i => i.I)
.ThenInclude(i => i.R)
.Include(i => i.TIs)
.ThenInclude(i => i.I.Es)
.Single(i => i.Id == id);
I'm not sure if this is what you're looking for, but to the best of my knowlesge includes can be cascaded like this if you need two ThenIncludes on the same relation.
var t = dbContext.T
.Include(i => i.TIs)
.ThenInclude(i => i.I)
.Include(i => i.TIs)
.ThenInclude(i => i.R)
.Single(i => i.Id == id);
If this is not what you're looking for, it would benefit to see your Entity relations / fluent confiugation.
I had to look at some old code of mine with a similar problem to solve, and forgive me if my relationship directions aren't exact to yours, but does this help you get any closer?
Without using the .ThenInclude()
var result = dbContext.T
.Include(t => t.TI)
.Include(t => t.TI.E)
.Include(t => t.TI.R)
.Where(t => t.Id == id)
.Single();

How to partially project a child object with many fields in nHibernate

I have the following nHibernate query that select a course based on its course id and then return selected fields for the course object on the initial fetch, and the query executes with no issues.
MatchMode option = ...
CourseItem courseAlias = null;
TeacherItem teacherAlias = null;
var query = session.QueryOver<CourseItem>()
.JoinAlias(c => c.Teacher, () => teacherAlias)
.Where(c => c.CourseID.IsInsensitiveLike(strNumber, option))
.SelectList(list => list
.Select(c => c.CourseID).WithAlias(() => courseAlias.CourseID)
.Select(c => c.IsActive).WithAlias(() => courseAlias.IsActive)
.Select(c => c.CourseDesc).WithAlias(() => courseAlias.CourseDesc)
.Select(c => c.Teacher).WithAlias(() => courseAlias.Teacher))
.TransformUsing(Transformers.AliasToBean<CourseItem>())
.List<CourseItem>();
I wanted to go a step further with the query to only return a partial teacher object, let's say i just wanted to return the ID and Name. So, I updated the projected list to as follows:
var query = session.QueryOver<CourseItem>()
.JoinAlias(c => c.Teacher, () => teacherAlias)
.Where(c => c.CourseID.IsInsensitiveLike(strNumber, option))
.SelectList(list => list
.Select(c => c.CourseID).WithAlias(() => courseAlias.CourseID)
.Select(c => c.IsActive).WithAlias(() => courseAlias.IsActive)
.Select(c => c.CourseDesc).WithAlias(() => courseAlias.CourseDesc)
.Select(c => c.Teacher.ID).WithAlias(() => courseAlias.Teacher.ID)
.Select(c => c.Teacher.Name).WithAlias(() => courseAlias.Teacher.Name))
.TransformUsing(Transformers.AliasToBean<CourseItem>())
.List<CourseItem>();
The query doesn't work because nHibernate has no idea how to resovled based on Teacher.ID and Teacher.Name. Any thoughts on whether it's possible to NOT fetch the entire child object back to a parent object?
I've tried the following query and it works this is not my fully desired outcome
var query = session.QueryOver<CourseItem>(() => courseAlias)
.JoinAlias(() => courseAlias.Teacher, () => teacherAlias)
.Where(() => courseAlias.CourseID.IsInsensitiveLike(strNumber, option))
.SelectList(list => list
.Select(() => courseAlias.CourseID)
.Select(() => courseAlias.IsActive)
.Select(() => courseAlias.CourseDesc)
.Select(() => teacher.ID)
.Select(() => teacher.Name))
.List<object[]>();
I can query the right values but unable to transform it back correctly to the Course / teacher data type.
Any thoughts?
thanks!
We can indeed use custom transformer. There is one, which I am using for a really very very deep projections (inlcuding dynamic objects - 5.1.13. component, dynamic-component)
DeepTransformer<TEntity>
Take it (if needed adjust it) and your final query could be like this
// just the last lines are different
var query = session.QueryOver<CourseItem>()
.JoinAlias(c => c.Teacher, () => teacherAlias)
.Where(c => c.CourseID.IsInsensitiveLike(strNumber, option))
.SelectList(list => list
.Select(c => c.CourseID).WithAlias(() => courseAlias.CourseID)
.Select(c => c.IsActive).WithAlias(() => courseAlias.IsActive)
.Select(c => c.CourseDesc).WithAlias(() => courseAlias.CourseDesc)
// the native WitAlias would not work, it uses expression
// to extract just the last property
//.Select(c => c.Teacher.ID).WithAlias(() => courseAlias.Teacher.ID)
//.Select(c => c.Teacher.Name).WithAlias(() => courseAlias.Teacher.Name))
// so we can use this way to pass the deep alias
.Select(Projections.Property(() => teacherAlias.ID).As("Teacher.ID"))
.Select(Projections.Property(() => teacherAlias.Name).As("Teacher.Name"))
// instead of this
// .TransformUsing(Transformers.AliasToBean<CourseItem>())
// use this
.TransformUsing(new DeepTransformer<CourseItem>())
And in case, that your aliases do match to property names, that transformer will built the object tree...

Loading all the children entities with entity framework

I have a data model like this
I would like to load all the related entities from a Reconciliation into a Reconciliation object.
For now the only way I could find to load all the related entites to a single Recon is in multiple Lists. But I want to load every related entities in a Reconciliation object. If possible in an elegant way.
Reconciliation recon = db.Reconciliations
.Where(r => r.ReconNum == 382485).First();
List<ReconciliationDetail> reconDetails = recon.ReconciliationDetails.ToList();
List<JrnlEntryDetail> jrnlDetails = reconDetails.Select(r => r.JrnlEntryDetail).ToList();
List<JrnlEntry> jrnl = jrnlDetails.Select(j => j.JrnlEntry).ToList();
List<ARInvoice> invoices = jrnl.SelectMany(j => j.ARInvoices).ToList();
List<ARInvoiceDetail> invoicesDetail = invoices
.SelectMany(i => i.ARInvoiceDetails).ToList();
List<ARCredMemo> credmemos = jrnl.SelectMany(j => j.ARCredMemoes).ToList();
List<ARCredMemoDetail> credmemosDetail = credmemos
.SelectMany(c => c.ARCredMemoDetails).ToList();
List<IncomingPay> incomingPays = jrnl.SelectMany(j => j.IncomingPays).ToList();
List<IncomingPayDetail> incomingPaysDetail = incomingPays
.SelectMany(i => i.IncomingPayDetails).ToList();
// ... and so on for outgoing pays, AP Invoices AP Cred Memo ...etc
I have also tried to load it with Include and Select but I get this exception :
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.
And I don't get how I could load every childs of JrnlEntry using Include and Select
Reconciliation recon = db.Reconciliations
.Where(r => r.ReconNum == 382485)
.Include(r => r.ReconciliationDetails
.Select(d => d.JrnlEntryDetail)
.Select(jd => jd.JrnlEntry)
.SelectMany(j => j.ARInvoices).SelectMany(i => i.ARInvoiceDetails))
Edit
Managed to do it this way too but it's not very beautiful :
Reconciliation recon = db.Reconciliations
.Where(r => r.ReconNum == 382485)
.Include(r => r.ReconciliationDetails.Select(rd => rd.JrnlEntryDetail)
.Select(jd => jd.JrnlEntry).Select(j => j.ARInvoices.Select(i => i.ARInvoiceDetails)))
.Include(r => r.ReconciliationDetails.Select(rd => rd.JrnlEntryDetail)
.Select(jd => jd.JrnlEntry).Select(j => j.ARCredMemoes.Select(c => c.ARCredMemoDetails)))
.Include(r => r.ReconciliationDetails.Select(rd => rd.JrnlEntryDetail)
.Select(jd => jd.JrnlEntry).Select(j => j.IncomingPays.Select(i => i.IncomingPayDetails)))
.Include(r => r.ReconciliationDetails.Select(rd => rd.JrnlEntryDetail)
.Select(jd => jd.JrnlEntry).Select(j => j.OutgoingPays.Select(o => o.OutgoingPayDetails)))
.Include(r => r.ReconciliationDetails.Select(rd => rd.JrnlEntryDetail)
.Select(jd => jd.JrnlEntry).Select(j => j.APInvoices.Select(o => o.APInvoiceDetails)))
.Include(r => r.ReconciliationDetails.Select(rd => rd.JrnlEntryDetail)
.Select(jd => jd.JrnlEntry).Select(j => j.APCredMemoes.Select(o => o.APCredMemoDetails)))
.Include(r => r.ReconciliationDetails.Select(rd => rd.JrnlEntryDetail)
.Select(jd => jd.JrnlEntry).Select(j => j.JrnlEntryDetails))
There are two ways to perform Eager Loading in Entity Framework:
ObjectQuery.Include Method
Explicit loading using either DbEntityEntry.Collection Method or DbEntityEntry.Reference Method along with their respective Load methods
There are also manners to write Raw SQL Queries against database:
DbSet.SqlQuery Method deals with entities
Database.SqlQuery Method deals with arbitrary types
Database.ExecuteSqlCommand Method for arbitrary DDL/DML
For the case, when you're attempting to load nearly entire database, it would be good idea to execute dedicated store procedure against it.
Try with just .Include(r => r.ReconciliationDetails) initially. Then add the .Select() statements one-by-one. At what point does the exception reappear? The .SelectMany() call looks a bit suspicious to me!
A second question that might help identify the problem... After you run the code that contains all the ToList() calls, is your recon entity complete? i.e. are all its navigation properties populated? This should be the case because of the automatic 'fixup' behavior of Entity Framework.
With EF, sometimes it is more efficient to load a complex object graph with several calls rather than chained Include() calls. Check the generated SQL and see what is most efficient in your case.
Not sure if it's to late but could you benefit from structuring your code such as
var acctName = "someName";
var detailList = _repository.Include(e => e.JrnlEntryDetail).Filter(c => c.JrnlEntryDetail.Any(e => e.AcctName == acctName)).Get().ToList();

could not resolve property: PropertyName of: Class error in NHibernate

I'm using NHibernate first time and in this line it throws exception for me
var total = session
.QueryOver<Comment>().Where(p => p.Entry.Author == username)
.ToRowCountQuery()
.FutureValue<int>();
var results = session
.QueryOver<Comment>().Where(p => p.Entry.Author == username)
.Fetch(x => x.Entry).Eager()
.OrderBy(x => x.Posted).Desc()
.Skip(skip)
.Take(take)
.List();
The Exception is
could not resolve property: Entry.Author of: FunnelWeb.Model.Comment
I guess, the problem is that Entry object doesn't loaded here. How can I do that trick with Nhibernate?
QueryOver is just a strongly-typed wrapper for Criteria, and doesn't allow implicit deep references.
You'd have to use:
session.QueryOver<Comment>()
.JoinQueryOver(x => x.Entry)
.Where(x => x.Author == username)
Or you can use Query<> instead (LINQ) which will work with the syntax you've tried.
You'd need to JoinAlias or JoinQueryOver. I have an example below on how to use Future queries...
Entry entryAlias = null;
var q = session.QueryOver<Comment>()
.JoinAlias(x => x.Entry, () => entryAlias)
.Where(() => entryAlias.Author == username);
var totalFuture = q.ToRowCountQuery().FutureValue<int>(); //ToRowcountQuery clones the query, we can reuse it for results
var resultsFuture = q
//.Fetch(x => x.Entry).Eager() //already joined
.OrderBy(x => x.Posted).Desc()
.Skip(skip)
.Take(take)
.Future<Comment>();
var results = resultsFuture.ToList(); //both future queries are executed in the same batch
var total = totalFuture.Value;

Categories

Resources