Fluent NHibernate Querying: OrderBy column of joined table - c#

I've been struggling quite some time now with trying to query some stuff using NHibernate.
I've managed to do some very simple queries, but now I'm trying to build some custom pagination through NHibernate.
What I want to do is twofold.
First (1) off, I want to be able to order my resultset based on a column from a joined table.
Let's say I have a 'Person' table that has a one-to-one (actually many-to-one) reference with an 'Address' table.
I want to query over the 'Person' table (using its fields), but want to sort using fields from the 'Address' table.
How do I do this? I've tried the following two ways
var resultset = GetCurrentSession().QueryOver<Person>()
.Where(x => x.Name == "...")
.JoinQueryOver<Address>(x => x.Address)
.OrderBy(x => x.HouseNumber).Desc
.Skip(...)
.Take(...)
.List();
// ==> could not execute query
Person person = null;
Address address = null;
var resultset = GetCurrentSession().QueryOver<Person>()
.JoinAlias(() => person, () => address.Persons)
.Where(() => person.Name == "...")
.OrderBy(() => address.HouseNumber).Asc
.Skip(...)
.Take(...)
.List();
// ==> could not resolve property: person of: Person
My second (2) question is, I would like to split the ordering up.
Based on conditional statements, I want to add a different OrderBy.
Can I just do it like so?
var query = GetCurrentSession().QueryOver<Person>()
.Where(x => x.Name == "...")
.JoinQueryOver<Address>(x => x.Address);
if(foo)
{
query = query.OrderBy(() => address.HouseNumber).Asc
}
else if (bar)
{
query = query.OrderBy(() => address.Street).Desc
}
var resultset = query
.Skip(...)
.Take(...)
.List();
Thanks a lot!

All of your examples should work, with the small caveat that you need to assign your aliases properly in order to use them (the message could not resolve property: person of: Person indicates that person was never actually set as the alias). For example:
Person person = null;
Address address = null;
// Assign the person alias using () => person
var resultset = GetCurrentSession().QueryOver<Person>(() => person)
.JoinAlias(() => person.Address, () => address) // Assign the address alias here
.Where(() => person.Name == "...")
.OrderBy(() => address.HouseNumber).Asc
.Skip(...)
.Take(...)
.List();
This should work fine.
As for your second question, what you're trying to achieve is very possible, an in fact a great reason to use QueryOver to begin with. You just need a few tweaks:
var query = GetCurrentSession().QueryOver<Person>()
.Where(x => x.Name == "...")
.JoinQueryOver<Address>(x => x.Address, () => address); // assign address alias
if(foo)
{
query.OrderBy(() => address.HouseNumber).Asc(); // <-- use the *method* .Asc()
}
else if (bar)
{
query.OrderBy(() => address.Street).Desc(); // <-- use the *method* .Desc()
}
Note that since .Asc() and .Desc() actually modify the query, you don't need to reassign the query. Be sure to use the methods .Asc() and .Desc() so that you don't get a build error.

Here is dynamic sorting
string orderColumn = "";
bool isAsc = (param.sSortDir_0 == "asc");
switch (param.iSortCol_0)
{
case 1: orderColumn = "Approved"; break;
case 2: orderColumn = "CreateDate"; break;
case 3: orderColumn = "Rate"; break;
}
result.UnderlyingCriteria.AddOrder(new NHibernate.Criterion.Order(orderColumn, isAsc));

Related

LINQ efficiency

Consider the following LINQ statements:
var model = getModel();
// apptId is passed in, not the order, so get the related order id
var order = (model.getMyData
.Where(x => x.ApptId == apptId)
.Select(y => y.OrderId));
var orderId = 0;
var orderId = order.LastOrDefault();
// see if more than one appt is associated to the order
var apptOrders = (model.getMyData
.Where(x => x.OrderId == orderId)
.Select(y => new { y.OrderId, y.AppointmentsId }));
This code works as expected, but I could not help but think that there is a more efficient way to accomplish the goal ( one call to the db ).
Is there a way to combine the two LINQ statements above into one? For this question please assume I need to use LINQ.
You can use GroupBy method to group all orders by OrderId. After applying LastOrDefault and ToList will give you the same result which you get from above code.
Here is a sample code:
var apptOrders = model.getMyData
.Where(x => x.ApptId == apptId)
.GroupBy(s => s.OrderId)
.LastOrDefault().ToList();
Entity Framework can't translate LastOrDefault, but it can handle Contains with sub-queries, so lookup the OrderId as a query and filter the orders by that:
// apptId is passed in, not the order, so get the related order id
var orderId = model.getMyData
.Where(x => x.ApptId == apptId)
.Select(y => y.OrderId);
// see if more than one appt is associated to the order
var apptOrders = model.getMyData
.Where(a => orderId.Contains(a.OrderId))
.Select(a => a.ApptId);
It seems like this is all you need:
var apptOrders =
model
.getMyData
.Where(x => x.ApptId == apptId)
.Select(y => new { y.OrderId, y.AppointmentsId });

Flattening Complex LINQ to SQL

I have a somewhat complex LINQ to SQL query that I'm trying to optimise (no, not prematurely, things are slow), that goes a little bit like this;
IQueryable<SearchListItem> query = DbContext.EquipmentLives
.Where(...)
.Select(e => new SearchListItem {
EquipmentStatusId = e.EquipmentStatuses.FirstOrDefault(s => s.DateTo == null).Id,
StatusStartDate = e.EquipmentStatuses.FirstOrDefault(s => s.DateTo == null).DateFrom,
...
});
The where clauses aren't important, they don't filter EquipmentStatuses, happy to include if someone thinks they're required.
This is on quite a large set of tables and returns a fairly details object, there's more references to EquipmentStatuses, but I'm sure you get the idea. The problem is that there's quite obviously two sub-queries and I'm sure that (among some other things) is not ideal, especially since they are exactly the same sub-query each time.
Is it possible to flatten this out a bit? Perhaps it's easier to do a few smaller queries to the database and create the SearchListItem in a foreach loop?
Here's my take given your comments, and with some assumptions I've made
It may look scary, but give it a try, with and without the ToList() before the GroupBy()
If you have LinqPad, check the SQL produced, and the number of queries, or just plug in the SQL Server Profiler
With LinqPad you could even put a Stopwatch to measure things precisely
Enjoy ;)
var query = DbContext.EquipmentLives
.AsNoTracking() // Notice this!!!
.Where(...)
// WARNING: SelectMany is an INNER JOIN
// You won't get EquipmentLive records that don't have EquipmentStatuses
// But your original code would break if such a case existed
.SelectMany(e => e.EquipmentStatuses, (live, status) => new
{
EquipmentLiveId = live.Id, // We'll need this one for grouping
EquipmentStatusId = status.Id,
EquipmentStatusDateTo = status.DateTo,
StatusStartDate = status.DateFrom
//...
})
// WARNING: Again, you won't get EquipmentLive records for which none of their EquipmentStatuses have a DateTo == null
// But your original code would break if such a case existed
.Where(x => x.EquipmentStatusDateTo == null)
// Now You can do a ToList() before the following GroupBy(). It depends on a lot of factors...
// If you only expect one or two EquipmentStatus.DateTo == null per EquipmentLive, doing ToList() before GroupBy may give you a performance boost
// Why? GroupBy sometimes confuses the EF SQL generator and the SQL Optimizer
.GroupBy(x => x.EquipmentLiveId, x => new SearchListItem
{
EquipmentLiveId = x.EquipmentLiveId, // You may or may not need this?
EquipmentStatusId = x.EquipmentStatusId,
StatusStartDate = x.StatusStartDate,
//...
})
// Now you have one group of SearchListItem per EquipmentLive
// Each group has a list of EquipmenStatuses with DateTo == null
// Just select the first one (you could do g.OrderBy... as well)
.Select(g => g.FirstOrDefault())
// Materialize
.ToList();
You don't need to repeat the FirstOrDefault. You can add an intermediate Select to select it once and then reuse it:
IQueryable<SearchListItem> query = DbContext.EquipmentLives
.Where(...)
.Select(e => e.EquipmentStatuses.FirstOrDefault(s => s.DateTo == null))
.Select(s => new SearchListItem {
EquipmentStatusId = s.Id,
StatusStartDate = s.DateFrom,
...
});
In query syntax (which I find more readable) it would look like this:
var query =
from e in DbContext.EquipmentLives
where ...
let s = e.EquipmentStatuses.FirstOrDefault(s => s.DateTo == null)
select new SearchListItem {
EquipmentStatusId = s.Id,
StatusStartDate = s.DateFrom,
...
});
There is another problem in your query though. If there is no matching EquipmentStatus in your EquipmentLive, FirstOrDefault will return null, which will cause an exception in the last select. So you might need an additional Where:
IQueryable<SearchListItem> query = DbContext.EquipmentLives
.Where(...)
.Select(e => e.EquipmentStatuses.FirstOrDefault(s => s.DateTo == null))
.Where(s => s != null)
.Select(s => new SearchListItem {
EquipmentStatusId = s.Id,
StatusStartDate = s.DateFrom,
...
});
or
var query =
from e in DbContext.EquipmentLives
where ...
let s = e.EquipmentStatuses.FirstOrDefault(s => s.DateTo == null)
where s != null
select new SearchListItem {
EquipmentStatusId = s.Id,
StatusStartDate = s.DateFrom,
...
});
Given that you don't test for null after calling FirstOrDefault(s => s.DateTo == null) I assume that:
either for each device there is always a status with DateTo == null or
you need to see only devices which have such status
In order to do so you need to join EquipmentLives with EquipmentStatuses to avoid subqueries:
var query = DbContext.EquipmentLives
.Where(l => true)
.Join(DbContext.EquipmentStatuses.Where(s => s.DateTo == null),
eq => eq.Id,
status => status.EquipmentId,
(eq, status) => new SelectListItem
{
EquipmentStatusId = status.Id,
StatusStartDate = status.DateFrom
});
However, if you do want to perform a left join replace DbContext.EquipmentStatuses.Where(s => s.DateTo == null) with DbContext.EquipmentStatuses.Where(s => s.DateTo == null).DefaultIfEmpty().

Filter query by related table count

I'm trying to get a list of people who have at least one family member associated in the database.
FamilyMembers is a related table, tied by a one-to-many foreign key.
[one] People.PersonId --> [0 or more] FamilyMembers.PersonId
I tried doing this, but Count() doesn't seem to work like I thought it would.
I get results containing both 0 and more FamilyMembers.
public IEnumerable<Person> GetPeopleWithFamilyMembers()
{
IQueryable<Person> query = Context.Persons;
query = query.OrderBy(x => x.FamilyMembers.Count());
query = query.Where(x => x.FamilyMembers.Count() > 0);
// Execute query and return result:
return query.Select(x => x);
}
I'm really not sure what to do here :-/
I struggled for a good bit on a elegant solution to this without using expanded query form.
The only way I could achieve this in SQL was using a Group By and a Join, but I have no idea if I translated it to LINQ correctly. Please feel free to point out my mistakes.
var query = Persons
.Join(FamilyMembers,
p => p.Id,
f => f.Id,
(p,f) => new { Person = p, FamilyMembers = f }
)
.GroupBy(g => g.FamilyMembers)
.Where(w => w.FamilyMembers.Count() > 0);
.Select(x => new {
Person = x.Person,
FamilyMembers = x.Count(y => y.FamilyMembers)
}
);

NHibernate Criteria to QueryOver

I am still learning NHibernate and am wondering if someone could help me translate the following criteria to its QueryOver equivalent.
I think I have the basics down, but I am a little lost when querying the child collection and adding the alias. The criteria query posted does return the expected data, but I'm not sure how comfortable I am with all the magic strings of the criteria format.
return Session.CreateCriteria<Person>()
.Add(Restrictions.Eq("FirstName", firstName))
.Add(Restrictions.Eq("LastName", lastName))
.CreateCriteria("PartyContactMechanisms")
.CreateAlias("ContactMechanism", "c")
.Add(Restrictions.Eq("c.ElectronicAddressString", emailAddress))
.UniqueResult<Person>();
Edit:
I was able to return the desired result using the following QueryOver. I thought I'd post the solution in case it helps someone else out. Any suggestions on improving this code are welcome as well.
Person personAlias = null;
ElectronicAddress emailAlias = null;
PartyContactMechanism partyContactMechAlias = null;
return Session.QueryOver(() => personAlias)
.Where(p => p.FirstName == firstName)
.And(p => p.LastName == lastName)
.JoinQueryOver(() => personAlias.PartyContactMechanisms, () => partyContactMechAlias)
.JoinAlias(() => partyContactMechAlias.ContactMechanism, () => emailAlias)
.Where(() => emailAlias.ElectronicAddressString == emailAddress)
.SingleOrDefault<Person>();
It could look like this:
// these are aliases, which we can/will use later,
// to have a fully-type access to all properties
Person person = null;
PartyContactMechanism partyContactMechanisms = null;
ContactMechanism contactMechanism = null;
// search params
var firstName = ..;
var lastName = ..;
var emailAddress = ..;
var query = session.QueryOver<Person>(() => person)
// the WHERE
.Where(() => person.FirstName == firstName)
// the And() is just more fluent eq of Where()
.And(() => person.LastName == lastName)
// this collection we will join as criteria
.JoinQueryOver(() => person.PartyContactMechanism, () => partyContactMechanisms)
// its property as alias
.JoinAlias(() => partyContactMechanisms.ContactMechanism, () => contactMechanism )
// final filter
.Where(() => contactMechanism.Code == emailAddress)
// just one result
.Take(1)
;
var uniqueResult = query
.List<Person>()
.SingleOrDefault();
for more information, please take a deep look here:
Chapter 16. QueryOver Queries
or here
NOTE: also check Subqueries, becuase these are much better when querying the "collections". Just search for them... and use them as subselect WHERE parentId IN (SELECT parentId FROM ChildTable...

How to select a single column with Entity Framework?

Is there a way to get the entire contents of a single column using Entity Framework 4? The same like this SQL Query:
SELECT Name FROM MyTable WHERE UserId = 1;
You can use LINQ's .Select() to do that. In your case it would go something like:
string Name = yourDbContext
.MyTable
.Where(u => u.UserId == 1)
.Select(u => u.Name)
.SingleOrDefault(); // This is what actually executes the request and return a response
If you are expecting more than one entry in response, you can use .ToList() instead, to execute the request. Something like this, to get the Name of everyone with age 30:
string[] Names = yourDbContext
.MyTable
.Where(u => u.Age == 30)
.Select(u => u.Name)
.ToList();
I'm a complete noob on Entity but this is how I would do it in theory...
var name = yourDbContext.MyTable.Find(1).Name;
If It's A Primary Key.
-- OR --
var name = yourDbContext.MyTable.SingleOrDefault(mytable => mytable.UserId == 1).Name;
-- OR --
For whole Column:
var names = yourDbContext.MyTable
.Where(mytable => mytable.UserId == 1)
.Select(column => column.Name); //You can '.ToList();' this....
But "oh Geez Rick, What do I know..."
Using LINQ your query should look something like this:
public User GetUser(int userID){
return
(
from p in "MyTable" //(Your Entity Model)
where p.UserID == userID
select p.Name
).SingleOrDefault();
}
Of course to do this you need to have an ADO.Net Entity Model in your solution.
You could use the LINQ select clause and reference the property that relates to your Name column.
If you're fetching a single item only then, you need use select before your FirstOrDefault()/SingleOrDefault(). And you can use anonymous object of the required properties.
var name = dbContext.MyTable.Select(x => new { x.UserId, x.Name }).FirstOrDefault(x => x.UserId == 1)?.Name;
Above query will be converted to this:
Select Top (1) UserId, Name from MyTable where UserId = 1;
For multiple items you can simply chain Select after Where:
var names = dbContext.MyTable.Where(x => x.UserId > 10).Select(x => x.Name);
Use anonymous object inside Select if you need more than one properties.

Categories

Resources