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

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();

Related

Optimizing loading complex tree from DB with EF Core

Initial query structure:
var a = await _Db.A
.Include(a => a.B)
.ThenInclude(b => b.C1)
.ThenInclude(c1 => c1.D)
.Include(a => a.B)
.ThenInclude(b => b.C2)
.SingleOrDefaultAsync(a => a.Id == id);
The problem is that because B has two sub trees the result of the generated query has a row for each combination of the values from these subtrees. Two separate queries would give two results that are in total much smaller. So I tried, to load the second tree separately with
an explicit load.
var a = await _Db.A
.Include(a => a.B)
.ThenInclude(b => b.C1)
.ThenInclude(c1 => c1.D)
.SingleOrDefaultAsync(a => a.Id == id);
_Db.Entry(a).Collection(a => a.B).Query()
.Include(b => b.C2);
How ever the explicit load does nothing, I guess because B is already filled and it never checks if C2 needs filling. So is there anyway to get this to work?
The database provider is Npgsql. An Npgsql specific solution would be acceptable but a native EF core solution would be preferred.

Multiple levels with theninclude not returning all rows unless I query the detail

I'm using .net core 2.0 (I know) and I've got nested data I'm pulling from sql.
The weird thing is that some of the data isn't showing up unless I explicitly search for it.
var record = db.Records
.Include(x => x.ParentLabels)
.Include(x => x.TopChildren)
.ThenInclude(x => x.LevelTwoChildren)
.ThenInclude(x => x.LevelThreeChildren)
.ThenInclude(x => x.LevelTwoChildren.LevelTwoLabels)
.FirstOrDefault(x => x.Id.Equals(id));
The weird thing is that some of the LevelTwoChildren.Labels data does not show up, UNLESS I add the following:
var t = record.TopChildren.FirstOrDefault();
var tt = t.LevelTwoChildren.FirstOrDefault(x => x.Id.Equals(id2));
I've tried doing a foreach through all the record levels but that doesn't help.
Any ideas?
I guess it is happening because of .ThenInclude(x => x.LevelTwoChildren.LevelTwoLabels). Include statements should include one property at a time. What you should do is:
var record = db.Records
.Include(x => x.ParentLabels)
.Include(x => x.TopChildren)
.ThenInclude(x => x.LevelTwoChildren)
.ThenInclude(x => x.LevelThreeChildren)
.Include(x => x.TopChildren)
.ThenInclude(x => x.LevelTwoChildren)
.ThenInclude(x => x.LevelTwoLabels)
.FirstOrDefault(x => x.Id.Equals(id));

Select from many tables - Entity Framework

I have that table construction and code sample:
var Tasks = db.Users
.Where(t => t.Id == 1)
.Where(t => t.Tables.Where(a => a.Id == 1)))
.Select(a => a.Tasks.Select(a => a.Tasks.Text));
This code don't work, how can I get Tasks.Text when I using many where questions?
Thanks

EF 6 complex query with Include not including relations

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);

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();

Categories

Resources