Preventing multiple instance after inner join - nhibernate

I have a small problem with multiple instances of the same object after a join to an other table. For testing I create one Store with two Products (ManyToMany-Relation). The following snippet hopefully describes my problem.
var preResult = _session.QueryOver<Store>().List(); // One store
Product productAlias = null;
var result = _session.QueryOver<Store>()
.JoinAlias(s => s.Products, () => productAlias)
.List(); // Two instances of the same store
I even think this behavior is correct but how can I prevent the multiple instances? Is it possible within the query?
Just for information why I need to make this unnecessary join: I want to extend the query according to different critirias, similar to this:
Product productAlias = null;
var query = _session.QueryOver<Store>().JoinAlias(s => s.Products, () => productAlias);
if (!string.IsNullOrWhiteSpace(criteria.ProductName))
{
query.Where(Restrictions.On(() => productAlias.Name).IsInsensitiveLike(criteria.ProductName));
}
if (criteria.ProductType != null)
{
query.Where(s => productAlias.Type == criteria.ProductType);
}
var result = query.List();
Here I ran into different problems, depending on the criterias.

Try using Transformers.DistinctRootEntity in your scenario to eliminate the cartesian product.
Product productAlias = null;
var query = _session.QueryOver<Store>()
.JoinAlias(s => s.Products, () => productAlias)
query = query.TransformUsing(Transformers.DistinctRootEntity);
var result = query.List();

Let's split solution into two queries.
Top one QueryOver<Store>() will be correctly returning just a distinct list. And what's more, by design it will support paging (Take(), Skip()).
The inner one, will be returning just a list of Store IDs, which fully meet whatever criteria...
The result SQL will look like this
SELECT ... // top one
FROM Store
WHERE StoreID IN ( SELECT StoreID ...) // inner one
Inner
Let's start with the inner select, the NHibernate detached QueryOver:
Store storeAlias = null;
Product productAlias = null;
// detached query, resulting in a set of searched StoreID
var subQuery = QueryOver.Of<Store>(() => storeAlias)
.JoinAlias((s) => s.Products, () => productAlias)
.Select((s) => s.ID); // ID projection
if (!string.IsNullOrWhiteSpace(criteria.ProductName))
{
subQuery.Where(Restrictions.On(() => productAlias.Code)
.IsInsensitiveLike(criteria.ProductName));
}
Top
Once we have filtered the Store we can use this subquery in top one
var query = session.QueryOver<Store>()
// IN clause
.Where(Subqueries.PropertyIn("ID", subQuery.DetachedCriteria))
.Skip(100)
.Take(50) // paging over already distinct resultset
;
var result = query.List<Store>();
And now we can apply whatever filter to inner query, and get list of Store IDs which do meet filter criteria... while working with top query, which is distinct...

Related

Conditional Queryover depending on the context

I want to do a QueryOver on an object for which I want to do an outer join on its sublist only if the current object has a property set to true.
Let's say you have an object Store which has a list of Product, and Product has a sublist Phone
The object Store has a boolean property HasAllProducts.
If HasAllProducts is true, then Store doesn't need to know the list of products, so no outer join is needed.
IfHasAllProducts is false, then Store has to know its list of Products so an outer join is needed, and another outer join is needed between Product and Phone
So I would like a query like
session.QueryOver<Store>(() => storeAlias)
if (!storeAlias.HasAllProducts)
{
.Left.JoinAlias(s => s.Products, () => productAlias)
}
.Future<Store>();
//Then again to avoid catersian product
if (!storeAlias.HasAllProducts)
{
session.QueryOver<Product>(() => productAlias)
.Left.JoinAlias(p => p.Phones, () => phoneAlias)
.Future<Phone>();
}
I know that for the first join, you can use withClause, but how would you perform (or not perform) the second conditional join? it should not be executed if no Phoneis fetched in the previous query.
The problem with with clause is that it will generate a query in all cases, I would like to avoid running two additional queries if it's not needed.
I would suggest 2 queries.
var stores = session.QueryOver<Store>()
.Where(filter)
.Future();
// only to intialize needed child collections
session.QueryOver<Store>()
.Where(filter)
.Where(s => !s.HasAllProducts)
.JoinQueryOver<Exam>(s => s.Products)
.Fetch(SelectMode.ChildFetch, e => e.Phones) // NHibernate 5
.Fetch(x => x.Phones).Eager // NHibernate 3
.Future();
Another option is to use CollectionBatchSize to not eager load here but use lazy loading with batches on usage

LinqToSql OrderBy has no Effect

I am using a LinqToSql-DataSource for a GridView in this way:
wsv.wsv2DataContext db = new wsv.wsv2DataContext();
e.KeyExpression = "id";
e.QueryableSource = (from mitgliedschaft in db.mitgliedschaft
join person in db.person on mitgliedschaft.person_id equals person.id
join institution in db.institution on mitgliedschaft.verein_id equals institution.id
select new
{
vorname = person.vorname,
nachname = person.nachname,
nameVerein = institution.name,
vereinid = mitgliedschaft.verein_id,
id = mitgliedschaft.id,
verbandsMitgliedsNummer = person.verbandsMitgliedsNummer,
strasse = person.strasse,
plz = person.plz,
ort = person.ort,
geburtsdatum = person.geburtsdatum,
geschlechtid = person.geschlechtid,
statusid = mitgliedschaft.statusid,
bezirk_id = mitgliedschaft.bezirk_id,
kreis_id = mitgliedschaft.kreis_id,
person_id = mitgliedschaft.person_id.Value,
deletedFlag = mitgliedschaft.deletedFlag,
stammverein = mitgliedschaft.stammVerein,
eintrittsdatum = mitgliedschaft.eintritt
}).GroupBy(p => p.person_id).Select(p => p.First());
}
Now i want to order the Selection. At first the "stammVerein"-Column of Table "mitgliedschaft" descending AND the Column "eintritt" of Table "mitgliedschaft". I have tried several ways:
wsv.wsv2DataContext db = new wsv.wsv2DataContext();
e.KeyExpression = "id";
e.QueryableSource = (from mitgliedschaft in db.mitgliedschaft
join person in db.person on mitgliedschaft.person_id equals person.id
join institution in db.institution on mitgliedschaft.verein_id equals institution.id
orderby mitgliedschaft.stammVerein descending, mitgliedschaft.eintritt
select new
{
...
}).GroupBy(p => p.person_id).Select(p => p.First());
}
AND:
wsv.wsv2DataContext db = new wsv.wsv2DataContext();
e.KeyExpression = "id";
e.QueryableSource = (from mitgliedschaft in db.mitgliedschaft
join person in db.person on mitgliedschaft.person_id equals person.id
join institution in db.institution on mitgliedschaft.verein_id equals institution.id
select new
{
...
}).GroupBy(p => p.person_id).Select(p => p.First()).OrderByDescending(stamm => stamm.stammverein).ThenBy(eintritt => eintritt.eintrittsdatum);
}
AND:
wsv.wsv2DataContext db = new wsv.wsv2DataContext();
e.KeyExpression = "id";
e.QueryableSource = (from mitgliedschaft in db.mitgliedschaft
join person in db.person on mitgliedschaft.person_id equals person.id
join institution in db.institution on mitgliedschaft.verein_id equals institution.id
select new
{
....
}).OrderByDescending(stamm => stamm.stammverein).ThenBy(eintritt => eintritt.eintrittsdatum).GroupBy(p => p.person_id).Select(p => p.First());
But nothing of this has any Effects ! I am very new in this kind of DataSource and Linq.
Can anyone help me achieving this order ?
Items within a grouped result will not retain their order. Depending on how you want to factor in the ordering, you will need to do it after the group by, and before, and/or after your First...
To accomplish this, it will be easiest if you map the relationships in EF with navigation properties rather than substituting SQL with Linq QL (joins and such)
Using the following base query:
var query = db.mitgliedschaft
.GroupBy(m => m.Person); // Group by related entity, not ID
For instance, after the group by, you will have sets of records grouped by Person. If you want the first Person with an earliest related record:
var result = query.OrderByDescending(g => g.Key.mitgliedschafts.Max(stamm => stamm.stammverein)
.ThenBy(stamm => stamm.eintritt.eintrittsdatum)
.First();
This is taking a wild guess at your schema & entity relationships, but hopefully it will help you work out something that fits. I can only guess at what eintritt is and how it relates to your entity model.
The initial query takes just your base entities that you want to group, and groups them by the related entity. The result of that grouping will be a set of Grouped mitgliedschafts with a key being the Person. To Order those groups by the person with the most recent mitgliedschafts we use an orderby on the Key's associated mitgliedschafts using the Max value for the collection given a descending order request.
The First then gives us the first grouped collection of mitgliedschafts.
Then if you want to sort the resulting list of mitgliedschafts after getting the person with the most recent one:
var result = query.OrderByDescending(g => g.Key.mitgliedschafts.Max(stamm => stamm.stammverein)
.ThenBy(stamm => stamm.eintritt.eintrittsdatum)
.First().OrderByDescending(stamm => stamm.stammverein)
.ThenBy(stamm => stamm.eintritt.eintrittsdatum)
.ToList();
The 2nd set of OrderBy clauses apply to the selected group, or the mitgliedschafts.
To compose the desired view model, Insert a Select() to build the view model from the mitgliedschafts before the ToList().
With the navigation properties this can probably be done without resorting to a group by. On a hunch, something like this should return something similar:
var query = db.Person
.OrderByDescending(p => p.mitgliedschafts.Max(stamm => stamm.stammverien))
.ThenBy(stamm => stamm.eintritt.eintrittsdatum)
.SelectMany(p => p.mitgliedschafts)
.OrderByDescending(stamm => stamm.stammverien)
.ThenBy(stamm => stamm.eintritt.eintrittsdatum)
.Select(stamm => new { ... })
.ToList();
Anyhow, hopefully that gives you some ideas on things to try if you have the navigation properties mapped or can set those up.

Linq-to-NHibernate - How to do a Join with a SelectMany

Is it possible in Linq-to-NHibernate to do a Join with a SelectMany within the same query?
It might look weird, but the idea would be to generate the following query:
select * from State
join (
select CityId, StateId
from State
inner join City on State.StateId=City.StateId
) as City on City.StateId = State.StateId
The reason is that we are using a repository pattern and I do not want to use 2 different repository to do my join.
In code, I have tried the following block, but I get a not supported exception
var states = Session.Query<State>();
var query = states.Join(states.SelectMany(x => x.Cities), state => state.StateId,
city => city.State.StateId, (state, city) => new {state, city});
var result = query.ToArray(); // <- Throws a not supported exception
I have tried the same thing with two repositories and it works:
var states = Session.Query<State>();
var cities = Session.Query<City>();
var query = states.Join(cities , state => state.StateId,
city => city.State.StateId, (state, city) => new {state, city});
var result = query.ToArray(); // <- This works perfectly fine
Thanks in advance!
Ah! I found out that I wasn't thinking the right way. I can do the equivalent by starting with a SelectMany, then do a select of both.
This is supported and will do the same thing as a join but even more optimized:
var states = Session.Query<State>();
var query = states.SelectMany(x => x.Cities).Select(city => new {city.State, city});
var result = query.ToArray(); // It works!
And with that, I don't even need to get a repo of City.

LINQ to EF Using a Collection in a Where Clause

I have a main VendorProfile table and a 1-many VendorHistory table that contains status codes and date stamps. The query below works at retrieving only the latest status (status code and date) for each vendor. However, the view allows the user to select checkboxes of any of the status codes to filter the view. So I need to add a where clause that matches ANY of the checkbox StatusSelections.
Model Diagram
public IEnumerable<BrowseStatusModel> BrowseByStatus(int[] StatusSelections)
{
IQueryable<BrowseStatusModel> query = _db.VendorProfiles
.Include("VendorStatusHistory")
.Include("StatusCodes")
.Select(s => new BrowseStatusModel
{
ProfileID = s.ProfileID,
Name = s.Name,
CompanyName = s.CompanyName,
CompanyDBA = s.CompanyDBA,
DateCreated = s.DateCreated,
Status = s.VendorStatusHistories.OrderByDescending(o => o.DateCreated).FirstOrDefault().Id,
StatusDate = s.VendorStatusHistories.OrderByDescending(o => o.DateCreated).FirstOrDefault().DateCreated
})
.OrderBy(x => x.ProfileID);
foreach (int status in StatusSelections)
{
query = query.Where(x => x.Status == status);
}
return query;
}
The above foreach loop works but, unfortunately creates AND condition where ALL selections must be true instead of ANY. I figured I would have to use a where clause with the following in some way but have been unsuccessful at the correct syntax.
.AsQueryable().Any();
Use contains in the place of that foreach loop
query = query.Where(x => StatusSelections.Contains(x.Status))

Nhibernate query<T> / queryover<T> orderby a subquery

I am having issues getting Nhibernate 3.3.2.4000 to generate the correct subquery used in the orderby clause as displayed below:
select *
from dbo.Person p inner join dbo.Task t on p.Task_FK = p.TaskId
order by (select p.CustomerNumber where p.IsMain=1) desc
We have two entities: Task and Person
One task can have N persons related to it. I.e Task has an IList property.
How can I make Nhibernate generate the correct subquery ? I have gotten as far as something like this with the Query API:
query = query.OrderBy(x => x.Persons.Single(t => t.CustomerNumber));
but I am unsure how I can correctly generate the where clause as displayed in the original sql query. Is this perhaps easier done using the queryover api somehow?
Any advice or guidance is most welcome.
Task task = null
Person person = null;
var subquery = QueryOver.Of<Task>()
.Where(t => t.Id == task.Id)
.JoinQueryOver(t => t.Persons, () => person)
.Where(p => p.IsMain)
.Select(() => person.CustomerNumber);
var query = session.QueryOver(() => task)
.OrderBy(Projections.SubQuery(subquery))
.FetchMany(x => x.Persons)
return query.List();