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
Related
I'm reasonably new to nHibernate and I've been trying to write a query. I cant seem to get it right. I have in my model, a "Product" which contains a ICollection of "Component". I need to find all products that contain any components that have a component reference starting with the letter "G". I have tried this:
var matching = session.QueryOver<Product>()
.JoinQueryOver<Component>(p => p.Components)
.Where(c => c.ComponentReference.StartsWith("G")).List();
However I am getting a compile error saying 'Delegate System.Func>> does not take 1 parameter.
There is an overload on JoinQueryOver where I can pass in an Expression>>
So I would have thought my query would work since ICollection implements IEnumerable.
I have tried various other ways using .Cast and JoinAlias but they just seem unecessarily complicated.
Can anyone point out where I am going wrong?
Thanks in advance
I would suggest to use the subquery, in this case. It could look like this
Product product = null;
Component component = null;
// the subselect, returning the Product.ID
// only if the Component.ComponentReference is like G%
var subQuery = QueryOver.Of<Component>(() => component)
.WhereRestrictionOn(() => component.ComponentReference)
.IsLike("G", MatchMode.Start)
.Select(c => c.Product.ID);
// the root Product
var query = session.QueryOver<Product>(() => product)
.WithSubquery
.WhereProperty(() => product.ID)
.In(subQuery);
// and finally the list
var list = query
.List<Product>();
The resulting SQL would be like this:
SELECT product
FROM product
WHERE productId IN (SELECT ProductId FROM Component WHERE ComponenentReferece LIKE 'G%')
And why to use subquery instead of JOIN? because the join would in this case result in Carthesian product. The set of products returned would be multiplied by all the components starting with G%
The subquery, will result in pure, flat set of Products, so we can correctly use paging over it (.Take() .Skip())
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'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"
I am using Nhibernate v2.1.2.4000. With many-to-many relationship between Posts an Tags I have the query:
tags
.Select(t => new { Name = t.Name, Count = t.Posts.Count })
.OrderBy(x => x.Count);
Ordering anonymous type fails (reference not set to an instance of an object). Is this issue something related to LinqToNH? What can be the source of this error? What is the solution?
If it is something related to LinqToNH then how it can be solved with some other option (ie Criteria API)?
EDIT: When I try Adam's ICriteria option, SqlProfiler says executed script is:
SELECT this_.Name as y0_, count(this_.Id) as y1_ FROM Tag this_ GROUP BY this_.Name ORDER BY count(this_.Id) asc
Mapping for Tag:
public class TagMap : ClassMap<Tag>
{
public TagMap()
{
Table("Tag");
Id(x => x.Id).GeneratedBy.GuidComb();
Map(x => x.Name);
HasManyToMany(x => x.Posts)
.Table("PostTags")
.ChildKeyColumn("Post")
.ParentKeyColumn("Tag")
.Cascade.None().Inverse();
}
}
There are many things in NHibernate.Linq for NHibernate 2.1.2.4000 that just won't work. You could use HQL or ICriteria instead, or upgrade to NHibernate 3.0, or if you're going to use all the data, force your Linq query to execute after the Select by adding a ToList.
tags
.Select(t = new { t.Name, t.Posts.Count })
.ToList()
.OrderBy(x => x.Count);
The anonymous object by itself is something that NHibernate.Linq can definitely handle.
By the way, you don't have to specify the field name in an anonymous object if it's the same as the field/property you're dragging it from.
EDIT: An ICriteria version of this query would look like this...
var tags = session.CreateCriteria(typeof(Tag), "tag")
.SetProjection(
Projections.GroupProperty("tag.Name"),
Projections.Count("tag.Posts"))
.AddOrder(Order.Asc(Projections.Count("tag.Posts")))
.List();
EDIT: With a proper mapping I'm getting the same SQL, Arch. My earlier mapping was wrong. This one seems to work however.
var tags = session.CreateCriteria(typeof(Tag), "tag")
.CreateCriteria("tag.Posts", "post")
.SetProjection(
Projections.GroupProperty("tag.Name"),
Projections.Count("post.Id"))
.AddOrder(Order.Asc(Projections.Count("post.Id")))
.List();
The SQL I get is this...
SELECT this_.Name as y0_, count(post1_.Id) as y1_ FROM Tag this_ inner join Post_Tags posts3_ on this_.Id=posts3_.Tag inner join Post post1_ on posts3_.Post=post1_.Id GROUP BY this_.Name ORDER BY count(post1_.Id) asc
Try ordering first and then selecting. I have very similar queries on 2.1.2.4 that work perfectly.
Edit: Also try switching between Count and Count()
my code is:
List<Benutzer> users = (from a in dc.Benutzer
select a).ToList();
I need this code but I only want to select 3 of the 20 Columns in the "Benutzer"-Table.
What is the syntax for that?
Here's a query expression:
var users = (from a in dc.Benutzer
select new { a.Name, a.Age, a.Occupation }).ToList();
Or in dot notation:
var users = dc.Benutzer.Select(a => new { a.Name, a.Age, a.Occupation })
.ToList();
Note that this returns a list of an anonymous type rather than instances of Benutzer. Personally I prefer this approach over creating a list of partially populated instances, as then anyone dealing with the partial instances needs to check whether they came from to find out what will really be there.
EDIT: If you really want to build instances of Benutzer, and LINQ isn't letting you do so in a query (I'm not sure why) you could always do:
List<Benutzer> users = dc.Benutzer
.Select(a => new { a.Name, a.Age, a.Occupation })
.AsEnumerable() // Forces the rest of the query to execute locally
.Select(x => new Benutzer { Name = x.Name, Age = x.Age,
Occupation = x.Occupation })
.ToList();
i.e. use the anonymous type just as a DTO. Note that the returned Benutzer objects won't be associated with a context though.
List<Benutzer> users = (from a in dc.Benutzer
select new Benutzer{
myCol= a.myCol,
myCol2 = a.myCol2
}).ToList();
I think that's what you want if you want to make the same kind of list. But that assumes that the properties you are setting have public setters.
try:
var list = (from a in dc.Benutzer select new {a.Col1, a.Col2, a.Col3}).ToList();
but now you have list of anonymous object not of Benutzer objects.