fluent hibernate Transformers.AliasToBean is not working as expected - fluent-nhibernate

I have three tables. One is the master table: TableA. One table is referenced by TableA called ReferencedTable and lastly a lookup table referenced by ReferencedTable.
I have this query that returns the ten most recent objects as:
TableADTO TableAlias = null;
LookupTableDTO LookupTableAlias = null;
ReferencedDTO ReferencedAlias = null;
dtos = session.QueryOver(() => TableAlias)
.JoinAlias(() => TableAlias.Object, () =>ReferencedAlias)
.JoinAlias(() => ReferencedAlias.ObjectType, () => LookupTableAlias)
.Where(() => ReferencedAlias.PersonId == user.Id &&
(LookupTableAlias.Id != INVOICE_ID ||
LookupTableAlias.Id != FINANCIAL_ID) &&
TableAlias.Status == NEW_STATUS_FLAG &&
ReferencedAlias.ReceivedDate < DateTime.Now)
.Take(10)
.List()
.Select(dto=>
new AbreviatedDTO
{
Id = dto.Referenced.Id,
Field1 = dto.Field1,
Priority = dto.Referenced.Priority,
ReceivedDate = dto.Referenced.ReceivedDate,
Field1 = dto.Referenced.Field1,
Type = dto.Referenced.Lookup.TypeCode,
Status = dto.Status
}).ToList();
This works as expected. However, I thought the the transformation below would work too. It does bring 10 objects but the objects have all default values and are not populated (e.g. AbreviatedDTO.ReceivedDate = DateTime.Minimum). Am I doing something wrong with the QueryOver?
Any help would be appreciated.
Bill N
TableDTO TableAlias = null;
LookupTableDTO LookupTableAlias = null;
ReferencedDTO ReferencedAlias = null;
dtos = session.QueryOver(() => TableAlias)
.JoinAlias(() => TableAlias.Object, () =>ReferencedAlias)
.JoinAlias(() => ReferencedAlias.ObjectType, () => LookupTableAlias)
.Where(() => ReferencedAlias.PersonId == user.Id &&
(LookupTableAlias.Id != INVOICE_ID ||
LookupTableAlias.Id != FINANCIAL_ID) &&
TableAlias.Status == NEW_STATUS_FLAG &&
ReferencedAlias.ReceivedDate < DateTime.Now)
.SelectList(list => list
.Select(x => TableAlias.Field1)
.Select(x => ReferencedAlias.Id)
.Select(x => ReferencedAlias.Field1)
.Select(x => ReferencedAlias.ReceivedDate)
.Select(x => ReferencedAlias.Priority)
.Select(x => LookupTableAlias.TypeCode))
.TransformUsing(Transformers.AliasToBean<AbreviatedDTO>())
.Take(10)
.List<AbreviatedDTO>()

you'll need to define an alias for each selected field same as the propertyname in the resulting dto
AbreviatedDTO alias = null;
// in query
.SelectList(list => list
.Select(() => TableAlias.Field1).WithAlias(() => alias.Field1)

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

Joining tables in NHibernate

I have a query below, How can recreate this one to join the tables that will return a list of SurveyProjectNormDTO using NHibernate? Any help please?
using (var session = OpenSession()){
var projectGroupIds = session.Query<ReportingStructureNodeProjectGroups>()
.Where(x => x.NodeID == nodeId);
projectGroupIds.Fetch(x => x.ProjectGroupID).ToFuture();
var projectIds = session.Query<ProjectGroup>().Where(p => projectGroupIds.Contains(p.Id));
projectIds.Fetch(x => x.ProjectID).ToFuture();
var projectNormProjects = session.Query<SurveyProjectNorm>().Where(x => projectIds.Contains(x.SurveyProjectId));
projectNormProjects.Fetch(x => x.ShortLabels).ToFuture();
projectNormProjects.Fetch(x => x.ReportingNames).ToFuture();
projectNormProjects.Fetch(x => x.NormProject).ToFuture();
var response = new List<SurveyProjectNormDTO>();
projectNormProjects.ToList().ForEach(
p =>
{
response.Add(
new SurveyProjectNormDTO { Id = p.Id, ProjectName = p.NormProject.ProjectName, ReportingName = p.ReportingNames.Select(s => s.LocalizedText).FirstOrDefault() });
});
return response;
I am not sure if these let commands will work fine but you can try this. It will do a single hit on the database fetching the properties. Test it and let us know if it works.
var queryResult = (from p in session.Query<SurveyProjectNorm>()
let projectGroupIds = session.Query<ReportingStructureNodeProjectGroups>().Where(x => x.NodeID == nodeId).Select(x => x.Id)
let projectIds = session.Query<ProjectGroup>().Where(x => projectGroupIds.Contains(x.Id)).Select(x => x.Id)
where projectIds.Contains(p.SurveyProjectId)
select p)
.Fetch(x => x.ShortLabels)
.Fetch(x => x.ReportingNames)
.Fetch(x => x.NormProject)
.ToList();
var response = new List<SurveyProjectNormDTO>();
queryResult.ForEach(p =>
response.Add(new SurveyProjectNormDTO {
Id = p.Id,
ProjectName = p.NormProject.ProjectName,
ReportingName = p.ReportingNames.Select(s => s.LocalizedText).FirstOrDefault() }));
return result;

NHibernate projection: How to create AliasToBean projection?

I am trying to convert this inefficient query into one that projects into a dto.
Original query looks like this:
var flatFeePolicies = _session.QueryOver<FlatChargeAccessFee>(() => flatChargeAccessFeeAlias)
.JoinAlias(x => x.AgreementAccessFee, () => agreementAccessFeeAlias)
.JoinQueryOver(x => x.ClientPolicy, () => clientPolicyAlias)
.Where(y => agreementAccessFeeAlias.Agreement.Id == request.AgreementId)
.List()
.Select(x => new FlatChargeAccessFeeInfo()
{
FlatChargeAccessFeeId = x.Id,
ClientName = x.ClientPolicy.Bid.Client.Name,
PolicyNumber = x.ClientPolicy.PolicyNumber,
ClientPolicyId = x.ClientPolicy.Id,
AgreementAccessFeeId = x.AgreementAccessFee.Id,
ShouldCheckBeGenerated = x.ShouldCheckBeGenerated,
MonthlyFee = x.MontlyFeeAmount.Amount.ToString(),
PolicyYear = x.ClientPolicy.PolicyNumber.Year
})
.ToList();
I tried it like this:
var flatFeePolicies = _session.QueryOver<FlatChargeAccessFee>(() => flatChargeAccessFeeAlias)
.JoinAlias(x => x.AgreementAccessFee, () => agreementAccessFeeAlias)
.JoinQueryOver(x => x.ClientPolicy, () => clientPolicyAlias)
.Where(y => agreementAccessFeeAlias.Agreement.Id == request.AgreementId)
.SelectList(list => list
.Select(x => x.Id).WithAlias(() => feeInfo.FlatChargeAccessFeeId)
.Select(x => x.ClientPolicy.Bid.Client.Name).WithAlias(() => feeInfo.ClientName)
.Select(x => x.ClientPolicy.PolicyNumber).WithAlias(() => feeInfo.PolicyNumber)
.Select(x => x.ClientPolicy.Id).WithAlias(() => feeInfo.ClientPolicyId)
.Select(x => x.AgreementAccessFee.Id).WithAlias(() => feeInfo.AgreementAccessFeeId)
.Select(x => x.ShouldCheckBeGenerated).WithAlias(() => feeInfo.ShouldCheckBeGenerated)
.Select(x => x.MontlyFeeAmount.Amount.ToString()).WithAlias(() => feeInfo.MonthlyFee)
.Select(x => x.ClientPolicy.PolicyNumber.Year).WithAlias(() => feeInfo.PolicyYear)
)
.TransformUsing(Transformers.AliasToBean<FlatChargeAccessFeeInfo>())
.List<FlatChargeAccessFeeInfo>();
and I am getting an error that variable "x" has been referenced in scope but was not defined. What is the proper syntax to convert this?
After help from Andrew, here is the correct version that works
ClientPolicy clientPolicyAlias = null;
Client clientAlias = null;
Bid bidAlias = null;
AgreementAccessFee agreementAccessFeeAlias = null;
FlatChargeAccessFee flatChargeAccessFeeAlias = null;
FlatChargeAccessFeeInfo feeInfo = null;
var flatFeePolicies = _session.QueryOver<FlatChargeAccessFee>(() => flatChargeAccessFeeAlias)
.JoinAlias(a => a.AgreementAccessFee, () => agreementAccessFeeAlias)
.JoinQueryOver(b => b.ClientPolicy, () => clientPolicyAlias)
.JoinAlias(b=>b.Bid,()=>bidAlias)
.JoinAlias(b=>b.Client, ()=>clientAlias)
.Where(c => agreementAccessFeeAlias.Agreement.Id == request.AgreementId)
.SelectList(list => list
.Select(d => d.Id).WithAlias(() => feeInfo.FlatChargeAccessFeeId)
.Select(e => clientAlias.Name).WithAlias(() => feeInfo.ClientName)
.Select(e => clientAlias.Number).WithAlias(() => feeInfo.ClientNumber)
.Select(f => bidAlias.OptionNumber).WithAlias(() => feeInfo.BidOptionNumber)
.Select(f => bidAlias.Year).WithAlias(()=>feeInfo.PolicyYear)
.Select(g => clientPolicyAlias.Id).WithAlias(() => feeInfo.ClientPolicyId)
.Select(h => agreementAccessFeeAlias.Id).WithAlias(() => feeInfo.AgreementAccessFeeId)
.Select(j => j.ShouldCheckBeGenerated).WithAlias(() => feeInfo.ShouldCheckBeGenerated)
.Select(k => k.MontlyFeeAmount.Amount).WithAlias(()=>feeInfo.MonthlyFee)
)
.TransformUsing(Transformers.AliasToBean<FlatChargeAccessFeeInfo>())
.List<FlatChargeAccessFeeInfo>();
You're close, a few things though:
This select:
.Select(x => x.MontlyFeeAmount.Amount.ToString()).WithAlias(() => feeInfo.MonthlyFee)
will not work. QueryOver attempts to turn your code directly into SQL. If the property does not exist as a column in the database, the query won't work properly (unless you're using a mapped custom type, QueryOver can handle those)
Nested property access won't work either:
.Select(x => x.ClientPolicy.Bid.Client.Name).WithAlias(() => feeInfo.ClientName)
for a similar reason listed above. QueryOver will attempt to turn your property access directly into SQL. You'll need to explicitly join from ClientPolicy to Bid to Client.
In general, remember that you're writing code that's going to be turned into SQL. In fact, I normally write the SQL I want to generate first and then write the QueryOver that corresponds to that. Hope that helps!

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

NHibernate QueryOver association does not contain item

could someone help me to translate LINQ expression to Nhibernate QueryOver
from m in messages
where !m.Recipients.Any(rcpt => rcpt.IsDeleted && rcpt.User = user)
I tried this
var qry = Session.QueryOver<UserMessage>();
qry.Where(m => m.Recipients.Any(r => !r.IsDeleted && r.User == user));
but got
System.Exception : Unrecognised method call: System.Linq.Enumerable:Boolean Any[TSource](System.Collections.Generic.IEnumerable1[TSource], System.Func2[TSource,System.Boolean]
!m.Recipients.Any(...) translates to a "not exists" sub-query. You will need a couple of aliases to correlate the sub-query with the main query, and the sub-query will need to have a projection to make NHibernate happy.
Try something like this:
UserMessage messageAlias = null;
UserMessage recipientMessageAlias = null;
var subquery = QueryOver.Of<MessageRecipient>()
.JoinAlias(x => x.Message, () => recipientMessageAlias)
.Where(x => x.IsDeleted == true) // your criteria
.Where(x => x.User.Id == userId)
.Where(() => recipientMessageAlias.Id == messageAlias.Id) // correlated subquery
.Select(x => x.Id); // projection
var query = session.QueryOver(() => messageAlias)
.Where(Subqueries.WhereNotExists(subquery));
return query.List();
I managed to it this way:
UserMessage messageAlias = null;
var qry = Session.QueryOver<UserMessage>(() => messageAlias);
UserMessageRecipient recipientAlias = null;
var deletedbyUser = QueryOver.Of(() => recipientAlias)
.Select(x => x.Id)
.Where( () => recipientAlias.Message.Id == messageAlias.Id
&& (recipientAlias.Recipient == query.User && recipientAlias.IsDeleted))
.DetachedCriteria;
qry.Where(Subqueries.NotExists(deletedbyUser));
Try to use the Linq version with session.Query<> instead of QueryOver
var qry = Session.Query<UserMessage>();
qry.Where(m => m.Recipients.Any(r => !r.IsDeleted && r.User == user));