Ignore Soft-Deleted records within graph - linq

Using EF Core
Assuming Structure:
class Parent
{
public int Id { get; set; }
public List<Child> Children { get; set; }
public string Name { get; set; }
public bool IsDeleted { get; set; }
}
class Child
{
public int Id { get; set; }
List<GrandChild> Grandchildren { get; set; }
public string Name { get; set; }
public bool IsDeleted { get; set; }
public int ParentId { get; set; }
}
class Grandchild
{
public int Id { get; set; }
public string Name { get; set; }
public bool IsDeleted { get; set; }
public int ChildId { get; set; }
}
Records could have been soft-deleted at any level, although no descendant of a soft-deleted entity can itself not be soft-deleted (obviously).
List<Parent> nonDeletedParents = Context.Parents
.Include(p => p.Children).ThenInclude(c => c.Grandchildren)
.Where(p => !p.IsDeleted)
.ToList();
will get me non-deleted parents, but how do I exclude deleted children and grandchildren in a single query to the database?
One approach I have explored is to use an anonymous return type and then rebuild:
var result = await Context.Parents
.Where(p => !p.IsDeleted)
.Select(p =>
{
Parent = p,
AllChildren = p.Children
.Where(c => !c.IsDeleted)
.ToList(),
AllGrandchildren = p.Children
.Where(c => !c.IsDeleted)
.SelectMany(c => c.Grandchildren)
.Where(g => !g.IsDeleted)
.ToList()
}).ToList();
List<Parent> actualResults = new List<Parent>();
results.ForEach(r =>
{
Parent actualResult = r.Parent;
actualResult.Children = r.AllChildren;
actualResult.Children.ForEach(c =>
{
c.Grandchildren = r.AllGrandchildren.Where(g => g.ChildId == c.Id).ToList();
});
actualResults.Add(actualResult);
}
return actualResults;
But, seems pretty inelegant. Is there a preferable approach?

Related

Fluent LINQ EF Core - Select filtered child property

I have the classes below:
public class User
{
public Guid Id { get; set; }
public string Name { get; set; }
}
public class ParentEntity
{
public Guid Id { get; set; }
public string SomeProperty { get; set; }
public ICollection<ChildEntity> ChildEntities { get; set; }
}
public class ChildEntity
{
public Guid Id { get; set; }
public int Vote { get; set; }
public Guid UserId { get; set; }
}
public class ReturnedParentDto
{
public Guid Id { get; set; }
public string SomeProperty { get; set; }
public int Vote { get; set; }
}
I want to be able to return a full list of ParenEntities, but take an Id of the User class (UserClassId), then filter the ParentEntity's ICollection where UserUid = UserClassId, so only 1 ChildEntity is always returned. Then I would want to extract a specific field from that returned ChildEntity and merge it with the ParentEntity fields. The end result should be like the ReturnedParentDto.
I want to do it in the style like
ParentEntities.Include(v => v.ChildEntities).ToList()
That seems to be possible in EF Core 5, but my project is in 3.1.
You can do this as below
Approach 1:
var result = result = parentEntities.Include(x => x.ChildEntities.Where(y => y.UserId == userId))
.Select(x => new ReturnedParentDto {
Id = x.Id,
SomeProperty = x.SomeProperty,
Vote = x.ChildEntities.FirstOrDefault()?.Vote // userId is the variable here
});
Approach 2:
var result = parentEntities.Select(x =>
new ReturnedParentDto {
Id = x.Id,
SomeProperty = x.SomeProperty,
Vote = x.ChildEntities.FirstOrDefault(y => y.UserId == userId)?.Vote // userId is the variable here
});

Entity Framework Core 2 querying multi level collections with theniclude

I created two classes:
public class A {
public int Id { get; set; }
public ICollection<B> Bs { get; set; }
}
public class B {
public ICollection<C> C1s { get; set; }
public ICollection<C> C2s { get; set; }
}
then I tried to fetch them with ThenInclude method:
var result = context.As //public DbSet<A> As { get; set; }
.Include(a => a.Bs)
.ThenInclude(b => b.C1s)
.Include(a => a.Bs)
.ThenInclude(b => b.C2s)
.SingleOrDefaultAsync(a => a.Id.Equals(id)); //id is given
return await result;
But unfortunately both C1s and C2s collections are empty.
How to retrieve C entities which are related to B one?
I replaced .ThenInclude() methods with
.Include("Points.MasterPoints")
.Include("Points.SlavePoints")
That solved my issue.

LINQ converting anaonymous list to strongly type with date splits in groupBY

I am trying to group together daily logs into months in a linq query from a dbset, getting above or below a percentage of allowed usage.
The dbset is on
public partial class DailyUsageForYear
{
public System.DateTime DateAdded { get; set; }
public Nullable<long> NumUsers { get; set; }
public int AllowedUsageCount { get; set; }
public string Application { get; set; }
public System.Guid ApplicationID { get; set; }
public System.Guid SupplierID { get; set; }
}
I am trying the to get a list of objects like
public class UsageDisplay
{
public int Year { get; set; }
public int Month { get; set; }
public string Application { get; set; }
public int NumUsers { get; set; }
public int AllowedUsageCount { get; set; }
public Guid ApplicationID { get; set; }
}
However the select at the end of my statement does not seem to understand the field names, can anyone tell me please, what am I missing about the linq syntax?
var predicate = PredicateBuilder.False<DailyUsageForYear>();
predicate = predicate.And (x => x.NumUsers > (x.AllowedUsageCount * (DropPercentage/100)));
if (supplier != null)
{
Guid supplierGuid = new Guid (supplier.ToString());
predicate = predicate.And (x => x.SupplierID == supplierGuid);
}
if (Direction == 0)
predicate = predicate.And (x => x.NumUsers < (x.AllowedUsageCount * (Percentage/100)));
else
predicate = predicate.And (x => x.NumUsers > (x.AllowedUsageCount * (Percentage/100)));
usageDetailModel.usage = db.DailyUsageForYears.**Where**(predicate)
.**GroupBy**(x => new { x.DateAdded.Year, x.DateAdded.Month, x.Application, x.NumUsers, x.SeatCount, x.LicenceID })
.**Select**(u => new UsageDisplay() { Year = u.DateAdded.Year, Month = u.DateAdded.Month, Application = u.Application, NumUsers = u.NumUsers, SeatCount = u.SeatCount, ApplicationId = u.ApplicationID });
Inside last select u is not DailyUsageForYear its groupping object with Key field, so try change your select like this
.Select(u => new UsageDisplay() {
Year = u.Key.Year,
Month = u.Key.Month,
Application = u.Key.Application,
NumUsers = u.Key.NumUsers,
SeatCount = u.Key.SeatCount,
ApplicationId = u.Key.ApplicationID
});
also in
.GroupBy(x => new {
x.DateAdded.Year,
x.DateAdded.Month,
x.Application,
x.NumUsers,
x.SeatCount,
x.LicenceID })
i think you need x.ApplicationId instead of x.LicenceID

Group Linq and return as class

I am querying an EF entity MatchHistory:
public partial class MatchHistory
{
public System.Guid ApplicantId { get; set; }
public int BuyerId { get; set; }
public System.DateTime AppliedOn { get; set; }
public int MatchResultId { get; set; }
public System.DateTime ReapplyOn { get; set; }
public virtual MatchBuyer MatchBuyer { get; set; }
}
I currently have this linq statement in my code.
return r.Find()
.Where(x => x.AppliedOn > cutoff && x.MatchResultId == (int)MatchResult.Accepted)
.ToList();
This will return all rows of the type MatchHistory matching the criteria.
However, what I want to do is group by BuyerId and return a count by BuyerId.
Here's the class, I want to output to:
public class QuotaCount
{
public int BuyerId { get; set; }
public int Count { get; set; }
}
Haven't quite managed to get the right syntax together yet - any advice appreciated.
return r.Find()
.Where(x => x.AppliedOn > cutoff && x.MatchResultId == (int)MatchResult.Accepted)
.GroupBy(x => x.BuyerId)
.Select(x => new QuotaCount { BuyerId = x.Key, Count = x.Count() })
.ToList();
return r.Find()
.Where(x => x.AppliedOn > cutoff && x.MatchResultId == (int)MatchResult.Accepted)
.GroupBy(mh=>mh.BuyerId).Select(gr=>new QuotaCount{BuyerId=gr.Key,Count=gr.Count});

most efficient Entity Framework Code First method of flattening / projecting parent entity with specific child

I have a parent entity Widget with core members and multiple WidgetTranslation children that have language translated members i.e. Description text available in English, French, German etc.
e.g.
public class Widget
{
public int Id { get; set; }
public string Code { get; set; }
public virtual ICollection<WidgetTranslation> WidgetTranslations { get; set; }
}
public class WidgetTranslation
{
public int WidgetId { get; set; }
public virtual Widget Widget { get; set; }
public int LanguageId { get; set; }
public virtual Language Language { get; set; }
public string Name { get; set; }
public string Description { get; set; }
public string Summary { get; set; }
}
What is the most efficient method of querying the widget collection, flattening for a given LanguageId & projecting to a TranslatedWidget DTO
public class TranslatedWidget
{
public int Id { get; set; }
public string Code { get; set; }
public int LanguageId { get; set; }
public virtual Language Language { get; set; }
public string Name { get; set; }
public string Description { get; set; }
public string Summary { get; set; }
}
Given languageId I've started with
DbSet.Select(w => new TranslatedWidget
{
Id = w.Id,
Code = w.Code,
LanguageId = w.LanguageId,
Name = w.WidgetTranslations.First(wt=>wt.LanguageId == languageId).Name,
Description = w.WidgetTranslations.First(wt=>wt.LanguageId == languageId).Description,
Summary = w.WidgetTranslations.First(wt=>wt.LanguageId == languageId).Summary
});
But I've a feeling this is inefficient and won't scale for more properties on WidgetTranslation.
Thanks
Use SelectMany to flatten structures via a single join:
var widgetQuery = from w in dbSet.Widgets
from wt in w.WidgetTranslations
where wt.Language == languageId
select new TranslatedWidget
{
Id = w.Id,
Code = w.Code,
LanguageId = w.LanguageId,
Name = wt.Name,
Description = wt.Description,
Summary = wt.Summary
});
I'm assuming here that you only have a single translation for each widget in a given language.
I would move Name, Description and Summary into a nested class of your DTO...
public class TranslatedWidgetTranslation
{
public string Name { get; set; }
public string Description { get; set; }
public string Summary { get; set; }
}
public class TranslatedWidget
{
public int Id { get; set; }
public string Code { get; set; }
public int LanguageId { get; set; }
public TranslatedWidgetTranslation Translation { get; set; }
}
Then you can project into that class and need First only once which would result in only one TOP(1) subquery in SQL instead of three:
DbSet.Select(w => new TranslatedWidget
{
Id = w.Id,
Code = w.Code,
LanguageId = languageId,
Translation = w.WidgetTranslations
.Where(wt => wt.LanguageId == languageId)
.Select(wt => new TranslatedWidgetTranslation
{
Name = wt.Name,
Description = wt.Description,
Summary = wt.Summary
})
.FirstOrDefault()
});
You must use FirstOrDefault here, First is not supported in a LINQ-to-Entities projection.
If you don't want that nested type you can project into anonymous types first and then convert into your final class, but the code will be a bit longer:
DbSet.Select(w => new
{
Id = w.Id,
Code = w.Code,
LanguageId = languageId,
Translation = w.WidgetTranslations
.Where(wt => wt.LanguageId == languageId)
.Select(wt => new
{
Name = wt.Name,
Description = wt.Description,
Summary = wt.Summary
})
.FirstOrDefault()
})
.AsEnumerable()
.Select(x => new TranslatedWidget
{
Id = x.Id,
Code = x.Code,
LanguageId = x.LanguageId,
Name = x.Translation != null ? x.Translation.Name : null,
Description = x.Translation != null ? x.Translation.Description : null,
Summary = x.Translation != null ? x.Translation.Summary : null
});

Resources