Take + FetchMany + returning unexpected results - nhibernate

I am trying to figure out how to do a Take and FetchMany Together.
var c = session.Query<Table1>().Take(5).FetchMany(x => x.Table2).ToList();
I expect this to return 5 rows from Table1 and unbound result from Table2. Instead I get one row back from Table1 and 5 rows from Table2.
var c = session.Query<Table1>().Take(1000).FetchMany(x => x.Table2).ToList();
I get 309 records from Table1(it has 6200 total records).
I don't get how nhibernate decided to return that many records.

well I am not sire but how this has a bearing on your code, but fetchmany will bring the whole result set to memory and then Take the specified subset.
use joinqueryover or joinalias to achieve the same..
var c = session.Query<Table1>().Take(5).FetchMany(x => x.Table2).ToList();
this becomes
Table2 table2Alias=null;
var c = session.QueryOver<Table1>().JoinAlias(x => x.Table2,()=>table2Alias).Take(5).ToList();
let me know if it works out for you.. or you can write an HQL query instead

Related

Many to many query joins in aqueduct

I have A -> AB <- B many to many relationship between 2 ManagedObjects (A and B), where AB is the junction table.
When querying A from db, how do i join B values to AB joint objects?
Query<A> query = await Query<A>(context)
..join(set: (a) => a.ab);
It gives me a list of A objects which contains AB joint objects, but AB objects doesn't include full B objects, but only b.id (not other fields from class B).
Cheers
When you call join, a new Query<T> is created and returned from that method, where T is the joined type. So if a.ab is of type AB, Query<A>.join returns a Query<AB> (it is linked to the original query internally).
Since you have a new Query<AB>, you can configure it like any other query, including initiating another join, adding sorting descriptors and where clauses.
There are some stylistic syntax choices to be made. You can condense this query into a one-liner:
final query = Query<A>(context)
..join(set: (a) => a.ab).join(object: (ab) => ab.b);
final results = await query.fetch();
This is OK if the query remains as-is, but as you add more criteria to a query, the difference between the dot operator and the cascade operator becomes harder to track. I often pull the join query into its own variable. (Note that you don't call any execution methods on the join query):
final query = Query<A>(context);
final join = query.join(set: (a) => a.ab)
..join(object: (ab) => ab.b);
final results = await query.fetch();

Using LINQ to pull collection until aggregate condition met

At a high level, I need a query that can pull a subset of records based on the sum of a column, just like Linq: How to query items from a collection until the sum reaches a certain value.
However, the key difference is that he's already got his records in an object, and I don't and can't. My table can have millions of records. If I build my query the way he did, I get this error:
"A lambda expression with a statement body
cannot be converted to an expression tree"
Which makes sense after researching it, LINQ can't turn the answer in the above referenced question into valid SQL.
I'm going to make a hypothetical table that represents my situation.
Order Id | Cookie Name | Qty
1 Sugar 5
2 Snickerdoodle 4
3 Chocolate chip 8
4 Snickerdoodle 10
5 Snickerdoodle 5
Given this sample, I need to write a query that grabs the first X orders of Snickerdoodle until the summed Qty exceedes an input from the parameter (i.e. If the user chooses 13, it would return records 2 & 4 ).
I'm using Nhibernate.Linq, because I'm more comfortable in LINQ. I'm completely open to ICreate if the need arises.
As a side note, I'm interested in this as a concept as well as a direct problem. Even though I need a Sum, there has to be a way to do something akin to a takewhile that executes until a condition is met.
pragmatic approach
int needed = ...;
int actual = 0;
int page = 0;
const int pagesize = 20; // set to some sensible value, eg. the pagesize of the grid shown to the user
var results = new List<CookieOrder>();
while (actual < needed)
{
var partialResults = session.Query<CookieOrder>()
.Where(c => c.Name == "Snickerdoodle")
.OrderBy(c => c.Id)
.Skip(page * pagesize)
.Take(pagesize)
.ToList();
for(int i = 0; i < partialResults.Length && actual < needed; i++)
{
results.Add(partialResults[i]);
actual = partialResults[i].Quantity;
}
page++;
}
return results;

Need help creating a linq select

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

Getting duplicate results in query

I have Three tables A, B, and C. A is a parent table with mutiple child records in B and C.
When I query into A I am getting too many records, as though FNH is doing a Cartesian product.
My query is of the form:
var list = session.Query<A>()
.Fetch(a=> a.Bs)
.Fetch(a=> a.Cs)
Where Bs is the IList property of A, and Cs is the IList property of A.
I should get only as many Bs as relate to A, and only as many Cs relate to A. Instead I get BxC elements of each.
Is there a better way to load these? I am pretty sure I avoided this exact issue in the past, but don't see it in my old example code.
I'm not sure if this is a NH bug or a mapping issue, however the query could be optimised to
session.Query<A>()
.Fetch(a=> a.Bs)
.ToFuture();
var results = session.Query<A>()
.Fetch(a=> a.Cs)
.ToFuture()
.ToList();
You could use a Transformer to get a distinct result:
var list = session.Query<A>()
.Fetch(a=> a.Bs)
.Fetch(a=> a.Cs)
.SetResultTransformer( Transformers.DistinctRootEntity )
This is NH3.2 syntax, for 2.1 you need to use new DistinctRootEntityTransformer() (I think) as parameter to SetResultTransformer instead.

How can I recreate this complex SQL Query using NHibernate QueryOver?

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.