Select performance problems in EF Core - entity-framework

_context.Homestays
.Include(x => x.CreatedUser)
.Include(x => x.UpdatedUser)
.Include(x => x.HomestayEvaluations)
.Include(x => x.HomestayContracts)
.Include(x => x.HomestayPoliceChecks)
.Include(x => x.HomestayHouseHolds)
.AsNoTracking()
.Select(x => new Homestay()
{
HomestayId = x.HomestayId,
HomestayFamily = ConstValue.GetHomestayFamilyName(x),
Address = x.Address,
Score = x.HomestayEvaluations.Any(x1 => x1.IsEvaluationActive) ? x.HomestayEvaluations.LastOrDefault(x1 => x1.IsEvaluationActive).GetScore() : 0,
Contract = x.HomestayContracts.Any(x1 => x1.IsContractActive) ? x.HomestayContracts.LastOrDefault(x1 => x1.IsContractActive).ContractDate : null,
Students = x.Students,
HouseHolders = x.HomestayHouseHolds.Count(x1 => x1.IsHouseHoldActive),
PoliceCheck = x.HomestayPoliceChecks.Any(x1 => x1.IsPoliceCheckActive) ? x.HomestayPoliceChecks.LastOrDefault(x1 => x1.IsPoliceCheckActive).PoliceCheckDate : null,
Language = x.Language,
Room = x.Room,
IsActive = x.IsActive,
CreatedDate = x.CreatedDate,
CreatedUserName = ConstValue.GetUserName(x.CreatedUser),
UpdatedDate = x.UpdatedDate,
UpdatedUserName = ConstValue.GetUserName(x.UpdatedUser)
})
.OrderByDescending(x => x.HomestayId);
Hello, Im wonder that how do I change my select query better ?
This code as below that it looks messy.
first excute any and if it is true, get last one. but is there any more short code ??
Contract = x.HomestayContracts.Any(x1 => x1.IsContractActive) ? x.HomestayContracts.LastOrDefault(x1 => x1.IsContractActive).ContractDate : null
I tried without any(), it occred error as null object if there is no data.
Please help me.
Thanks :)

Use this
Contract = x.HomestayContracts.LastOrDefault(x1 => x1.IsContractActive)?.ContractDate ?? null
Also you can disable lazy loading for this query in context's configuration
context.Configuration.LazyLoadingEnabled = false;
This way you can eliminate Include methods, cause shorter statement but not better performance. Use stored procedure for performance. Stored Procedure in Entity Framework

Related

EF Core - Order by on filtered left join properties

I would like to generate this SQL query in entity framework
SELECT FunctionalAssets.Name, FunctionalAssetStructurePath.Path
FROM FunctionalAssets
LEFT JOIN FunctionalAssetStructurePath ON FunctionalAssets.Id =
FunctionalAssetStructurePath.FunctionalAssetId AND
FunctionalAssetStructurePath.StructureConfigurationId = 'A8A41B14-0A35-45D3-2A2B-08D904A3CD0B'
ORDER BY FunctionalAssetStructurePath.Path, FunctionalAssets.Name
OFFSET 0 ROWS FETCH NEXT 100 ROWS ONLY
I try with this code (but I know that it won't work)
var result = context.FunctionalAssets
.Include(x => x.FunctionalAssetsStructurePaths.Any(y => y.StructureConfigurationId == configurationId))
.OrderBy(x => x.FunctionalAssetsStructurePaths.Select(y => y.Path))
.ThenBy(x => x.Name)
.Skip(0)
.Take(100)
.AsNoTracking()
.Select(x => x.Name)
.ToList();
Have you an idea how to do this ?
Thank you
I found a solution.
var result = context.FunctionalAssets
.GroupJoin(context.FunctionalAssetStructurePathModel.Where(y => y.StructureConfigurationId == 'A8A41B14-0A35-45D3-2A2B-08D904A3CD0B'),
fa => fa.Id,
fasp => fasp.FunctionalAssetId,
(fa, fasp) => new { fa, fasp })
.SelectMany(x => x.fasp.DefaultIfEmpty(), (x, p) => new { x.fa, p.Path })
.OrderBy(x => x.Path)
.Skip(0)
.Take(100)
.ToList();
And the generated SQL query is :
SELECT [f].[Id], [f].[Name], [t].[Path]
FROM [FunctionalAssets] AS [f]
LEFT JOIN (
SELECT [f0].[FunctionalAssetId], [f0].[Path]
FROM [FunctionalAssetStructurePath] AS [f0]
WHERE [f0].[StructureConfigurationId] = 'A8A41B14-0A35-45D3-2A2B-08D904A3CD0B'
) AS [t] ON [f].[Id] = [t].[FunctionalAssetId]
ORDER BY [t].[Path]
OFFSET 0 ROWS FETCH NEXT 100 ROWS ONLY

GroupBy + OrderByDescending, returning separate id [duplicate]

I need to get top 10 rows for each group in a table with entity framework.
Based on other solution on SO, I tried 2 things:
var sendDocuments = await context.Set<DbDocument>
.Where(t => partnerIds.Contains(t.SenderId))
.GroupBy(t => t.SenderId)
.Select(t => new
{
t.Key,
Documents = t.OrderByDescending(t2 => t2.InsertedDateTime).Take(10)
})
.ToArrayAsync();
error:
System.InvalidOperationException: 'The LINQ expression
'(GroupByShaperExpression: KeySelector: (d.SenderId),
ElementSelector:(EntityShaperExpression:
EntityType: DbDocument
ValueBufferExpression:
(ProjectionBindingExpression: EmptyProjectionMember)
IsNullable: False ) )
.OrderByDescending(t2 => t2.InsertedDateTime)' 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
> either AsEnumerable(), AsAsyncEnumerable(), ToList(), or
> ToListAsync().
and
var sendDocuments2 = await context.Set<DbDocument>
.Where(t => partnerIds.Contains(t.SenderId))
.GroupBy(t => t.SenderId)
.SelectMany(t => t.OrderByDescending(t2 => t2.InsertedDateTime).Take(10))
.ToArrayAsync();
error:
System.InvalidOperationException: 'Processing of the LINQ expression
't => t
.OrderByDescending(t2 => t2.InsertedDateTime)
.AsQueryable()
.Take(10)' by 'NavigationExpandingExpressionVisitor' failed. This may indicate either a bug or a limitation in EF Core.
Any other idea?
Update (EF Core 6.0):
EF Core 6.0 added support for translating GroupBy result set projection, so the original code for taking (key, items) now works as it should, i.e.
var query = context.Set<DbDocument>()
.Where(e => partnerIds.Contains(e.SenderId))
.GroupBy(e => e.SenderId)
.Select(g => new
{
g.Key,
Documents = g.OrderByDescending(e => e.InsertedDateTime).Take(10)
});
However flattening (via SelectMany) is still unsupported, so you have to use the below workaround if you need such query shape.
Original (EF Core 3.0/3.1/5.0):
This is a common problem, unfortunately not supported by EF Core 3.0/3.1/5.0 query translator specifically for GroupBy.
The workaround is to do the groping manually by correlating 2 subqueries - one for keys and one for corresponding data.
Applying it to your examples would be something like this.
If you need (key, items) pairs:
var query = context.Set<DbDocument>()
.Where(t => partnerIds.Contains(t.SenderId))
.Select(t => t.SenderId).Distinct() // <--
.Select(key => new
{
Key = key,
Documents =
context.Set<DbDocument>().Where(t => t.SenderId == key) // <--
.OrderByDescending(t => t.InsertedDateTime).Take(10)
.ToList() // <--
});
If you need just flat result set containing top N items per key:
var query = context.Set<DbDocument>()
.Where(t => partnerIds.Contains(t.SenderId))
.Select(t => t.SenderId).Distinct() // <--
.SelectMany(key => context.Set<DbDocument>().Where(t => t.SenderId == key) // <--
.OrderByDescending(t => t.InsertedDateTime).Take(10)
);

Entity Framework Query : Include with where

I want to get all layergroups and include (eager loading) layers that have a specific url.
Here are my tables:
- LayerGroup (id, List<Layer>layers and more...)
- Layer(id, url, List<Attribute>attributes and more...)
- Attribute (id and more...)
This is what i got so far.
var groups = _context.LayerGroups
.Where(group => group.IsActive)
.Where(g => g.Layers.All(l => l.IsActive == true))
.Where(g => g.Layers.All(l => l.Url == "example1"))
.Include(g => g.Layers)
.ThenInclude(layer => layer.Attributes)
.Include(group => group.Layers).ThenInclude(layer => layer.SearchEngines)
.ToList();
But it seems to only get groups if exactly all layers is active and have the url example1. I want to get the layers with the right Url, even if the groups have layers with other url:s.
According to this you cannot filter Include and ThenInclude collection but you can filter parent with the condition based on Include. If you need so then you can write your query as follows:
var groups = _context.LayerGroups.Where(group => group.IsActive)
.Include(g => g.Layers)
.ThenInclude(layer => layer.Attributes)
.Include(group => group.Layers)
.ThenInclude(layer => layer.SearchEngines)
.Where(g => g.Layers.Any(l => l.IsActive == true && l => l.Url == "example1"))
.ToList();

Entity Framework Core: combining multiple result objects into one collection

I am facing this linq query for entity framework core (2.0).
var result = await dbContext.Table1DbSet
.Where(t1e => t1e.Id == id && t1e.Id2 == id2)
.Select
(
t1e =>
t1e.Table2NavPropICollection.Select
(
t2e => new
{
singleObject = t2e.Table3NavPropObject.TargetObject,
enumerable1 = t2e.Table3NavPropObject.Table4NavPropObject.Table5NavPropICollection.Select(t5e => t5e.TargetObject),
enumerable2 = t2e.Table3NavPropObject.Table6NavPropObject.Table7NavPropICollection.Select(t7e => t7e.TargetObject),
enumerable3 = t2e.Table3NavPropObject.Table8NavPropICollection.SelectMany(t8e => t8e.Table9NavPropICollection.Select(t9e => t9e.TargetObject))
}
)
)
.ToListAsync();
The goal is to query all the referenced instances of TargetObject which is referenced accros ~ 10 different tables.
Currently it returns an IEnumerable where the anonymous object contains the properties singleObject, enumerable1, enumerable2, enumerable3. All those properties are of type TargetObject or IEnumerable.
Can I, and how, rewrite the query to not return anonymous objects but just an IEnumerable containing all the values?
For some reason the compiler won't let me iterate over the anonymous collection and flatten it manually.
This should do the trick if you want one array per t1e row.
var result = await dbContext.Table1DbSet
.Where(t1e => t1e.Id == id && t1e.Id2 == id2)
.Select
(
t1e =>
t1e.Table2NavPropICollection.Select
(
t2e => new[] {t2e.Table3NavPropObject.TargetObject}.Concat(
t2e.Table3NavPropObject.Table4NavPropObject.Table5NavPropICollection.Select(t5e => t5e.TargetObject)).Concat(
t2e.Table3NavPropObject.Table6NavPropObject.Table7NavPropICollection.Select(t7e => t7e.TargetObject)).Concat(
t2e.Table3NavPropObject.Table8NavPropICollection.SelectMany(t8e => t8e.Table9NavPropICollection.Select(t9e => t9e.TargetObject)))
)
)
.ToListAsync();
If you want it completely flat, you'll want to switch that Select to a SelectMany().
var result = await dbContext.Table1DbSet
.Where(t1e => t1e.Id == id && t1e.Id2 == id2)
.SelectMany
(
t1e =>
t1e.Table2NavPropICollection.Select
(
t2e => new[] {t2e.Table3NavPropObject.TargetObject}.Concat(
t2e.Table3NavPropObject.Table4NavPropObject.Table5NavPropICollection.Select(t5e => t5e.TargetObject)).Concat(
t2e.Table3NavPropObject.Table6NavPropObject.Table7NavPropICollection.Select(t7e => t7e.TargetObject)).Concat(
t2e.Table3NavPropObject.Table8NavPropICollection.SelectMany(t8e => t8e.Table9NavPropICollection.Select(t9e => t9e.TargetObject)))
)
)
.ToListAsync();
Is Add and/or Concat a possible solution?
PoC:
var a = new [] { 1,2,3 };
var result = new {
firstA = a.First(),
otherAs = a,
backwards = a.Reverse()
};
var final = new List<int>();
final.Add(result.firstA);
final.Concat(result.otherAs.Concat(result.backwards))
.Dump();

lamba expression nested .select and using navigation properties in lower level

var discount= q.Products
.SelectMany(qp => qp.ProductMods)
.SelectMany(qpm => qpm.ModDiscounts)
.Where(qmd => qmd.DiscountID == discountid)
.Sum(qmd => qmd.DiscountValue *
(q.Products.SelectMany(qpm => qpm.ProductMods).Select(qpm => qpm.Quantity)).FirstOrDefault()
);
I would like to do the above like this:
var discount= q.Products
.SelectMany(qp => qp.ProductMods)
.SelectMany(qpm => qpm.ModDiscounts)
.Where(qmd => qmd.DiscountID == discountid)
.Sum(qmd => qmd.DiscountValue * qpm.Quantity);
but I do not have access to qpm.Quantity value because it's higher level.
any suggestions?
If the relationship between ProductMods and ModDiscounts is one to many you should have a navigation property in ModeDiscount to ProductMod, so your query could be like:
var discount= q.Products
.SelectMany(qp => qp.ProductMods)
.SelectMany(qpm => qpm.ModDiscounts)
.Where(qmd => qmd.DiscountID == discountid)
.Sum(qmd => qmd.DiscountValue * qmd.ProductMod.Quantity);