NHibernate QueryOver Entity with IList properties sub property - c#

Having the following classes;
public class Customer{
....
....
IList<Receipt> Receipts { get; set; }
}
public class Receipt{
....
IList<SoldProduct> Products { get; set; }
}
Having this said, my problem is that I'm trying to query the customer that has bought a specific product. When I try to execute the following code I get a NullReferenceException.
Customer c = null;
Receipt r = null;
SoldProduct sP = null;
var queryOver = Session.QueryOver(() => c)
.JoinAlias(() => c.Receipts, () => r)
.JoinAlias(() => r.SoldProducts, () => sP)
.Where(() => c.Name.IsLike(query.Search, MatchMode.Anywhere) ||
c.Surname.IsLike(query.Search, MatchMode.Anywhere) ||
c.Address.IsLike(query.Search, MatchMode.Anywhere) ||
c.Receipts.Select(receipt =>
receipt.SoldProducts.Select(product => product.Product.OldId.ToString()))
.SingleOrDefault().Single().IsLike(query.Search, MatchMode.Anywhere))
I'm just stuck right now. I might be missing a key point like here if so please let me know. If there is actually a easier way to execute this query I would really appreciate any help.

Try to apply filter to product alias:
Customer c = null;
Receipt r = null;
SoldProduct sP = null;
Product p = null;
var queryOver = Session.QueryOver(() => c)
.JoinAlias(() => c.Receipts, () => r)
.JoinAlias(() => r.SoldProducts, () => sP)
.JoinAlias(() => sp.Product, () => p)
.Where(() => c.Name.IsLike(query.Search, MatchMode.Anywhere) ||
c.Surname.IsLike(query.Search, MatchMode.Anywhere) ||
c.Address.IsLike(query.Search, MatchMode.Anywhere) ||
p.OldId.ToString().IsLike(query.Search, MatchMode.Anywhere))

Related

NHibernate QueryOver .Left.JoinAlias with additional join criteria

I have the following method:
public IEnumerable<MyRosterDTO> MyRosterGetCustomers(
IEnumerable<Guid> SalesRepIds,
IEnumerable<Guid> escrowOfficerIds,
IEnumerable<int> targetTypes,
IEnumerable<int> tagIds,
IEnumerable<Guid> custTypes,
IEnumerable<int> distListIds,
bool myExceptions)
{
customerStatusLog cslAlias = null;
customer custAlias = null;
customerOptions coAlias = null;
employee salesRepAlias = null;
Office officeAlias = null;
ListTags tagsAlias = null;
MyRosterDTO dto = null;
contactInformation contactInfo = null;
var myRosterQuery = _sms.CurrentSession.QueryOver<customer>(() => custAlias)
.JoinAlias(c => c.CustomerOptions, () => coAlias)
.Left.JoinAlias(c => c.SalesRep, () => salesRepAlias)
.JoinAlias(c => coAlias.Company, () => officeAlias)
.Left.JoinAlias(c => c.ContactInfo, () => contactInfo)
.Where(x => contactInfo.ContactTypeID == 8);
#region Where Clauses for parameters
if (myExceptions)
{
myRosterQuery.Where(c => salesRepAlias.Id == _ctx.User.Id && _ctx.User.Id != officeAlias.RepId);
}
else
{
if (SalesRepIds != null)
{
if (SalesRepIds.Contains(Guid.Empty))
{
if (SalesRepIds.Count() > 1) { myRosterQuery.Where(c => salesRepAlias.Id.IsIn(SalesRepIds.ToArray()) || salesRepAlias.Id == null); }
else
{ myRosterQuery.Where(c => salesRepAlias.Id == null); }
}
else
{ myRosterQuery.Where(c => salesRepAlias.Id.IsIn(SalesRepIds.ToArray())); }
}
}
if (escrowOfficerIds != null
&& escrowOfficerIds.Any())
{
myRosterQuery.Where(c => coAlias.PreferredEscrowOfficer.IsIn(escrowOfficerIds.ToArray()));
}
if (targetTypes != null
&& targetTypes.Any())
{
myRosterQuery.JoinAlias(c => c.CustomerStatusLog, () => cslAlias)
.Where(() => cslAlias.StatusId.IsIn(targetTypes.ToArray()));
}
if (tagIds != null
&& tagIds.Any())
{
myRosterQuery.JoinAlias(c => c.Tags, () => tagsAlias)
.Where(() => tagsAlias.Id.IsIn(tagIds.ToArray()));
}
if (custTypes != null
&& custTypes.Any())
{
myRosterQuery.Where(c => coAlias.cusTypeID.IsIn(custTypes.ToArray()));
}
if (distListIds != null
&& distListIds.Any())
{
var distCustIds = _sms.CurrentSession.Query<ListofAgents>()
.Where(loa => distListIds.Contains(loa.ListId))
.Select(loa => loa.AgentId)
.Distinct();
myRosterQuery.Where(c => c.Id.IsIn(distCustIds.ToArray()));
}
#endregion
return myRosterQuery.SelectList(list => list
.SelectGroup(c => c.Id).WithAlias(() => dto.Id)
.SelectGroup(c => c.FirstName).WithAlias(() => dto.FirstName)
.SelectGroup(c => c.LastName).WithAlias(() => dto.LastName)
.SelectGroup(() => officeAlias.Name).WithAlias(() => dto.CompanyName)
.SelectGroup(() => officeAlias.Address1).WithAlias(() => dto.Address1)
.SelectGroup(() => officeAlias.Address2).WithAlias(() => dto.Address2)
.SelectGroup(() => officeAlias.City).WithAlias(() => dto.City)
.SelectGroup(() => officeAlias.State).WithAlias(() => dto.State)
.SelectGroup(() => officeAlias.Zip).WithAlias(() => dto.Zip)
.SelectGroup(() => contactInfo.ContactData).WithAlias(() => dto.Phone)
.SelectGroup(() => salesRepAlias.FirstName).WithAlias(() => dto.SalesRepFirstName)
.SelectGroup(() => salesRepAlias.LastName).WithAlias(() => dto.SalesRepLastName)
)
.TransformUsing(Transformers.AliasToBean<MyRosterDTO>())
.List<MyRosterDTO>();
}
The query is nice and fast, but the problem arises where if the record doesn't have a contactInfo with a ContactTypeID of 8, then that record gets tossed out of the final results. (8 equates to Phone Number, fyi.)
What I need to be able to do is get the customer record, show all the customers, but provide phone numbers where available, and nothing where it's not.
The trick is that the contactInfo contains multiple types of contact information (email, phone, fax, etc), and without the .Where clause above, the output data explodes, showing a record on the screen for each contactInfo record a user has - so a user with 3 contactInfo types in the DB shows as 3 rows of output data.
This method is used to get a list of customers based on the input parameters, but instead of showing customers who don't have phone numbers, along side those who do, I only get those who have them.
If this were SQL, I'd just have a nice
LEFT JOIN ContactInfo as CI on customer.UserId = CI.UserId AND CI.ContactTypeID = 8
and my resultant query would show customers both with and without phone numbers.
You need the "with clause", which comes from HQL and had been integrated in query over:
.Left.JoinAlias(c => c.ContactInfo, () => contactInfo, () => contactInfo.ContactTypeID == 8)
This produces following SQL:
SELECT ...
FROM
...
LEFT OUTER JOIN ContactInfo as CI on (customer.UserId = CI.UserId
AND CI.ContactTypeID = 8)
In contrast to
.Left.JoinAlias(c => c.ContactInfo, () => contactInfo)
.Where(x => contactInfo.ContactTypeID == 8)
Which creates
SELECT ...
FROM
...
LEFT OUTER JOIN ContactInfo as CI on (customer.UserId = CI.UserId)
WHERE CI.ContactTypeID = 8
WITH clause in HQL looks as following:
FROM
...
left join ContactInfo ci WITH ci.ContactTypeID = 8
See this blog post by Fabio.

Select multiple child columns with the same filter

I have this Linq lambda expression which generates abnormally complex SQL select to database. Is it somehow possibility to simplify it?
var devices = db.Devices
.Where(a => a.active == true)
.Select(a => new DeviceToDisplay
{
Id = a.Id,
serialNumber = a.serialNumber,
deviceRegion = a.deviceRegion,
activeIP = a.IPaddresses.Where(b => b.active == true).Select(b => b.IPaddress1).FirstOrDefault(),
Wip = a.IPaddresses.Where(b => b.active == true).Select(b => b.W_IP).FirstOrDefault(),
Sip = a.IPaddresses.Where(b => b.active == true).Select(b => b.S_IP).FirstOrDefault(),
model = a.SPdatas.Where(c => c.model != "").OrderByDescending(c => c.collectionDate).Select(c => c.model).FirstOrDefault(),
firmware = a.SPdatas.Where(c => c.model != "").OrderByDescending(c => c.collectionDate).Select(c => c.firmware).FirstOrDefault(),
lastMPteamActivity = a.activityLogs.OrderByDescending(c => c.updatedDate).Select(c => c.updatedDate).FirstOrDefault(),
country = a.MPPinformations.Select(c => c.country).FirstOrDefault()
});
For a start, your linq query looks very complicated. Imagine how you would implement this by writing a SQL query for example.
A suggestion: you are writing things like:
a.IPaddresses.Where(b => b.active == true).
and
a.SPdatas.Where(c => c.model != "").OrderByDescending(c => c.collectionDate).
in multiple places.
Instead you could create an anonymous type. For example,
var foo = from x in sb.Devices.Where(a=> a.active)
select new { Id = x.ID,
IPAddress = a.IPaddresses.Where(b => b.active), ... }
You can then use foo to create your Devices object.
See if this is any better:
var devices = db.Devices
.Where(a => a.active == true)
.Select(a => new DeviceToDisplay {
Id = a.Id,
serialNumber = a.serialNumber,
deviceRegion = a.deviceRegion,
activeIP = a.IPaddresses.Where(b => b.active == true).FirstOrDefault(),
SPdata = a.SPdatas.Where(c => c.model != "").OrderByDescending(c => c.collectionDate).FirstOrDefault(),
lastMPteamActivity = a.activityLogs.OrderByDescending(c => c.updatedDate).Select(c => c.updatedDate).FirstOrDefault(),
country = a.MPPinformations.Select(c => c.country).FirstOrDefault()
})
.Select(a=> new DeviceToDisplay {
Id=a.Id,
serialNumber=a.serialNumber,
deviceRegion=a.deviceRegion,
activeIP=a.activeIP.IPaddress1,
Wip=a.activeIP.W_IP,
Sip=a.activeIP.S_IP,
model=a.SPdata.model,
firmware=a.SPdata.firmware,
lastMPteamActivity=a.lastMPteamActivity,
country=a.county
});

How can I do this with one QueryOver statement

This statement is using a foreach which I am trying to get rid of:
CBTAppointmentDto app = null;
ModifyAppointmentRequest alias = null;
Domain.PearsonVue.TestCenter center = null;
Exam exam = null;
var result = Session.QueryOver(() => alias)
.Where(x => x.Candidate.Id == candidateId)
.Where(x => x.EventType == "ApptCreated")
.JoinAlias(x => x.Exams, () => exam)
.JoinAlias(() => alias.TestCenter, () => center)
.SelectList(list => list
.Select(() => exam.ExamName).WithAlias(() => app.TestName)
.Select(() => exam.ExamSeriesCode)
.WithAlias(() => app.ExamSeriesCode)
.Select(() => alias.AppointmentStartTime)
.WithAlias(() => app.TestDate)
.Select(() => center.TestCenterName)
.WithAlias(() => app.TestCenterName))
.TransformUsing(Transformers.AliasToBean<CBTAppointmentDto>())
.List<CBTAppointmentDto>().ToList();
foreach (var cbtAppointmentDto in result)
{
var session = Session.QueryOver<TestSession>()
.Where(x => x.SessionName == cbtAppointmentDto.ExamSeriesCode)
.SingleOrDefault();
if (session == null) continue;
cbtAppointmentDto.TestStartDate = session.TestsStartDate;
cbtAppointmentDto.TestEndDate = session.TestsEndDate;
}
return result;
Is there a way to do it with aQueryOver statement only?
Any suggestions ?
to save roundtrips use batch selects with ToFutureValue
var sessions = results.Select(cbtAppointmentDto => Session.QueryOver<TestSession>()
.Where(x => x.SessionName == cbtAppointmentDto.ExamSeriesCode)
.FutureValue()).ToOList();
for (int i = 0; i < sessions.Count; i++)
{
var session = sessions[i].Value;
if (session != null)
{
results[i].TestStartDate = session.TestsStartDate;
results[i].TestEndDate = session.TestsEndDate;
}
}

Add correlated subqueries to Select statement using QueryOver

I'm attempting to convert a SQL statement to use QueryOver (in hopes of pre-fetching the entities part of the response) but I'm having trouble figuring out how to add a correlated subquery to the Select statement (all the examples I found have only shown using a subquery in the Where clause).
This is the query I'm trying to convert:
var pendingFeedbackStatus = Session.QueryOver<FeedbackStatus>().Where(fs => fs.Name == "pending");
var projectWhereClause = project != null ? "AND f1.project_id = " + project.Id : "";
var query = Session.CreateSQLQuery(string.Format(#"
SELECT
ft.id as FEEDBACK_TYPE_ID,
(SELECT COUNT(*) FROM FEEDBACK f1 WHERE ft.id = f1.feedback_type_id AND f1.archive_ind = 0 {0}) as ALL_FEEDBACK_COUNT,
(SELECT COUNT(*) FROM FEEDBACK f1 WHERE ft.id = f1.feedback_type_id AND f1.archive_ind = 0 {0} AND feedback_status_id = {1}) as PENDING_FEEDBACK_COUNT
FROM feedback f
RIGHT JOIN feedback_type ft on f.feedback_type_id = ft.id WHERE ft.RESTRICTED_IND = 0
GROUP BY ft.id, ft.sort_order
ORDER BY ft.sort_order",
projectWhereClause,
pendingFeedbackStatus.Id
))
.SetResultTransformer(Transformers.AliasToEntityMap);
var results = query.List<IDictionary>();
return results.Select(r =>
new FeedbackTypeSummary
{
Type = Get(Convert.ToInt32(r["FEEDBACK_TYPE_ID"])),
AllFeedbackCount = Convert.ToInt32(r["ALL_FEEDBACK_COUNT"]),
PendingFeedbackCount = Convert.ToInt32(r["PENDING_FEEDBACK_COUNT"])
}).ToList();
and here is what I have so far (which is mostly everything minus the correlated subqueries and some additional filtering added to the subqueries):
var pendingFeedbackStatus = Session.QueryOver<FeedbackStatus>().Where(fs => fs.Name == "pending");
Feedback feedbackAlias = null;
FeedbackType feedbackTypeAlias = null;
var allFeedback = QueryOver.Of<Feedback>()
.Where(f => f.Type.Id == feedbackTypeAlias.Id)
.Where(f => !f.IsArchived);
var pendingFeedback = QueryOver.Of<Feedback>()
.Where(f => f.Type.Id == feedbackTypeAlias.Id)
.Where(f => !f.IsArchived)
.Where(f => f.Status.Id == pendingFeedbackStatus.Id);
var foo = Session.QueryOver<Feedback>(() => feedbackAlias)
.Right.JoinAlias(f => f.Type, () => feedbackTypeAlias, ft => !ft.IsRestricted)
.SelectList(list => list
// TODO: Add correlated subqueries here?
.SelectGroup(() => feedbackTypeAlias.Id)
.SelectGroup(() => feedbackTypeAlias.SortOrder)
)
.OrderBy(() => feedbackTypeAlias.SortOrder).Asc;
var test = foo.List<object[]>();
I'd also like to find a way to return a full FeedbackType entity of from the statement, instead of returning feedbackTypeAlias.Id and then having to perform Type = Get(Convert.ToInt32(r["FEEDBACK_TYPE_ID"])) in a loop as I do in the original.
I felt like I looked for this 10 times, but I overlooked the .SelectSubQuery() method which provided the desired correlated subqueries. This answer tipped me off - https://stackoverflow.com/a/8143684/191902.
Here is the full QueryOvery version:
var pendingFeedbackStatus = Session.QueryOver<FeedbackStatus>().Where(fs => fs.Name == "pending").SingleOrDefault();
Domain.Feedback.Feedback feedbackAlias = null;
FeedbackType feedbackTypeAlias = null;
var allFeedback = QueryOver.Of<Domain.Feedback.Feedback>()
.Where(f => f.Type.Id == feedbackTypeAlias.Id)
.Where(f => !f.IsArchived);
var pendingFeedback = QueryOver.Of<Domain.Feedback.Feedback>()
.Where(f => f.Type.Id == feedbackTypeAlias.Id)
.Where(f => !f.IsArchived)
.Where(f => f.Status.Id == pendingFeedbackStatus.Id);
if (project != null)
{
allFeedback.Where(f => f.Project.Id == project.Id);
pendingFeedback.Where(f => f.Project.Id == project.Id);
}
FeedbackTypeSummary result = null;
var query = Session.QueryOver<Domain.Feedback.Feedback>(() => feedbackAlias)
.Right.JoinAlias(f => f.Type, () => feedbackTypeAlias, ft => !ft.IsRestricted)
.SelectList(list => list
.SelectSubQuery(allFeedback.ToRowCountQuery()).WithAlias(() => result.AllFeedbackCount)
.SelectSubQuery(pendingFeedback.ToRowCountQuery()).WithAlias(() => result.PendingFeedbackCount)
.SelectGroup(() => feedbackTypeAlias.Id).WithAlias(() => result.TypeId)
.SelectGroup(() => feedbackTypeAlias.Name).WithAlias(() => result.TypeName)
.SelectGroup(() => feedbackTypeAlias.NamePlural).WithAlias(() => result.TypeNamePlural)
.SelectGroup(() => feedbackTypeAlias.SortOrder)
)
.OrderBy(() => feedbackTypeAlias.SortOrder).Asc
.TransformUsing(Transformers.AliasToBean<FeedbackTypeSummary>());
var results = query.List<FeedbackTypeSummary>();
return results;
I also was able to populate my FeedbackTypeSummary DTO from a single query, although I couldn't find a way to alias an entity and ended up extracting a few of the needed properties from FeedbackType into FeedackTypeSummary (which is probably a better thing to do anyways).

Convert string to guid in linq join query : conditional statement

I have linq query as below
using (RMPortalEntities _RMPortalEntities = new RMPortalEntities()) {
var _RSVP_ButtonLocations = _RMPortalEntities
.tbl_RSVP_ButtonLocation
.Join(_RMPortalEntities.tbl_RSVP_Setting,
_RSVP_ButtonLocation => Guid.Parse(_RSVP_ButtonLocation.ID),
_RSVP_Setting => _RSVP_Setting.RSVP_Button_Location_ID,
(_RSVP_ButtonLocation, _RSVP_Setting) => new { _RSVP_ButtonLocation, _RSVP_Setting })
.Join(_RMPortalEntities.tbl_Event,
_RSVP_ButtonLocation_RSVP_Setting => _RSVP_ButtonLocation_RSVP_Setting._RSVP_Setting.EventID,
_Event => _Event.ID,
(_RSVP_ButtonLocation_RSVP_Setting, _Event) => new { _RSVP_ButtonLocation_RSVP_Setting, _Event })
.Where(x => x._Event.Active == true
&& x._Event.ID == _EventID)
.Select(x => new
{
RSVP_ButtonLocations = x._RSVP_ButtonLocation_RSVP_Setting._RSVP_ButtonLocation.RSVP_ButtonLocation
});
return _RSVP_ButtonLocations.FirstOrDefault().RSVP_ButtonLocations;
}
But problem is linq query does not allow me to convert string to Guid value.
Could anyone give me suggestion please?
Building on CjCoax comment you can store an instance of GUID and then use it later in the query:
using (RMPortalEntities _RMPortalEntities = new RMPortalEntities()) {
var GUID = new Guid(_RMPortalEntities.tbl_RSVP_ButtonLocation.ID);
var _RSVP_ButtonLocations = _RMPortalEntities
.tbl_RSVP_ButtonLocation
.Join(_RMPortalEntities.tbl_RSVP_Setting,
_RSVP_ButtonLocation => GUID,
_RSVP_Setting => _RSVP_Setting.RSVP_Button_Location_ID,
(_RSVP_ButtonLocation, _RSVP_Setting) => new { _RSVP_ButtonLocation, _RSVP_Setting })
.Join(_RMPortalEntities.tbl_Event,
_RSVP_ButtonLocation_RSVP_Setting => _RSVP_ButtonLocation_RSVP_Setting._RSVP_Setting.EventID,
_Event => _Event.ID,
(_RSVP_ButtonLocation_RSVP_Setting, _Event) => new { _RSVP_ButtonLocation_RSVP_Setting, _Event })
.Where(x => x._Event.Active == true
&& x._Event.ID == _EventID)
.Select(x => new
{
RSVP_ButtonLocations = x._RSVP_ButtonLocation_RSVP_Setting._RSVP_ButtonLocation.RSVP_ButtonLocation
});
return _RSVP_ButtonLocations.FirstOrDefault().RSVP_ButtonLocations;
}

Categories