How to avoid the select n + 1 problem in nested entities - entity-framework

Given the following code:
var persons = context.PERSONs.Select(
x =>
new
{
personId = x.PERSON_ID,
personName = x.PERSON_NAME,
items = x.ITEMs.Select(
y =>
new
{
itemID = y.ITEM_ID,
itemName = y.ITEM_NAME,
properties = y.PROPERTies.Select(
z =>
new
{
z.PROPERTY_ID,
z.PROPERTY_NAME
}
)
}
)
}
).ToList();
How can I avoid select n + 1 problems with it? Tried .Include("ITEMs").Include("ITEMs.PROPERTies") but it didn't help. Would expect a single query with 2 left outer joins.
Note - would like a generic answer because I'm working on an OData background where it's hard to craft queries for each entity by hand
-edit-
Database: MS SQL Server
Entity Framework version: 6
Can confirm that all properties are simple mapped properties (ints and strings actullay, no functions nor computed values)

Related

Is my Linq Query Optimal and High performance in EF Core 3.1?

can i remove vg.First().Voucher ? and replace the beter code? what is the optimal and best practice?
is convertable this code to another method? like chain method?
var query = from v in _journalLineRepository.TableNoTracking
.Include(j => j.Voucher).AsEnumerable()
group v by v.AccountId into vg
select new // <-- temporary projection with group by fields needed
{
AccountId = vg.Key,
Credit = vg.Sum(v => v.Credit),
Debit = vg.Sum(v => v.Debit),
Voucher = vg.First().Voucher
} into vg
join p in _partyRepository.TableNoTracking.Include(p => p.PartyPhones).AsEnumerable() on vg.AccountId equals p.AccountId // <-- additional join(s)
select new PartyDeptorAndCreditorViewModel
{
PartyId = p.Id,
FullName = p.FullName,
PhoneNo = p.PartyPhones.FirstOrDefault(p => p.IsActive)?.Phone,
ProjectId = vg.Voucher.ProjectId,
AccountId = vg.AccountId.Value,
Creditor = vg.Credit,
Deptor = vg.Debit,
Balance = vg.Credit - vg.Debit,
VoucherDate = vg.Voucher.VoucherDate,
VoucherRegisterDate = vg.Voucher.VoucherDate,
BalanceType =
vg.Debit > vg.Credit ? AccountingComplexEnum.ShowPartyBalanceParamSearch.Deptor.ToDisplay(DisplayProperty.Name) :
vg.Debit < vg.Credit ? AccountingComplexEnum.ShowPartyBalanceParamSearch.Creditor.ToDisplay(DisplayProperty.Name) :
AccountingComplexEnum.ShowPartyBalanceParamSearch.ZeroBalance.ToDisplay(DisplayProperty.Name),
};
I'd certainly be looking at the SQL query generated. At face value I see a few warning flags that it may not be composing a query but possibly pre-executing to in-memory processing which would be inefficient. It would firstly depend on what these .TableNoTracking methods/properties return, and the use of .AsEnumerable on the eager load joins.
Firstly, when projecting with Select, eager load joins (.Include) are not necessary. The projections will take care of the joins for you, provided it is projecting down to SQL. If you take out the .Include().AsEnumerable() calls and your query still works then it is likely projecting down to SQL. If it is no longer working then it's processing in memory and not efficiently.
Edit: Nope, the inner projection won't resolve: Regarding the .Voucher, your final projection is using 2 fields from this entity, so it stands you could replace this in the initial projection:
select new // <-- temporary projection with group by fields needed
{
AccountId = vg.Key,
Credit = vg.Sum(v => v.Credit),
Debit = vg.Sum(v => v.Debit),
Voucher = vg.Select(v => new { v.ProjectId, v.VoucherDate }).First()
} into vg
When it comes to transformations like this:
BalanceType = vg.Debit > vg.Credit
? AccountingComplexEnum.ShowPartyBalanceParamSearch.Deptor.ToDisplay(DisplayProperty.Name)
: vg.Debit < vg.Credit
? AccountingComplexEnum.ShowPartyBalanceParamSearch.Creditor.ToDisplay(DisplayProperty.Name)
: AccountingComplexEnum.ShowPartyBalanceParamSearch.ZeroBalance.ToDisplay(DisplayProperty.Name),
... inside a projection, this sends off warning flags as Linq2EF needs to compose projections down to SQL so methods/extensions like ToDisplay won't be understood. Instead, since this is based solely on the Credit/Debit amounts, I'd move this to be computed by the property in the view model:
select new PartyDeptorAndCreditorViewModel
{
PartyId = p.Id,
FullName = p.FullName,
PhoneNo = p.PartyPhones
.Where(p => p.IsActive)
.Select(p => p.Phone)
.FirstOrDefault(),
ProjectId = vg.Voucher.ProjectId,
AccountId = vg.AccountId.Value,
Creditor = vg.Credit,
Deptor = vg.Debit,
Balance = vg.Credit - vg.Debit,
VoucherDate = vg.Voucher.VoucherDate,
VoucherRegisterDate = vg.Voucher.VoucherDate
};
Then in the view model:
[Serializable]
public class PartyDebtorAndCreditorViewModel
{
// ...
public decimal Balance { get; set; }
public string BalanceType
{
get
{
return Balance < 0
? AccountingComplexEnum.ShowPartyBalanceParamSearch.Deptor.ToDisplay(DisplayProperty.Name)
: Balance > 0
? AccountingComplexEnum.ShowPartyBalanceParamSearch.Creditor.ToDisplay(DisplayProperty.Name)
: AccountingComplexEnum.ShowPartyBalanceParamSearch.ZeroBalance.ToDisplay(DisplayProperty.Name);
}
}
}

Load subentities with JPQL/EntitGraph is not working with EclipseLink and spring data

I'm trying to use EntityGraphs or JPQL to create 1 select instead of many small (sub) selects. However, there sub entities are loaded in extra selects.
Example:
#NamedEntityGraph( name = "All",
attributeNodes = {
#NamedAttributeNode( value = "bars", subgraph = "subgraph.foobars") },
subgraphs = {
#NamedSubgraph( name = "subgraph.foobars",
attributeNodes = {
#NamedAttributeNode(value = "fooBars", subgraph = "subgraph.foobar"),
#NamedAttributeNode( "mqttEndpoints" ) } ),
#NamedSubgraph( name = "subgraph.foobar",
attributeNodes = {
#NamedAttributeNode( "name" ) } ) })
public class Foo {
#OneToMany( fetch = FetchType.LAZY )
private Set<Bar> bars = Sets.newHashSet();
}
public class Bar {
#OneToMany( fetch = FetchType.LAZY )
private Set<FooBar> fooBars = Sets.newHashSet();
}
public class FooBar {
String name;
}
#EntityGraph( value = "All", type = EntityGraph.EntityGraphType.FETCH )
Optional<Foo> findById( String id);
#Query( "SELECT DISTINCT f FROM foo f"
+ " LEFT JOIN FETCH f.bar bars "
+ " LEFT JOIN FETCH bar.fooBars foobars "
+ " WHERE t.id =:id " )
Optional<Foo> readById( String id);
Log
SELECT ...
FROM foo t1
LEFT OUTER JOIN bars t0 ON (t0.bar_id = t1.id)
SELECT name
FROM foobar
WHERE ...
If i use queryhints then it works with subselect so what is wrong?
https://www.eclipse.org/eclipselink/documentation/2.5/jpa/extensions/q_left-join-fetch.htm
With hibernate this works well.
EclipseLink you'll notice always does an extra query for any relationship mapping unless you specify a fetchJoin annotation (or query hint, or modify the mapping with customizers) that tells it what to do, and this is independent of the relationship being eager or lazily fetched. Hibernate on the other hand interprets all eager access to use join. No point debating which is better - they are situational and there are good reasons to go with either solution as a generic one.
This means that if you want a join with EclipseLink on the fly, you'll have to do more than just indicate the relationship needs to be eagerly fetched, and include in query hints. If you are going with fetch graphs to optimize things, it might be a helpful to look into the other fetch types, such as batch fetching. An extra query/statement isn't always a bad thing for performance, especially as object graphs grow. The database is going to be forced to return N*M duplicate rows of Foo data, one for each Bar and FooBar combination in the results. Depending on the data size, there will be a point where it is more efficient to get the children in separate queries.

how to implement EF inner join with given filter?

SELECT DISTINCT k.* FROM [dbo].[kinds] K
INNER JOIN KindGraphic KG ON K.KindId = KG.KindId
INNER JOIN Graphics G ON KG.GraphicId = G.GraphicId
WHERE K.CategoryType = 2
AND G.IsSpecial = 1
How to write this in EF ? I am new to EF. I m using dbContex for my MVC project.
Make Note that "KindGraphic" table is mapped liked this ways
so I can not use this method https://stackoverflow.com/a/21986882/3264939
modelBuilder.Entity<Kind>()
.HasMany(c => c.Graphics)
.WithMany(g => g.Kinds)
.Map(t => t.MapLeftKey("KindId")
.MapRightKey("GraphicId")
.ToTable("KindGraphic"));
The result from your original query is some kind of complex result. So without selecting the exact columns (instead of using *), I assume the result is contained in an anonymous type like this:
{
Kind,
Graphic
}
I understand that KindGraphic is some kind of junction (join) table, so it's info is not important to include in the result (we can access KindId from Kind and GraphicId from Graphic). Here is the LINQ query:
var result = context.kinds.Where(e => e.CategoryType == 2)
.SelectMany(e=> e.Graphics.Where(g=>g.IsSpecial == 1),
(e, g) => new { Kind = e, Graphic = g} );
After your edit to use distinct, the query can be translated as you want all kinds having category type = 2 and any Graphics with IsSpecial = 1. So it should be like this:
var result = context.kinds.Where(e => e.CategoryType == 2 &&
e.Graphics.Any(g=>g.IsSpecial == 1));

Entity framework returning incorrect data from columns with same name

I have a few tables in my database which are mapped to an edmx. Using the entity framework to return data from these tables, I've encountered a problem where the value in the "name" column from one of my higher level data models is being populated into each model below it.
I'm pulling the data like this:
var query = (from a in context.things
where a.id == 1
select a);
var model = query.Select(a => new modelA()
{
Name = a.name,
Bs = a.Bs.Select(b => new modelB()
{
Name = b.name,
Cs = b.Cs.Select(c => new modelC()
{
Name = c.name,
Ds = c.Ds.Select(d => new modelD()
{
Name = d.name
})
})
})
}).FirstOrDefault();
b.name, c.name and d.name are all returning the same value as a.name despite my database definitely having different values in across these tables.
Could this be a caching issue?
Update
After upgrading the mySql connector to the latest version (6.7.4.0) I am still encountering the error.
After the upgrade it seems i can get 3 levels deep before the data in the name column starts reappearing :(

Join 2 different entities from 2 different models into a single Linq to Entities query

I have a default Entity Framework model that holds all of my default tables for my product, and that all customers share in common. However, on some customers, I have some custom tables that exist for only that customer, but they relate to the default product's tables. I have a second Entity Framework model to hold these custom tables.
My question is how can I make a Linq to Entities query using Join so I can relate the entities from my default model to the tables on my custom model? I don't mind not having the Navigation properties from the custom entity to the entities on the default model; I just need a way to query both models in a single query.
Below is the code:
using (ProductEntities oProductDB = new ProductEntities())
{
using (ProductEntitiesCustom oProductCustomDB = new ProductEntitiesCustom())
{
var oConsulta = oProductCustomDB.CTBLCustoms
.Where(CTBLCustoms => CTBLCustoms.IDWOHD >= 12)
.Join(oProductDB.TBLResources,
CTBLCustoms => new
{
CTBLCustoms.IDResource
},
TBLResources => new
{
TBLResources.IDResource
},
(CTBLCustoms, TBLResources) => new
{
IDCustom = CTBLCustoms.IDCustom,
Descricao = CTBLCustoms.Descricao,
IDWOHD = CTBLCustoms.IDWOHD,
IDResource = CTBLCustoms.IDResource,
ResourceCode = TBLResources.Code
});
gvwDados.DataSource = oConsulta;
}
}
I get a The specified LINQ expression contains references to queries that are associated with different contexts error.
EDIT
Could I merge the 2 ObjectContext into a third, and then run the Linq query?
Tks
EDIT
Below is the code that worked, using the AsEnumerable() proposed solution:
using (ProductEntities oProductDB = new ProductEntities())
{
using (ProductEntitiesCustom oProductCustomDB = new ProductEntitiesCustom())
{
var oConsulta = (oProductCustomDB.CTBLCustoms.AsEnumerable()
.Where(CTBLCustoms => CTBLCustoms.IDWOHD >= 12)
.Join(oProductDB.TBLResources,
CTBLCustoms => new
{
CTBLCustoms.IDResource
},
TBLResources => new
{
TBLResources.IDResource
},
(CTBLCustoms, TBLResources) => new
{
IDCustom = CTBLCustoms.IDCustom,
Descricao = CTBLCustoms.Descricao,
IDWOHD = CTBLCustoms.IDWOHD,
IDResource = CTBLCustoms.IDResource,
ResourceCode = TBLResources.Code
})).ToList();
gvwDados.DataSource = oConsulta;
}
}
I added the AsEnumerable() as suggested, but I had to add the ToList() at the end so I could databind it to the DataGridView.
You can't do this in L2E. You could bring this into object space with AsEnumerable(), and it would work, but possibly be inefficient.
Merging the ObjectContexts is possible, and would work, but would need to be done manually.