I'm trying to get something similar to the SQL below via QueryOver:
SELECT
docs.*,
(SELECT TOP 1 eventDate from events WHERE id=docs.id
AND type=4 ORDER BY eventDate DESC) as eventDate
FROM documents as docs
WHERE doc.accountId = ...
I've got close with a projection, however I'm not sure how to get the entire documents table back. Documents has a one-to-many relationship with Events, I don't want to outer join as it will bring multiple results, and an inner join may not bring back a row:
var query = QueryOver<Document>
.Where(d => d.Account == account)
.SelectList(list => list
.Select(d => d)
.Select(d => d.Events.OrderByDescending(e => e.EventDate).FirstOrDefault(e => e.Type == 4))
)
.List<object[]>()
.Select(d => return new DocumentSummary(d[0],d[1]) etc.);
Is there an easier way of performing subqueries for columns? I'm reluctant to replace this with the property performing a query in its get.
After some research it looks like HQL (which QueryOver is converted into) does not support TOP inside subqueries.
My solution: create a view which includes the computed properties, and then mark these properties in the mappings files as insert="false" and update="false"
Related
Hello i'm trying to get all users which have had payments at least 6 months over the given period (which must be a year). I've written SQL which works fine, but i have difficulties trying to convert it to nhibernate.
SQL:
SELECT COUNT(UserId) AS paidMonthsCount, UserId FROM (
SELECT DISTINCT UserId,
YEAR(PayDate) as _year,
MONTH(PayDate) as _month
FROM Payments
WHERE PayDate >= '2014-04-02T00:00:00' AND PayDate < '2015-04-02T23:59:00'
)result GROUP BY result.UserId
i have converted inner SQL:
var subQuery = Session.QueryOver(() => paymentAlias)
.SelectList(list => list
.Select(Projections.Distinct(Projections.Property<VelferdPayment>(p => p.Client.Id)).WithAlias(() => userWithHelp.Id))
.Select(p => p.AssignmentYear).WithAlias(() => userWithHelp.AssignmentDate)
)
.WhereRestrictionOn(p => p.AssignmentDate)
.IsBetween(parameters.FromDate)
.And(parameters.ToDate);
which selects the distinct part and i have the other part which is selecting from result:
var query = Session.QueryOver(() => userWithHelp).
SelectList(list => list
.SelectCount(p=> p.Id).WithAlias(()=> userWithHelpCount.Count)
.SelectGroup(p => p.Id).WithAlias(() => userWithHelpCount.Id)
)
.TransformUsing(Transformers.AliasToBean<UserWithHelpCount>())
.List<UserWithHelpCount>();
How can i queryover the subQuery results or is it possible to write single request to SQL. Working for a long time please help.
In general, with NHibernate we can only (or mainly) query Entities, not TABLES. Other words, we firstly map tables or views or even some <subselect>s into entities. The below mapping of the User (C# object User)
<class name="user" table="[dbo].[user_table]" ...
Will allow us to create query over C# User.
session.QueryOver<User>()...
Behind the scene it will generate FROM clause, which will contain the content of table attribute, i.e. FROM [dbo].[user_table]
That's it. There is no other way how to set the generated FROM clause. Just by mapping.
But there is a way which allow us to use existing ADO.NET connection to create custom query and even convert its result to some entity, or DTO. It is CreateSQLQuery() API:
17.1.5. Returning non-managed entities
It is possible to apply an IResultTransformer to native sql queries. Allowing it to e.g. return non-managed entities.
sess.CreateSQLQuery("SELECT NAME, BIRTHDATE FROM CATS")
.SetResultTransformer(Transformers.AliasToBean(typeof(CatDTO)))
This query specified:
the SQL query string
a result transformer
The above query will return a list of CatDTO which has been instantiated and injected the values of NAME and BIRTHNAME into its corresponding properties or fields.
So, we can use native SQL SELECT statements to get any results. We can even create some custom DTO and let NHibernate to transform result into them...
I need some help creating an LINQ select, i have a table with some columns in it, but only 2 of interest to this problem.
userid, type
Now this table have many thousands entries, and I only want the top, let’s say 50. So far so good, but the hard part is that there a lot of rows in success that should only be counted as 1.
Example
Type UserId
============
Add 1
Add 1
Add 1
Add 2
I would like this to only be counted as 2 in the limit of rows I am taking out, but I would like all the rows to be outputted still.
Is this possible with a single SQL request, or should I find another way to do this?
Edit: I can add columns to the table, with values if this would solve the problem.
Edit2: Sotred procedures are also an solution
Example 2: This should be counted as 3 rows
Type UserId
============
Add 1
Add 1
Add 2
Add 1
Are you stuck on LINQ?
Add a PK identity.
Order by PK.
Use a DataReader and just count the changes.
Then just stop when the changes count is at your max.
If you are not in a .NET environment then same thing with a cursor.
Since LINQ is deferred you might be able to just order in LINQ and then on a ForEach just exit.
I'm not close to a computer right now so I'm not sure is 100% correct syntax wise, but I believe you're looking for something like this:
data.Select(x => new {x.Type, x.UserId})
.GroupBy(x => x.UserId)
.Take(50);
You could do it with Linq, but it may be a LOT slower than a traditional for loop. One way would be:
data.Where((s, i) => i == 0 ||
!(s.Type == data[i-1].Type && s.UserId == data[i-1].UserId))
That would skip any "duplicate" items that have the same Type and UserID as the "previous" item.
However this ONLY works if data has an indexer (an array or something that implements IList). An IEnumerable or IQueryable would not work. Also, it is almost certainly not translatable to SQL so you'd have to pull ALL of the results and filter in-memory.
If you want to do it in SQL I would try either scanning a cursor and filling a temp table if one of the values change or using a common table expression that included a ROW_NUMBER column, then doing a look-back sub-query similar to the Linq method above:
WITH base AS
(
SELECT
Type,
UserId,
ROW_NUMBER() OVER (ORDER BY ??? ) AS RowNum
FROM Table
)
SELECT b1.Type, b1.UserId
FROM base b1
LEFT JOIN base b2 ON b1.RowNum = b2.RowNum - 1
WHERE (b1.Type <> b2.Type OR b1.UserId <> b2.UserId)
ORDER BY b1.RowNum
You can do this with LINQ, but I think it might be easier to go the "for(each) loop" route...
data.Select((x, i) => new { x.Type, x.UserId, i })
.GroupBy(x => x.Type)
.Select(g => new
{
Type = g.Key,
Items = g
.Select((x, j) => new { x.UserId, i = x.i - j })
})
.SelectMany(g => g.Select(x => new { g.Type, x.UserId, x.i }))
.GroupBy(x => new { x.Type, x.i })
.Take(50);
.SelectMany(g => g.Select(x => new { x.Type, x.UserId }));
SQL query is:
select B.* from A inner join B on A.b_id = B.id where A.x in (1,2,3)
A <-> B relation is many-to-one
I need to filter by A but fetch related B.
UPDATE:
I tried this NH QueryOver
Session.QueryOver<A>.Where(a => a.x.IsIn(array)).JoinQueryOver(a => a.B).Select(a => a.B).List<B>()
but it results in a N+1 sequence of queries: the first one fetches IDs of related Bs, and others fetch related Bs one by one by ID (analyzed via NHProf). I want it to fetch a list of Bs in one go.
UPDATE 2:
for now I worked around this with subquery
Session.QueryOver(() => b).WithSubquery.WhereExists(QueryOver.Of<A>().Where(a => a.x.IsIn(array)).And(a => a.b_id == b.id).Select(a => a.id)).List<B>()
but I still hope to see an example of QueryOver without subquery as I tend to think subquery is less efficient.
This works (at least in my test application):
var list = session.QueryOver<A>()
.Where(a => a.X.IsIn(array))
.Fetch(x => x.B).Eager
.List<A>()
.Select(x => x.B);
Note that the .Select() statement is a normal LINQ statement, not part of NHibernate.
Generated SQL:
SELECT
this_.Id as Id0_1_,
this_.B as B0_1_,
this_.X as X0_1_,
b2_.Id as Id1_0_,
b2_.SomeValue as SomeValue1_0_
FROM A this_ left outer join B b2_ on this_.B=b2_.Id
WHERE this_.X in (?, ?, ?)
It's not optimal if A is a very large class, of course.
An NHibernate-only solution with a subquery:
var candidates = QueryOver.Of<A>()
.Where(a => a.X.IsIn(array))
.Select(x => x.B.Id);
var list = session.QueryOver<B>()
.WithSubquery.WhereProperty(x => x.Id).In(candidates).List();
I'll try to find the reason why the most obvious solution (just adding Fetch().Eager) doesn't work as expected. Stay tuned!
I want to try and introduce the DISTINCT keyword into SQL, basically I require the following SQL:-
SELECT distinct this_.Id as y0_,
this_.Name as y1_,
this_.Description as y2_,
this_.UnitPrice as y3_,
this_.Director as y4_
FROM Product this_
inner join ActorRole actor1_
on this_.Id = actor1_.MovieId
WHERE this_.ProductType = 'Movie'
AND actor1_.Name like 'm%' /* #p0 */
The QueryOver code looks like this, however I can't use the DISTINCT keyword without using a projection:-
var movie = Session.QueryOver<Movie>()
.JoinQueryOver<Actor>(m => m.ActorList).Where(a => a.Name.IsLike("m%"))
.Select(
Projections.Distinct(
Projections.ProjectionList()
.Add(Projections.Property<Movie>(w => w.Id))
.Add(Projections.Property<Movie>(w => w.Name))
.Add(Projections.Property<Movie>(w => w.Description))
.Add(Projections.Property<Movie>(w => w.UnitPrice))
.Add(Projections.Property<Movie>(w => w.Director))
)
)
.TransformUsing(Transformers.AliasToBean<Movie>());
return movie.List<Movie>();
This works returns me distinct movies where actors begin with the letter 'm'. Now the problem comes as the projection is meant for DTO's and when I iterate over the results and want to lazy load the children. For example:-
#foreach (var item in Model.ActorList)
{
<li>#(item.Name) <em>plays</em> #item.Role</li>
}
Model.ActorList is always NULL, it appears that projecting and using a transformer loses the lazy loading as this method is designed for DTO's. What are my options?
I know I can use a sub query or HQL rather than a select distinct
Transformers.AliasToBean<Movie>() just creates a new Movie and fills in the properties. It is therefor a new Movie and not loaded from DB and therefor doesnt inherit the collection of the original Movie. AFAIK AliasToBean is to fill ViewModels etc with projected data.
Can't you just use:
Session.QueryOver<Movie>()
.JoinQueryOver<Actor>(m => m.ActorList).Where(a => a.Name.IsLike("m%"))
.List();
If anyone else is interested in this then please read the blog post that explains this behaviour
Imagine the following (simplified) database layout:
We have many "holiday" records that relate to going to a particular Accommodation on a certain date etc.
I would like to pull from the database the "best" holiday going to each accommodation (i.e. lowest price), given a set of search criteria (e.g. duration, departure airport etc).
There will be multiple records with the same price, so then we need to choose by offer saving (descending), then by departure date ascending.
I can write SQL to do this that looks like this (I'm not saying this is necessarily the most optimal way):
SELECT *
FROM Holiday h1 INNER JOIN (
SELECT h2.HolidayID,
h2.AccommodationID,
ROW_NUMBER() OVER (
PARTITION BY h2.AccommodationID
ORDER BY OfferSaving DESC
) AS RowNum
FROM Holiday h2 INNER JOIN (
SELECT AccommodationID,
MIN(price) as MinPrice
FROM Holiday
WHERE TradeNameID = 58001
/*** Other Criteria Here ***/
GROUP BY AccommodationID
) mp
ON mp.AccommodationID = h2.AccommodationID
AND mp.MinPrice = h2.price
WHERE TradeNameID = 58001
/*** Other Criteria Here ***/
) x on h1.HolidayID = x.HolidayID and x.RowNum = 1
As you can see, this uses a subquery within another subquery.
However, for several reasons my preference would be to achieve this same result in NHibernate.
Ideally, this would be done with QueryOver - the reason being that I build up the search criteria dynamically and this is much easier with QueryOver's fluent interface. (I had started out hoping to use NHibernate Linq, but unfortunately it's not mature enough).
After a lot of effort (being a relative newbie to NHibernate) I was able to re-create the very inner query that fetches all accommodations and their min price.
public IEnumerable<HolidaySearchDataDto> CriteriaFindAccommodationFromPricesForOffers(IEnumerable<IHolidayFilter<PackageHoliday>> filters, int skip, int take, out bool hasMore)
{
IQueryOver<PackageHoliday, PackageHoliday> queryable = NHibernateSession.CurrentFor(NHibernateSession.DefaultFactoryKey).QueryOver<PackageHoliday>();
queryable = queryable.Where(h => h.TradeNameId == website.TradeNameID);
var accommodation = Null<Accommodation>();
var accommodationUnit = Null<AccommodationUnit>();
var dto = Null<HolidaySearchDataDto>();
// Apply search criteria
foreach (var filter in filters)
queryable = filter.ApplyFilter(queryable, accommodationUnit, accommodation);
var query1 = queryable
.JoinQueryOver(h => h.AccommodationUnit, () => accommodationUnit)
.JoinQueryOver(h => h.Accommodation, () => accommodation)
.SelectList(hols => hols
.SelectGroup(() => accommodation.Id).WithAlias(() => dto.AccommodationId)
.SelectMin(h => h.Price).WithAlias(() => dto.Price)
);
var list = query1.OrderByAlias(() => dto.Price).Asc
.Skip(skip).Take(take+1)
.Cacheable().CacheMode(CacheMode.Normal).List<object[]>();
// Cacheing doesn't work this way...
/*.TransformUsing(Transformers.AliasToBean<HolidaySearchDataDto>())
.Cacheable().CacheMode(CacheMode.Normal).List<HolidaySearchDataDto>();*/
hasMore = list.Count() == take;
var dtos = list.Take(take).Select(h => new HolidaySearchDataDto
{
AccommodationId = (string)h[0],
Price = (decimal)h[1],
});
return dtos;
}
So my question is...
Any ideas on how to achieve what I want using QueryOver, or if necessary Criteria API?
I'd prefer not to use HQL but if it is necessary than I'm willing to see how it can be done with that too (it makes it harder (or more messy) to build up the search criteria though).
If this just isn't doable using NHibernate, then I could use a SQL query. In which case, my question is can the SQL be improved/optimised?
I have manage to achieve such dynamic search criterion by using Criteria API's. Problem I ran into was duplicates with inner and outer joins and especially related to sorting and pagination, and I had to resort to using 2 queries, 1st query for restriction and using the result of 1st query as 'in' clause in 2nd creteria.