NHibernate QueryOver .Left.JoinAlias with additional join criteria - c#

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.

Related

NHibernate QueryOver Entity with IList properties sub property

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

EF Core: Distinct and OrderBy strange behavior

I migrate my old project to the new EF Core and found this problem
My old code:
private IQueryable<SeriesData> GetSeriesData(IQueryable<Series> query, long? userId = null)
{
DateTime date = DateTime.Today.AddMonths(1);
IQueryable<SeriesData> seriesDataQuery = query.Select(x => new SeriesData
{
Series = x,
Subscribed = userId.HasValue && x.Subscriptions.Any(y => y.UserId == userId),
CurrentSeasonNumber = x.Episodes.Where(z => z.ReleaseDate.HasValue && z.ReleaseDate < date).Max(y => y.SeasonNumber),
Channel = x.Channel,
Country = x.Channel.Country,
ReleaseGroups =
x.Episodes.SelectMany(z => z.Releases)
.Select(y => y.ReleaseGroup)
.Distinct() // 1
.OrderBy(y => y.Name) // 2
.Select(r => new ReleaseGroupData
{
ReleaseGroup = r,
Subscribed =
userId.HasValue &&
x.Subscriptions.Any(y => y.UserId == userId && y.ReleaseGroupId == r.Id)
}).ToList()
});
return seriesDataQuery;
}
When i execute this query i get "InvalidOperationException: Sequence contains more than one element" exception
But if i swap line 1 and 2 everything works.

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 to combine mulitiple LINQ to objects requests into single one

I would like to rewrite GetCurrentAuction into single LINQ request:
private AuctionInfo GetCurrentAuction()
{
var auctions = Auctions.List().ToList();
var liveAuction = auctions
.Where(AuctionIsLive)
.OrderBy(a => a.StartDate)
.FirstOrDefault();
if (liveAuction != null)
{
return liveAuction;
}
var openAuction = auctions
.Where(AuctionIsOpen)
.OrderBy(a => a.StartDate)
.FirstOrDefault();
if (openAuction != null)
{
return openAuction;
}
// next upcoming auction
return auctions
.Where(a => a.StartDate >= DateTime.UtcNow)
.OrderBy(a => a.StartDate)
.FirstOrDefault();
}
private bool AuctionIsLive(AuctionInfo auction)
{
// WorkflowStage is int
return auction.WorkflowStage == LIVE_WORKFLOW_STAGE;
}
private bool AuctionIsOpen(AuctionInfo auction)
{
return auction.WorkflowStage == OPEN_WORKFLOW_STAGE;
}
Could someone suggest how to achieve this? It looks like using auctions.GroupBy(a => a.WorkflowStage) doesn't bring me closer to the solution.
You can indicate preference by ordering them - something like:
return
Auctions.List().ToList() //--> ToList() not needed here?
.Where
( a =>
AuctionIsLive(a) ||
AuctionIsOpen(a) ||
a.StartDate >= DateTime.UtcNow
)
.OrderBy
( a =>
AuctionIsLive( a ) ? 0 :
AuctionIsOpen( a ) ? 1 : 2
)
.ThenBy( a => a.StartDate )
.FirstOrDefaut();
You can use very usefull ?? ( https://msdn.microsoft.com/en-us/library/ms173224.aspx ) operator and achive this:
var result = auctions.Where(AuctionIsLive).OrderBy( x => x.StartDate).FirstOrDefault() ??
auctions.Where(AuctionIsOpen).OrderBy( x => x.StartDate).FirstOrDefault() ??
auctions.Where(a => a.StartDate >= DateTime.UtcNow).OrderBy(a => a.StartDate).FirstOrDefault();
return result;
It depends on the datasource and LINQ provider you're using.
For example, if you use LINQ to SQL the preferent way of doing it would be using Expressions to save your memory and end up with the answer simular to #fankyCatz's:
return Auctions.Where(a => a.WorkflowStage == LIVE_WORKFLOW_STAGE).OrderBy(x => x.StartDate).FirstOrDefault() ??
Auctions.Where(a => a.WorkflowStage == OPEN_WORKFLOW_STAGE).OrderBy(x => x.StartDate).FirstOrDefault() ??
Auctions.Where(a => a.StartDate >= DateTime.UtcNow).OrderBy(a => a.StartDate).FirstOrDefault();
However, using only LINQ to Objects I would end up with the answer simular to #Clay's one, just would improve readability with mapping:
public static Dictionary<int, Func<AuctionInfo, bool>> Presedence =
new Dictionary<int, Func<AuctionInfo, bool>>
{
{ 0, a => a.WorkflowStage == LIVE_WORKFLOW_STAGE },
{ 1, a => a.WorkflowStage == OPEN_WORKFLOW_STAGE },
{ 2, a => a.StartDate >= DateTime.UtcNow },
};
//in your GetCurrentAuction()
return Auctions.Where(a => Presedence.Any(p => p.Value(a)))
.OrderBy(a => Presedence.First(p => p.Value(a)).Key)
.ThenBy(a => a.StartDate)
.FirstOrDefault();

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

Categories