Linq to NHibernate: Select multiple sums at once - sql

Is there a way to select multiple sums at once using Linq to NHibernate?
Now I have
int? wordCount = (from translation in session.Query<TmTranslation>()
where translation.SatisfiesCondition
select translation.TranslationUnit)
.Sum(x => (int?)(x.WordCount + x.NumberCount)) ?? 0;
int? tagCount = (from translation in session.Query<TmTranslation>()
where translation.SatisfiesCondition
select translation.TranslationUnit)
.Sum(x => (int?)(x.TagCount)) ?? 0;
int? characterCount = (from translation in session.Query<TmTranslation>()
where translation.SatisfiesCondition
select translation.TranslationUnit)
.Sum(x => (int?)(x.CharacterCount)) ?? 0;
which generates three different SQL queries. In SQL I can grab them all three at once, but is there a way to do this in Linq to NHibernate?
Thank you.

this should help get you started with QueryOver way...
ResultDTO dtoAlias = null; //placeholder alias variable
var dto = session.OueryOver<TmTranslation>()
.Where(x => x.SatisfiesCondition)
//Change this to the actual type of Translation property
.JoinQueryOver<Translation>(x => x.Translation)
.SelectList(list => list
//we can sum these columns individually to keep query simple,...add them together later
.SelectSum(x => x.WordCount).WithAlias(() => dtoAlias.WordCountTotal)
.SelectSum(x => x.NumberCount).WithAlias(() => dtoAlias.NumberCountTotal)
//add more select sums to the select list
)
.TransformUsing(Transformers.AliasToBean<ResultDTO>())
.SingleOrDefault<ResultDTO>();

Having multiple aggregate functions within a Select() call works and results in a single SQL command sent to the database. For example:
var result = session.Query<TmAssignment>()
.Select(a => new
{
Words = session.Query<TmTranslation>().Where(s => s.Assignment == a).Sum(u => (int?) u.WordCount) ?? 0,
Others = session.Query<TmTranslation>().Where(s => s.Assignment == a).Sum(u => (int?)(u.TagCount + u.NumberCount)) ?? 0,
}).ToList();

A potentially simpler solution I use is to do an artificial GroupBy then project to an anonymous object:
eg.
session.Query<TmTranslation>()
.Where(o => o.SatisfiesCondition)
.Select(o => o.TranslationUnit)
.GroupBy(o => 1)
.Select(o => new
{
WordCount = o.Sum(x => (int?)(x.WordCount + x.NumberCount)) ?? 0,
TagCount = o.Sum(x => (int?)(x.TagCount)) ?? 0,
CharacterCount = o.Sum(x => (int?)(x.CharacterCount)) ?? 0
})
.Single();
This also produces just a single SQL statement and reduces the duplication nicely.

Related

Getting error 'Unable to translate a collection subquery in a projection' when using union and select together in EF Core 7

I wrote a code that EF Core creates an expression for that looks like this:
DbSet<Reception>()
.Include(x => x.Employee)
.Include(x => x.ReceptionSignatures)
.Where(x => x.Employee.FirstName.Contains("mo"))
.Union(DbSet<Reception>()
.Include(x => x.Employee)
.Include(x => x.ReceptionSignatures)
.Where(x => x.Employee.PersonelId.Contains("mo")))
.Union(DbSet<Reception>()
.Include(x => x.Employee)
.Include(x => x.ReceptionSignatures)
.Where(x => x.Employee.LastName.Contains("mo")))
.Union(DbSet<Reception>()
.Include(x => x.Employee)
.Include(x => x.ReceptionSignatures)
.Where(x => x.Employee.NationId.Contains("mo")))
.OrderBy(x => x.Employee.FirstName.CompareTo("mo") == 0 ? 0 : 1)
.Select(r => new ReceptionAllDTO{
ReceptionId = r.Id,
NationId = r.Employee.NationId,
PersonelId = r.Employee.PersonelId,
FirstName = r.Employee.FirstName,
LastName = r.Employee.LastName,
Birthday = r.Employee.Birthday,
RecepDate = r.RecepDate,
Height = r.Height,
Weight = r.Weight,
ReceptionSignatures = r.ReceptionSignatures,
}
)
In Reception entity, I have a relation to Signature like this:
public virtual ICollection<Signature> ReceptionSignatures { get; set; }
but when EF Core wants to create a query for SQL, it throws this exception:
Unable to translate a collection subquery in a projection since either parent or the subquery doesn't project necessary information required to uniquely identify it and correctly generate results on the client side. This can happen when trying to correlate on keyless entity type. This can also happen for some cases of projection before 'Distinct' or some shapes of grouping key in case of 'GroupBy'. These should either contain all key properties of the entity that the operation is applied on, or only contain simple property access expressions.
It seems like you are querying for more data which is really not efficient. Its better to project your required columns using the Select() and then write a Union.
When writing the Union the number of columns Selected must be same as shown below from a code base i wrote 2 weeks ago and which works.
var billPaymentVoucherQuery = _context.Set<BillPaymentVoucher>().AsQueryable();
var billsQuery = _context.Set<Bill>().AsQueryable();
var anon_billsQuery = billsQuery.Where(w => w.InvoiceDate.Date <= filter.AsAtDate.Date)
.Where(w => w.OperationalStatus == OperationalBillStatus.Approved &&
(
w.FinancialStatus == FinancialBillStatus.Pending ||
w.FinancialStatus == FinancialBillStatus.OnHold ||
w.FinancialStatus == FinancialBillStatus.PartiallyApproved ||
w.FinancialStatus == FinancialBillStatus.Approved
))
.Select(s => new
{
VendorName = s.VendorInvoice.Vendor!.Name,
Type = "Bill",
Date = s.InvoiceDate,
Number = Convert.ToString(s.InvoiceNumber),
Amount = s.LineItemTotal + s.VATAmount
}).AsQueryable();
var anon_billPaymentVoucherQuery = billPaymentVoucherQuery
.Where(w => (
w.UpdatedOn.HasValue &&
w.UpdatedOn.Value.Date <= filter.AsAtDate.Date
)
||
(
w.UpdatedOn.HasValue == false &&
w.CreatedOn.Date <= filter.AsAtDate.Date
))
.Where(w => w.BillPaymentVoucherStatus == BillPaymentVoucherStatus.Paid)
.Select(s => new
{
VendorName = s.PaymentApprovedBill.Bill.VendorInvoice.Vendor!.Name,
Type = "Payment",
Date = s.UpdatedOn ?? s.CreatedOn,
Number = Convert.ToString(s.PaymentApprovedBill.Bill.InvoiceNumber + " | " +
s.PaymentVoucherNumber),
Amount = -s.PayAmount
}).AsQueryable();
var unionedQuery = anon_billsQuery.Union(anon_billPaymentVoucherQuery)
.Where(w => string.IsNullOrWhiteSpace(filter.Type) || w.Type == filter.Type);
int pageSize = 2;
bool hasMoreRecords = true;
var transactionData = await unionedQuery.OrderBy(w => w.VendorName)
.ThenBy(w => w.Date)
.Skip((paginator.PageNumber - 1) * pageSize)
.Take(pageSize)
.ToListAsync(token);

Query Optimization for EF Query

I am using EF core to perform a complex query. I need to use group by and pick the top result from the group and then perform filter on the top rows of each group.
When I use EF profiler, it shows that query fetched 21647 rows and the final result after the filter is 28. I assume that EF uses the where query after retrieving the columns from DB.
Here is the structure of query.
private IQueryable<long> GetAssignedIds(int? lpId = null, int? customerTenantId = null)
{
var islpAdmin = _permissionChecker.IsGranted(AppPermissions.Pages_PP_lpAdmin);
var query = (
from -------QUERY--------------------
join -------QUERY--------------------
join -------QUERY--------------------
join -------QUERY--------------------
select new
{
-----SELECT VARIABLES----------
})
.OrderBy(c => c.Priority)
.GroupBy(c => new { c.ScpId, c.OrderId })
.Select(c => c.FirstOrDefault())
.WhereIf(lpId.HasValue, c => c.lpId == lpId)
.WhereIf(islpAdmin, c => c.lpId == currentTenantId)
.WhereIf(!islpAdmin, c => c.eId == currentTenantId)
.Select(c => c.ScoId)
.Distinct();
}
How can I optimize this query? I know .Select(c => c.FirstOrDefault()) is the problem here but I'm not able to find proper alternative here.

How would one create this query using NHibernate QueryOver instead of Linq?

I need to use QueryOver instead of Linq but am struggling to recreate the following query:
public IQueryable<AuctionItem> GetLiveAuctionItems(){
repository.Query<AuctionItem>().Where(IsInActiveAuction()
}
public static Expression<Func<AuctionItem, bool>> IsInActiveAuction()
{
var now = SystemTime.Now();
var expression = PredicateBuilder.True<AuctionItem>();
return expression.And
(x => x.Lots.Any(
z => z.Auction.StartTime < now && z.Auction.EndTime > now && !z.DateWithdrawn.HasValue
&& z.DateApproved.HasValue));
}
I realise this creates subqueries but when I try to create using queryover I get errors stating projections needed.
Any help is much appreciated.
A quick draft with a clear how to steps. The first part, the subquery could look like this:
QueryOver<Lot> subQuery =
QueryOver.Of<Lot>(() => lot)
// Lot WHERE
.WhereRestrictionOn(() => lot.DateWithdrawn).IsNull
.AndRestrictionOn(() => lot.DateApproved).IsNotNull
// Auction JOIN
.JoinQueryOver<Auction>(l => l.Auction, () => auction)
// Auction WHERE
.Where(() => auction.StartTime < now)
.Where(() => auction.EndTime > now)
// AuctionItem.ID SELECT == projection
.Select(Projections.Property(() => lot.AuctionItem.ID))
;
So, this will return the AuctionItem.ID, which does meet our seraching criteria. And we can use it like this:
AuctionItem auctionItem = null;
var query = session.QueryOver<AuctionItem>(() => auctionItem)
.WithSubquery
.WhereProperty(() => auctionItem.ID)
.In(subQuery)
...

Nhibernate queryover order by random

I'm trying to write a query which returns randomly ordered results. I've found this post Linq Orderby random ThreadSafe for use in ASP.NET which gave me some basic clue how to do that. But i'm getting following exception:
variable 'x' of type 'Accomodations.DAL.Model.Generated.Accomodation' referenced from scope '', but it is not defined
Here is my query:
var query = session.QueryOver<Accomodation>()
.OrderBy(x => (~(x.Id & seed)) & (x.Id | seed)).Asc; // this is the problematic line of code
if (searchParams.District != 0)
query = query.Where(x => x.District.Id == searchParams.District);
if (searchParams.Region != 0)
query = query.Where(x => x.Region.Id == searchParams.Region);
if (searchParams.Location != 0)
query = query.Where(x => x.Location.Id == searchParams.Location);
var futureCount = query.Clone().Select(Projections.RowCount()).FutureValue<int>();
SearchAccomodationResultItem resultItemAlias = null;
var futurePage = query
.SelectList(list => list
.Select(x => x.Id).WithAlias(() => resultItemAlias.Id)
.Select(x => x.AccomodationType.Id).WithAlias(() => resultItemAlias.AccomodationTypeId)
.Select(x => x.Region.Id).WithAlias(() => resultItemAlias.RegionId)
.Select(x => x.Name).WithAlias(() => resultItemAlias.Title)
.Select(x => x.MaxCapacity).WithAlias(() => resultItemAlias.MaxCapacity)
.Select(x => x.MinPrice).WithAlias(() => resultItemAlias.MinPrice)
.Select(x => x.MinStayLength).WithAlias(() => resultItemAlias.MinStayLength)
.Select(x => x.MainImageName).WithAlias(() => resultItemAlias.ImgSrc)
)
.TransformUsing(Transformers.AliasToBean<SearchAccomodationResultItem>())
.Skip(skip)
.Take(searchParams.PageSize)
.Future<SearchAccomodationResultItem>();
searchResults = futurePage.ToList();
numberOfResults = futureCount.Value;
});
Any suggestion will be appreciated. Thanks
Here is a good example of how to do this. This is a technique that I'm currently using.
http://puredotnetcoder.blogspot.com/2011/09/nhibernate-queryover-and-newid-or-rand.html
Edit
Below is taken from the above article and I've modified it slightly to include Skip as well.
public IList<CmsTestimonial> GetRandomTestimonials(int count, int skip) {
return Session
.QueryOver<CmsTestimonial>()
.OrderByRandom()
.Take(count)
.Skip(skip)
.List();
}
To use the above approach with paging, you could store the seed for the user (on a per session basis probably) and then use the RAND(seed) function in SQL - because you use the same seed, it will generate the same sequence of pseudo-random numbers and thus allow paging

Multiple fetch in ThenFetch

I've an associated entity with <many-to-one> and that entity has two <many-to-one> that I want to fetch at once. I can achieve this by this query:
var tshead = session.Query<MainEntity>()
.Fetch(r=>r.FirstAssoc).ThenFetch(p=>p.Other)
.Fetch(r=>r.FirstAssoc).ThenFetch(p=>p.Another)
.Take(10)
.ToList();
As you can see I had to wrote twice .Fetch(r=>r.FirstAssoc)
I'm sure I can avoid this but I cant figure out how. Any idea ?
Thanks !
If you select a specific 'MainEntity' then you can do it using QueryOver like so:
FirstAssoc firstAssoc = null;
Other other = null;
Another another = null;
tshead = session.QueryOver<MainEntity>()
.Where(x => x.Id == id)
.JoinAlias(x => x.FirstAssoc, () => firstAssoc)
.JoinAlias(() => firstAssoc.Other, () => other)
.JoinAlias(() => firstAssoc.Another, () => another)
.SingleOrDefault();
I've written about it here:
http://www.philliphaydon.com/2011/04/nhibernate-querying-relationships-are-depth/
You can't do:
One-Many-Many
only One-Many-One.
I'm not sure you can do Many-Many-One, transforming that would be too difficult.