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...
Related
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"
Domain Model
I've got a canonical Domain of a Customer with many Orders, with each Order having many OrderItems:
Customer
public class Customer
{
public Customer()
{
Orders = new HashSet<Order>();
}
public virtual int Id {get;set;}
public virtual ICollection<Order> Orders {get;set;}
}
Order
public class Order
{
public Order()
{
Items = new HashSet<OrderItem>();
}
public virtual int Id {get;set;}
public virtual Customer Customer {get;set;}
}
OrderItems
public class OrderItem
{
public virtual int Id {get;set;}
public virtual Order Order {get;set;}
}
Problem
Whether mapped with FluentNHibernate or hbm files, I run two separate queries, that are identical in their Fetch() syntax, with the exception of one including the .First() extension method.
Returns expected results:
var customer = this.generator.Session.Query<Customer>()
.Where(c => c.CustomerID == id)
.FetchMany(c => c.Orders)
.ThenFetchMany(o => o.Items).ToList()[0];
Returns only a single item in each collection:
var customer = this.generator.Session.Query<Customer>()
.Where(c => c.CustomerID == id)
.FetchMany(c => c.Orders)
.ThenFetchMany(o => o.Items).First();
I think I understand what's going on here, which is that the .First() method is being applied to each of the preceding statements, rather than just to the initial .Where() clause. This seems incorrect behavior to me, given the fact that First() is returning a Customer.
Edit 2011-06-17
After further research and thinking, I believe that depending on my mapping, there are two outcomes to this Method Chain:
.Where(c => c.CustomerID == id)
.FetchMany(c => c.Orders)
.ThenFetchMany(o => o.Items);
NOTE: I don't think I can get subselect behavior, since I'm not using HQL.
When the mapping is fetch="join" I should get a cartesian product between the Customer, Order and OrderItem tables.
When the mapping is fetch="select" I should get a query for Customer, and then multiple queries each for Orders and OrderItems.
How this plays out with adding the First() method to the chain is where I lose track of what should be happening.
The SQL Query that get's issued is the traditional left-outer-join query, with select top (#p0) in front.
The First() method is translated into SQL (T-SQL at least) as SELECT TOP 1 .... Combined with your join fetching, this will return a single row, containing one customer, one order for that customer and one item for the order. You might consider this a bug in Linq2NHibernate, but as join fetching is rare (and I think you're actually hurting your performance pulling the same Customer and Order field values across the network as part of the row for each Item) I doubt the team will fix it.
What you want is a single Customer, then all Orders for that customer and all Items for all those Orders. That happens by letting NHibernate run SQL that will pull one full Customer record (which will be a row for each Order Line) and construct the Customer object graph. Turning the Enumerable into a List and then getting the first element works, but the following will be slightly faster:
var customer = this.generator.Session.Query<Customer>()
.Where(c => c.CustomerID == id)
.FetchMany(c => c.Orders)
.ThenFetchMany(o => o.Items)
.AsEnumerable().First();
the AsEnumerable() function forces evaluation of the IQueryable created by Query and modified with the other methods, spitting out an in-memory Enumerable, without slurping it into a concrete List (NHibernate can, if it wishes, simply pull enough info out of the DataReader to create one full top-level instance). Now, the First() method is no longer applied to the IQueryable to be translated to SQL, but it is instead applied to an in-memory Enumerable of the object graphs, which after NHibernate has done its thing, and given your Where clause, should be zero or one Customer record with a hydrated Orders collection.
Like I said, I think you're hurting yourself using join fetching. Each row contains the data for the Customer and the data for the Order, joined to each distinct Line. That is a LOT of redundant data, which I think will cost you more than even an N+1 query strategy.
The best way I can think of to handle this is one query per object to retrieve that object's children. It would look like this:
var session = this.generator.Session;
var customer = session.Query<Customer>()
.Where(c => c.CustomerID == id).First();
customer.Orders = session.Query<Order>().Where(o=>o.CustomerID = id).ToList();
foreach(var order in customer.Orders)
order.Items = session.Query<Item>().Where(i=>i.OrderID = order.OrderID).ToList();
This requires a query for each Order, plus two at the Customer level, and will return no duplicate data. This will perform far better than a single query returning a row containing every field of the Customer and Order along with each Item, and also better than sending a query per Item plus a query per Order plus a query for the Customer.
I'd like to update the answer with my found so that could help anybody else with the same problem.
Since you are querying the entity base on their ID, you can use .Single instead of .First or .AsEnumerable().First():
var customer = this.generator.Session.Query<Customer>()
.Where(c => c.CustomerID == id)
.FetchMany(c => c.Orders)
.ThenFetchMany(o => o.Items).Single();
This will generate a normal SQL query with where clause and without the TOP 1.
In other situation, if the result has more than one Customer, exception will be thrown so it won't help if you really need the first item of a series based on condition. You have to use 2 queries, one for the first Customer and let the lazy load do the second one.
I’m having a problem with translating T-SQL query into Nhibernate query – or writing query that will return same results but will be properly written in NH query language (HQL, Criteria, QueryOver, LINQ – I don’t truly care).
I would like to execute similar query from NHibernate:
SELECT
lic.RegNo,
lic.ReviewStatus
FROM
Licence lic
WHERE
lic.RegNo IN
(
SELECT
grouped.RegNo
FROM
(
SELECT
g.[Type],
g.Number,
MAX(g.Iteration) AS [Iteration],
MAX(g.RegNo) AS [RegNo]
FROM
Licence g
GROUP BY
g.[Type],
g.Number
) as grouped
)
ORDER BY
lic.RegNo desc
It returns the top most licenses and fetch their review status if exists. RegNo is created from Type, Number and Iteration (pattern: {0}{1:0000}-{2:00}). Each license can have multiply iterations and some of them can contains ReviewStatus, for instance:
W0004-01 NULL
W0001-03 1
P0004-02 3
P0001-02 4
If iteration part is greater than 1 it means that there are multiply iterations (n) for specific licence.
I’ve manage to create NH query by going twice to database:
LicenceInfoViewModel c = null;
var grouped = session.QueryOver<Licence>()
.SelectList(l => l
.SelectGroup(x => x.Type)
.SelectGroup(x => x.Number)
.SelectMax(x => x.Iteration)
.SelectMax(x => x.RegNo).WithAlias(() => c.RegNo)
).TransformUsing(Transformers.AliasToBean<LicenceInfoViewModel>())
.Future<LicenceInfoViewModel>();
var proper = session.QueryOver<Licence>()
.Select(x => x.RegNo, x => x.ReviewStatus)
.WhereRestrictionOn(x => x.RegNo)
.IsIn(grouped.Select(x => x.RegNo).ToArray())
.TransformUsing(Transformers.AliasToBean<LicenceInfoViewModel>())
.List<LicenceInfoViewModel>();
// ...
public class LicenceInfoViewModel
{
public string RegNo { get; set; }
public LicReviewStatus? ReviewStatus { get; set; }
}
public enum LicReviewStatus
{
InProgress,
Submitted,
Validated,
RequestForInformation,
DecissionIssued
}
However this solution is not good as it require to download all grouped licences from database, and there could be a thousands of them.
Is there a better way to write this query or is there a way to translate provided above T-SQL query into NHibernate?
adding nhibernate and hibernate tags as IMO if this can be done in hibernate it should be easily translated into nh
I don't think that SQL does what you think it does. Iteration is not used for anything.
In any case, it seems unnecessary. You can change the WHERE to the following, and you'll have both valid SQL and HQL:
lic.RegNo IN
(
SELECT
MAX(g.RegNo)
FROM
Licence g
GROUP BY
g.Type,
g.Number
)
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.
Sorry to ask all these questions about Kohana. They usually get ignored. I think I just found a bug. I'm making a join between two tables that are not directly related.
$results = ORM::factory('foo')->join("bar")->on("foo.foreign_id", "=", "bar.id");
This generates a query that does not resolve the table names explicitly:
SELECT * FROM `foo` JOIN `bar` ON (`foo`.`foreign_id` = `bar`.`id`)
Which gives (in phpMyAdmin) a table that looks like this:
id time foreign_id blah_int id baz
4 1291851245 3 0 3 52501504
Notice there are two id columns, one for the foo table and one for bar. This is a real problem. Because now, in my results, if I loop through...
foreach ($results as $result) {
echo $result->id; // prints 3!!!
}
Because my results should be foo objects, I expect to get an id of 4, but it's giving me 3 because of the join. Is this a bug in the ORM library? Should I be using a different method to restrict my results from the query? I really don't want to do two separate queries where I load all the bars id's, and then load my foos that way, but it looks like I have to.
You have to use the Database object to build raw queries, not ORM, like this:
$results = DB::select()->from('foo')->join('bar')->on("foo.foreign_id", "=", "bar.id")->execute();
You will need to specific some column aliases however to make your query work unless you use ORM as it was intended.
Using ORM
If you want to use ORM, you need to define the relationships in your model. You mention that they share a relationship with another table so in your case you could use a has many through relationship like this:
protected $_has_many = array(
'bars' => array('model' => 'bar', 'through' => 'other_table', 'foreign_key' => 'foreign_id'),
);
Although your example as given suggests that a straight has_many relationship would work:
protected $_has_many = array(
'bars' => array('model' => 'bar','foreign_key' => 'foreign_id'),
);
This would allow you to access all of the bars using a statement like
$bars = $results->bars->find_all();
foreach($bars as $bar)
{
echo $bar->id; // should echo 4, assuming one record in bars with id 4
}
The Kohana 3.1 ORM Reference Guide is good place to start if you want to learn more about ORM and relationships
Using the Kohana database object and query builder
If you prefer ad hoc queries and are doing joins using the query builder you will likely have colliding column names regardless if you are using Kohana or just raw queries (pop "SELECT * FROM foo JOIN bar ON (foo.foreign_id = bar.id)" into MySQL and you will get the exact same result).
Kohana, just like MySQL allows you to define column aliases for precisely this reason. (See here for more information)
Rewrite your query as follows:
$results = DB::select('id', 'time', 'foreign_id', array('bar.id', 'bar_id'), 'baz')->from('foo')->join("bar")->on("foo.foreign_id", "=", "bar.id")->execute();
This will return:
id time foreign_id blah_int bar_id baz
4 1291851245 3 0 3 52501504