NHibernate QueryOver how to join on non declared relationship - nhibernate

How to do the following join to return Users who have access to a Company given a company id.
The problem is there is no explicit relationship using a User object between UserAccess and User they simply join on the string property Username:
User(Username, Name)
UserAccess(Username, Company)
Company(Id)
Session.QueryOver<Company>()
.Where(c => c.Id == companyId)
.JoinQueryOver<UserCompanyAccess>(u => u.UserAccessList)
.JoinQueryOver<User>(u => **Nope no property, just a string**

could be done with a subquery
var subquery = QueryOver.Of<Company>()
.Where(c => c.Id == companyId)
.JoinQueryOver<UserCompanyAccess>(u => u.UserAccessList)
.Select(uca => uca.UserName);
var users = session.QueryOver<User>()
.WithSubquery.WhereProperty(u => u.Name).In(subquery)
.List();

As of 5.1.0, it is possible to make hibernate generate an actual sql join on an undeclared (unmapped) relationship. E.g. all orders sorted by customer's spending:
var criteria = _session
.CreateCriteria<Order>("order");
criteria
.CreateEntityAlias(
"customer",
Restrictions.EqProperty("order.customerId", "customer._id"),
JoinType.LeftOuterJoin,
typeof(Customer).FullName)
.AddOrder(new Order("customer._lifetimeSpending", ascending:false));
return criteria.List<Order>();
Also possible with QueryOver (sample from NHibernate docs):
Cat cat = null;
Cat joinedCat = null;
var uniquelyNamedCats = sess.QueryOver<Cat>(() => cat)
.JoinEntityAlias(
() => joinedCat,
() => cat.Name == joinedCat.Name && cat.Id != joinedCat.Id,
JoinType.LeftOuterJoin)
.Where(() => joinedCat.Id == null)
.List();

Related

Multiple Conditions in Nhibernate Left Join

Please need some help converting this sql query to nhibernate
select a.ID, count(b.ID)
from appusers a
left join weeklytasks b on a.ID = b.TaskOwner and b.taskstatus = 1
group by a.ID
It's difficult to answer without knowing your entities, mappings, used technology(ICreteria API, QueryOver, Linq).
But I can suggest this solution using QueryOver:
AppUser ownerAlias = null;
WeeklyTask taskAlias = null;
var result = Session.QueryOver(() => taskAlias)
.JoinAlias(x => x.TaskOwner,
() => ownerAlias,
NHibernate.SqlCommand.JoinType.RightOuterJoin,
Restrictions.Where(() => taskAlias.Status == 1))
.SelectList(list => list
.SelectGroup(x => ownerAlias.Id)
.SelectCount(x => x.Id))
.List<object[]>();
or this:
var result = Session.QueryOver<WeeklyTask>()
.Where(x => x.Status == 1)
.Right.JoinQueryOver(x => x.TaskOwner)
.SelectList(list => list
.SelectGroup(x => x.TaskOwner.Id)
.SelectCount(x => x.Id))
.List<object[]>();
Please notice that in this approach your WeeklyTask entity must contains mapped reference to AppUser entity.

NHibernate QueryOver - Join without path (or with "reversed" path)

I am trying to fetch several entities without having a single root entity ( the directed graph of entity-relations is only a weakly connected graph, not strongly connected) and I cant figure out how to do it in Nhibernates QueryOver api (or Linq, but that seems to be even weaker)
These are the relations I have:
ClientTaxEntity references 1 Client and N Manufacturers
InstructionTemplate references 1 Client and 1 Manufacturer
I want to get a result with all possible Client-Manufacturer pairs (possible = they are together within a ClientTaxEntity) and fetch a Template to them if it exists (null otherwise)
This is what I have tried so far:
Client client = null;
Manufacturer manufacturer = null;
InstructionTemplate template = null;
ClientTaxEntity taxEntity = null;
InstructionTemplateInfo info = null;
var query =Session.QueryOver<ClientTaxEntity>(() => taxEntity)
.JoinAlias(x => x.Client, () => client)
.JoinAlias(x => x.Manufacturers, () => manufacturer)
.Left
.JoinQueryOver(() => template, () => template,() => template.Client == client && template.Manufacturer == manufacturer);
var result = query
.SelectList(builder => builder
.Select(() => client.Name).WithAlias(() => info.ClientName)
.Select(() => client.Id).WithAlias(() => info.ClientId)
.Select(() => manufacturer.Name).WithAlias(() => info.ManufacturerName)
.Select(() => manufacturer.Id).WithAlias(() => info.ManufacturerId)
.Select(() => template.Id).WithAlias(() => info.TemplateId)
.Select(() => template.Type).WithAlias(() => info.Type)
)
.TransformUsing(Transformers.DistinctRootEntity)
.TransformUsing(Transformers.AliasToBean<InstructionTemplateInfo>())
.List<InstructionTemplateInfo>();
The info object is the result I would like.
However the syntax .JoinQueryOver(() => template does not seem to be valid for a path parameter (exception says : could not resolve property: template of: ClientTaxEntity
To get the result you want when writing a query in SQL it would be necessary to write something along the lines of:
SELECT Client_Id, Client_Name, Manufacturer_Id, Manufacturer_Name
FROM
(
SELECT Client.Id as Client_Id, Client.Name as Client_Name,
Manufacturer.Id as Manufacturer_Id, Manufacturer.Name as Manufacturer_Name
FROM ClientTax
INNER JOIN Client on Client.Id = ClientTax.Client_Id
INNER JOIN Manufacturer on Manufacturer.Id = Manufacturer.Manufacturer_id
UNION
SELECT Client.Id, Client.Name, Manufacturer.Id, Manufacturer.Name
FROM InstructionTemplate
INNER JOIN Client on Client.Id = InstructionTemplate.Client_Id
INNER JOIN Manufacturer on Manufacturer.Id = InstructionTemplate.Manufacturer_id
) a
GROUP BY Client_Id, Client_Name, Manufacturer_Id, Manufacturer_Name;
Unfortunately converting such a query to one of NHibernate's query APIs is not possible because NHibernate does not support the UNION statement*. See this question and the feature request NH-2710 in NHibernate's bug tracker.
*Except when using union-subclass. See the docs for further details
The only options I can see are
Perform an SQL Query and map this to a DTO
public class InstructionTemplateInfo
{
public int Client_Id { get; set; }
public string Client_Name { get; set; }
public int Manufacturer_Id { get; set; }
public string Manufacturer_Name { get; set; }
}
then
var result = session
.CreateSQLQuery(theSQLQueryString)
.SetResultTransformer(Transformers.AliasToBean<InstructionTemplateInfo>())
.List<InstructionTemplateInfo>();
Create a view in the database and map this like a normal entity.
If the DBMS supports multiple results sets, like SQL Server, you could write two queries but mark them both as Future and then merge the two results set in code, i.e.
var resultSet1 = Session.QueryOver<ClientTaxEntity>(() => taxEntity)
.JoinAlias(x => x.Client, () => client)
.JoinAlias(x => x.Manufacturers, () => manufacturer)
.SelectList(builder => builder
.SelectGroup((() => client.Name).WithAlias(() => info.ClientName)
.SelectGroup((() => client.Id).WithAlias(() => info.ClientId)
.SelectGroup((() => manufacturer.Name).WithAlias(() => info.ManufacturerName)
.SelectGroup((() => manufacturer.Id).WithAlias(() => info.ManufacturerId)
.TransformUsing(Transformers.AliasToBean<InstructionTemplateInfo>())
.Future<InstructionTemplateInfo>;
var resultSet2 = Session.QueryOver<InstructionTemplate>(() => taxEntity)
.JoinAlias(x => x.Client, () => client)
.JoinAlias(x => x.Manufacturers, () => manufacturer)
.SelectList(builder => builder
.SelectGroup((() => client.Name).WithAlias(() => info.ClientName)
.SelectGroup((() => client.Id).WithAlias(() => info.ClientId)
.SelectGroup((() => manufacturer.Name).WithAlias(() => info.ManufacturerName)
.SelectGroup((() => manufacturer.Id).WithAlias(() => info.ManufacturerId)
.TransformUsing(Transformers.AliasToBean<InstructionTemplateInfo>())
.Future<InstructionTemplateInfo>;
var result = resultSet1.Concat(resultSet2 )
.GroupBy(x=>x.ClientId+"|"+x.ManufacturerId)
.Select(x=>x.First());
The advantage of this approach is that the DBMS will only be hit once.
See Ayende's blog post for further details about this feature.

NHIbernate QueryOver detached criteria and Any clause

I have the following in subquery which is failing:
var subquery = QueryOver.Of<Product>()
.Where(x => x.ProductCategories.Any(y => y.Categgory == parameter.Category));
I get an error for the Any statement:
Unrecognised method call: System.Linq.Enumerable:Boolean Any
How would I update the above restriction for QueryOver?
ProductCategory productCategory = null;
var subquery = QueryOver.Of<Product>()
.JoinAlias(product => product.ProductCategories, () => productCategory)
.Where(() => productCategory.Category.Id == parameter.Category.Id);
What's type of Category? If this is an entity:
productCategory.Category.Id == parameter.Category.Id
If this is base property:
productCategory.Category == parameter.Category
Is it many-to-many relation? (Product and Category)
Category category = null;
var subquery = QueryOver.Of<Product>()
.JoinAlias(product => product.Category, () => category)
.Where(() => category.Id == parameter.Category.Id);
ProductCategory productCategory = null;
var subquery = QueryOver.Of<Product>()
.JoinAlias(product => product.ProductCategories, () => productCategory)
.Where(Subqueries.WhereExists(CatExistsQuery())
private QueryOver<ProductCategory , ProductCategory > CatExistsQuery(<your type> parameter)
{
ProductCategory _innerCat = null;
var query = (QueryOver<ProductCategory , ProductCategory >)Session
.QueryOver(() => _innerCat )
.Where(() => _productCategory.Id== _innerCat.Id)
.And (innerCat.Id == parameter.Category.Id)
return query ;
}

NHibernate QueryOver subquery to return only newest record

I have a query in Nhibernate QueryOver which brings back a collection of episode objects (episode being a spell of care) which in turn has a collection of episode statuses as a property of each episode. However I want to change this so that each episode only brings back the latest status update for that episode instead of all of them.
The SQL to do this is as follows:
SELECT *
FROM DIPEpisode e
INNER JOIN DIPEpisodeStatus s on s.EpisodeID = e.SequenceID
WHERE e.ClientID = '1000001'
AND s.SequenceID IN (
SELECT TOP 1 SequenceID
FROM DIPEpisodeStatus s
WHERE s.EpisodeID = e.SequenceID
ORDER BY StatusRecordedDate DESC
)
I have written the following query which gives me almost exactly what I need
var statuses =
QueryOver.Of<DIPEpisodeStatus>()
.OrderBy(x => x.StatusRecordedDate).Desc
.Select(x => x.Id).Take(1);
DIPEpisodeStatus statusAlias = null;
return
session.QueryOver<DIPEpisode>()
.JoinQueryOver(x => x.DIPEpisodeStatuss, () => statusAlias)
.Fetch(x => x.AgencyID).Eager
.Fetch(x => x.DIPEpisodeStatuss).Eager
.Where(e => e.ClientID.Id == this.clientId)
.WithSubquery.WhereProperty(x => x.Id).Eq(statuses)
.List();
This generates the following SQL:
SELECT *
FROM DIPEpisode this_
inner join DIPEpisodeStatus statusalia1_
on this_.SequenceID = statusalia1_.EpisodeID
WHERE statusalia1_.ClientID = '1000001' /* #p0 */
and statusalia1_.SequenceID = (SELECT TOP (1 /* #p1 */) this_0_.SequenceID as y0_
FROM DIPEpisodeStatus this_0_
ORDER BY this_0_.StatusRecordedDate desc)
As you can see, the only thing missing is the where clause from the subquery. What changes do I need to make to the query in order to generate this extra where clause and pull back only the most recent status update?
Thanks
Ben
the collection DIPEpisodeStatuss is always initialized with all entities because it would break changetracking otherwise. you could either define a filter for the collection or return a DTO with what you want. Also the fetch will be ignored because it can not eager load and filter in one sql statement.
NHibernate filters are explained here
defining Filters in FNH
how it would be done with a DTO
// assuming SequneceID and StatusRecordedDate correlates
var subquery = QueryOver.Of<DIPEpisode>()
.Where(e => e.ClientID.Id == this.clientId)
.JoinAlias(e => e.DIPEpisodeStatuss, () => statusAlias)
.Select(Projections.Max(() => statusAlias.SequenceID));
// or as in question
var subquery = QueryOver.Of<DIPEpisode>()
.Where(e => e.ClientID.Id == this.clientId)
.JoinAlias(e => e.DIPEpisodeStatuss, () => statusAlias)
.OrderByDescending(() => statusAlias.StatusRecordedDate)
.Select(() => statusAlias.SequenceID)
.Take(1);
DIPEpisodeDto dto = null;
DIPEpisodeStatus statusAlias = null;
return session.QueryOver<DIPEpisode>()
.Where(e => e.ClientID.Id == this.clientId)
.JoinQueryOver(e => e.DIPEpisodeStatuss, () => statusAlias)
.WithSubquery.WhereProperty(estatus => estatus.Id).Eq(statuses)
.SelectList(list => list
.Select(e => e.Whatever).WithAlias(() => dto.Whatever)
.Select(() => statusAlias.SquenceId).WithAlias(() => dto.StatusId)
...
)
.TransFormUsing(Transformers.AliasToBean<DIPEpisodeDto>())
.List();
or using LINQ
var query = from e in session.Query<DIPEpisode>()
from s in e.DIPEpisodeStatuss
where e.ClientID.Id == this.clientId
where s.Id == (
from e2 in session.Query<DIPEpisode>()
from s2 in e2.DIPEpisodeStatuss
orderby s2.StatusRecordedDate descending
select s2.Id)
.First()
select new DIPEpisodeDto
{
e.Prop1,
Status = s,
};
return query.List<DIPEpisodeDto>();

Construct NHibernate query that orders by a boolean condition

I have two entities: Client, and AccountPlan, that have a 1 to 0..1 relationship. I would like to fetch my Clients, ordered first by Clients that have an AccountPlan, and then by Clients that do not. When I try the following Linq to Nhibernate query:
return NHibernateSession.Current.Query<Client>()
.Where(x => x.SalesRepId == id)
.OrderBy(x => x.AccountPlan == null);
I get a QuerySyntaxException with the following message:
{"Exception of type 'Antlr.Runtime.NoViableAltException' was thrown.
[.OrderBy(.Where(NHibernate.Linq.NhQueryable`1[FIS.AccountManagement.Core.Domain.Client],
Quote((x, ) => (Equal(x.SalesRepId, p1))), ), Quote((x, ) =>
(Equal(x.AccountPlan, ))), )]"}
Here's the mapping relationship between the two entities, if that's important:
public ClientMap()
{
HasOne(x => x.AccountPlan).PropertyRef(r => r.Client);
}
public AccountPlanMap()
{
DynamicInsert();
References(x => x.Client, "EntityID");
}
Does anyone know of a query from one of NHibernate's myriad APIs that will accomplish what I want? Thanks in advance.
two queries in one roundtrip concated together
var clientsWithPlan = NHibernateSession.Current.Query<Client>()
.Where(x => x.SalesRepId == id)
.Where(x => x.AccountPlan != null)
.Future();
var clientsWithoutPlan = NHibernateSession.Current.Query<Client>()
.Where(x => x.SalesRepId == id)
.Where(x => x.AccountPlan == null)
.Future();
return clientsWithPlan.Concat(clientsWithoutPlan);
Using a conditional operator:
return NHibernateSession.Current.Query<Client>()
.Where(x => x.SalesRepId == id)
.OrderBy(x => x.AccountPlan == null ? 1 : 0);
As a workaround, you could retrieve all Clients from the database unordered, then perform the ordering in the application using LINQ to objects:
var clients = NHibernateSession.Current.QueryOver<Client>()
.Where(x => x.SalesRepId == id)
.Fetch(x => x.Account).Eager
.List();
return clients.OrderBy(x => x.AccountPlan == null);