How does fetch work [duplicate] - nhibernate

This question already has an answer here:
Closed 11 years ago.
Possible Duplicate:
NHibernate Join Fetch(Kind)
I have read article.
http://fabiomaulo.blogspot.com/2010/03/conform-mapping-components.html
Tried to reproduce it on my project and got strange behavioral.
I have two entites:
Plan
{
public virtual int Id { get; set; }
public virtual Payer Payer { get; set; }
}
Payer
{
public virtual int Id { get; set; }
public virtual string Name { get; set; }
}
Mapping for it:
orm.Cascade<Payer, Plan >(CascadeOn.Persist | CascadeOn.Merge);
orm.Cascade<Plan, Payer>(CascadeOn.None);
Fetch:
mapper.Customize<CarePlan>(cm => cm.ManyToOne(o => o.Payer, x =>
x.Fetch(FetchKind.Join)));
Simple get:
var plan = session.Query<Plan>().Where(c=>c.Id ==
1).SingleOrDefault();
Console.WriteLine(plan.Payer.Name);
And I got this queries:
select careplan0_.Id
careplan0_.PayerId
from CarePlans careplan0_
where careplan0_.Id = 1 /* #p0 */
and other query
SELECT payer0_.Id ,
payer0_.Name as Name
FROM Payers payer0_
WHERE payer0_.Id = 2
Why I got 2 queries? How can I get one query with join?

Try
var plan = session.Query()
.Where(c=>c.Id == 1)
.Fetch(x => x.Player).Eager
.SingleOrDefault();
Try disabling lazy load, so it will eager load by default

Related

Fluent Nhibernate generating incorrect query resulting in Cartesian product instead of single row

After trying numerous things, I am still unable to figure out the right way to query the following relationship using Fluent NHibernate.
This would otherwise have been an easier task if I had to write SQL queries. Hope to get some better advice to avoid N+1 issue and unoptimized auto-generated SQL queries.
I have the following relationship which goes something as below :
Tags can have media content (Images of various predefined sizes, videos, documents etc.) associated with it.
- 1 Tags can have multiple Media Items mapped to it (Lets's say images with dimension 32x32, 64x64, 600x100, 0 or more Videos)
- Every media item is mapped to a media description which helps in identifying the size and type of the media
- The same media item can be used by a different tag. Example, having a generic image for all tags which do not have any icons.
Entities:
Media
public class Media:IEntity
{
private ICollection<TagMedia> _tagMedia;
public virtual int Id { get; set; }
public virtual string FilePath { get; set; }
public virtual MediaType MediaType { get; set; }
public virtual ICollection<TagMedia> TagMedia
{
get { return _tagMedia?? (_tagMedia= new List<TagMedia>()); }
protected set { _tagMedia= value; }
}
}
Tag
public class Tag:IEntity
{
private ICollection<TagMedia> _tagMedia;
public virtual int Id { get; set; }
public virtual string Name { get; set; }
public virtual ICollection<TagMedia> TagMedia
{
get { return _tagMedia?? (_tagMedia= new List<TagMedia>()); }
set { _tagMedia= value; }
}
}
TagMedia
public class TagMedia :IEntity
{
public virtual int Id { get; set; }
public virtual Media Media { get; set; }
public virtual Tag Tag { get; set; }
public virtual DateTime AddedOn { get; set; }
}
MediaType
public class MediaType:IEntity
{
public virtual int Id { get; set; }
public virtual string Description { get; set; }
}
Mappings
MediaMapping
public class MediaMapping : IAutoMappingOverride<Media>
{
public void Override(AutoMapping<Media> mapping)
{
mapping.Map(c => c.FileName).CustomSqlType("varchar(60)").Not.Nullable();
}
}
TagMapping
public class TagMapping : IAutoMappingOverride<Tag>
{
public void Override(AutoMapping<Tag> mapping)
{
mapping.HasMany<TagMedia>(c => c.TagMedia)
.KeyColumn("TagId")
.Cascade.SaveUpdate()
.BatchSize(25);
mapping.BatchSize(25);
mapping.DynamicUpdate();
mapping.DynamicInsert();
}
}
TagMediaMapping
public class TagMediaMapping : IAutoMappingOverride<TagMedia>
{
public void Override(AutoMapping<TagMedia> mapping)
{
mapping.Map(c=>c.AddedOn);
}
}
Query:
The following query gets the cartesian product of all media mapped to the tag and does not eliminate records other than "Icon-16x16". I expect the ORM to return no more than one row.
Any help would be highly appreciated.
_session.Query<Tag>()
.FetchMany(x => x.TagMedia)
.ThenFetch(x => x.Media)
.ThenFetch(x=>x.MediaType)
.Where(c => c.Id == id
&& c.TaxonomyMedia.Any(x=>x.Media.MediaType.Description== "Icon-16x16"))
.SingleOrDefault();
Generated SQL:
exec sp_executesql N'select *
from [Tag] Tag0_ left outer join [TagMedia] Tagme1_ on Tag0_.TagId=Tagme1_.TagId
left outer join [Media] media2_ on Tagme1_.MediaId=media2_.MediaId
left outer join [MediaType] mediatype3_ on media2_.MediaTypeId=mediatype3_.MediaTypeId
where Tag0_.TagId=#p0
and (exists (select Tagme4_.TagMediaId
from [TagMedia] Tagme4_ inner join [Media] media5_ on Tagme4_.MediaId=media5_.MediaId inner join [MediaType] mediatype6_ on media5_.MediaTypeId=mediatype6_.MediaTypeId where Tag0_.TagId=Tagme4_.TagId and mediatype6_.MediaTypeDescription=#p1))',N'#p0 int,#p1 nvarchar(4000)',#p0=102,#p1=N'Icon-16x16'
go
the SingleOrDefault function is a LINQ function and not implemented by NHibernate. Use .Take(1)
I think the solution is:
_session.Query<Tag>()
.FetchMany(x => x.TagMedia)
.ThenFetch(x => x.Media)
.ThenFetch(x=>x.MediaType)
.Where(c => c.Id == id
&& c.TaxonomyMedia.Any(x=>x.Media.MediaType.Description== "Icon-16x16"))
.Take(1)
.SingleOrDefault();

NHibernate Query w/ criteria against collection items

I have the following configuration in the database.
Database Schema
I want to be able to query all the Individuals where they are either an employee or vendor. Of the examples I've seen I can't get any of them to work. The code doesn't throw an error, it just doesn't bring back any records.
Here are the DTO's
public class Individual
{
public virtual int Sid { get; set;}
public virtual string Id { get; set; }
public virtual string FirstName { get; set; }
public virtual string LastName { get; set; }
public virtual string MiddleInitial { get; set; }
public virtual ISet<Company> Companies { get; set; }
}
public class Company
{
private bool _inactive;
public virtual int Sid { get; set; }
public virtual string Name { get; set; }
public virtual IList<Individual> Individuals { get; set; }
public virtual bool Active
{
get { return !_inactive; }
set { _inactive = value; }
}
public virtual bool IsVendor { get; set; }
}
public class IndividualCompany
{
public virtual Individual Individual { get; set; }
public virtual Company Company { get; set; }
public override bool Equals(object obj)
{
if (obj == null)
return false;
IndividualCompany key = obj as IndividualCompany;
if (key == null)
return false;
if (Individual.Sid == key.Individual.Sid && Company.Sid == key.Company.Sid)
return true;
return false;
}
public override int GetHashCode()
{
int hash = 13;
hash = 7 * hash * Individual.Sid.GetHashCode();
hash = 7 * hash * Company.Sid.GetHashCode();
return hash;
}
}
I have another function that uses these 3 tables and all my Individuals and the collection of Companies gets populated just fine so I know the mapping is working. I just don't know how to add criteria on the child record.
string sql = "from Individual i" +
" inner join fetch i.Companies";
return _session.CreateQuery(sql).List<Individual>();
Here's the query I'd like it to produce.
select i.*
from individual i inner join individual_company_assoc ica
on i.individual_sid = ica.individual_sid
inner join company c
on ica.company_sid = c.company_sid
where c.is_vendor = 0
Here's what I've tried:
public IList<Individual> Get(bool vendorsOnly)
{
try
{
return _session.CreateCriteria<Individual>()
.CreateAlias("Company", "c")
.Add(Restrictions.Eq("c.IsVendor", vendorsOnly))
.List<Individual>();
}
catch (NHibernate.HibernateException)
{
throw;
}
}
I'm not using FluentNHibernate. Any help would be greatly appreciated.
An nHibernate query will not automatically join with other tables, even if you reference columns of a joining table in the query.
You must explicitly state that the query should join with the Company table.
Here's an example which will work. It uses the QueryOver syntax, which is a type-safe wrapper around the Criteria API.
return _session
.QueryOver<Individual>()
.JoinQueryOver<Company>(i => i.Companies>())
.Where(c => c.IsVendor == vendorsOnly)
.TransformUsing(NHibernate.Transform.Transformers.DistinctRootEntity)
.List();

How to retrieve entities from a join using QueryOver in NHibernate

I'm trying to return multiple entities from a QueryOver query. I'm doing this code in a plain text editor so there may be syntactic errors, but it should get the idea across.
public class Product
{
public virtual int ID { get; set; }
public virtual string ProductName { get; set; }
public virtual List<Category> Categories { get; set; }
public virtual List<Inventory> Inventories { get; set; }
...
}
public class Category
{
public virtual int ID { get; set; }
public virtual string CategoryName { get; set; }
public virtual string Style { get; set; }
public virtual Product Product { get; set; }
...
}
public class Inventory
{
public virtual int ID { get; set; }
public virtual List<Discount> Discounts { get; set; }
public virtual Product Product { get; set; }
public virtual bool InStock { get; set; }
...
}
public class Discount
{
public virtual int ID { get; set; }
public virtual Inventory Inventory { get; set; }
public virtual decimal DiscountAmount { get; set; }
...
}
Now my goal is to take a product ID and a couple other options to pull back a Category, Inventory, and DiscountAmount in a single query. I've gotten this to work using HQL with this query:
var query = session.CreateQuery("select category, inventory, discount.DiscountAmount"
+ " from Product product"
+ " join product.Categories category"
+ " join product.Inventories inventory"
+ " left join inventory.Discounts discount"
+ " where product.ID = :productID"
+ " and category.Style = :style"
+ " and inventory.InStock = 1");
With this query I get an list of object arrays that each have a Category entity, Inventory entity, and a DiscountAmount decimal. My goal is to use a QueryOver query to do this same query with no magic strings, but I can't get it to work. Here's what I've tried so far:
Product productAlias = null;
Category categoryAlias = null;
...
var query = session.QueryOver<Product>(() => productAlias)
.Where(() => productAlias.ID == productID)
.JoinAlias(() => productAlias.Categories, () => categoryAlias)
...
.Select(Projections.Property(() => categoryAlias.ID),
Projections.Property(() => discountAlias.Inventory),
Projections.Property(() => discount.DiscountAmount));
This query only pulls back the ID for Category, and while it does pull the full Inventory entity back it uses a full additional database query to grab it.
...
.Select(Projections.Property(() => categoryAlias),
Projections.Property(() => inventoryAlias),
Projections.Property(() => discountAlias.DiscountAmount));
This query throws a runtime exception of "Could not resolve property: categoryAlias of : Product".
...
.Select(Projections.Property(() => categoryAlias.ID).WithAlias(() => ReturnClass.Category),
Projections.Property(() => inventoryAlias.ID).WithAlias(() => ReturnClass.Inventory),
Projections.Property(() => discountAlias.DiscountAmount).WithAlias(() => ReturnClass.DiscountAmount))
.TransformUsing(Transformers.AliasToBean<ReturnClass>());
This query throws a runtime exception of "Object of type Int32 cannot be converted to type Category".
...
.Select(Projections.Property(() => categoryAlias.ID)
.TransformUsing(Transformers.AliasToBean<Category>())
This query returns a default Category entity.
So is there any way to mimic the HQL query using the QueryOver API, or is my only option to choose between HQL or making multiple queries?
Edit: To be more clear, I really want to avoid magic strings as much as possible, so I'd really prefer strongly typed QueryOver queries. Currently I'm using a QueryOver query that returns the IDs for the Category and Inventory entities and then querying for them separately, but since I have to hit those tables in the first query anyway I'd rather return them all at once.
Edit 2: The exact SQL I'm trying to achieve is
Select Category.ID, Category.CategoryName, Category.Style, (other Category columns),
Inventory.ID, Inventory.InStock, (other Inventory columns),
Discount.DiscountAmount
From Products as Product
Inner join Categories as Category ...
Where Product.ID = #productID
And Category.Style = #style
And ...
I think you should use Fetch instead of JoinAlias:
.Fetch(product => product.Categories).Eager
and don't use select: .List<Product>() // then by LINQ you can get Categories and Inventories from Product
So here is an example of "eager" loading all of the subcategories for my categories.
No N+1 when iterating over the categories collection. Future is the key here.
Category catalias = null;
var subCategories =_session.QueryOver<Category>().JoinQueryOver(x => x.SubCategories, () => catalias, JoinType.LeftOuterJoin).
Future<Category>();
var categories = _session.QueryOver<Category>().Where(x => x.ParentCategoryId == null).Future<Category>();

LINQ-to-NHibernate: Cannot use Linq Skip() and Take() with FetchMany

I have these entities:
public class BlogPost {
public virtual int Id { get; set; }
public virtual IList<Keyword> Keywords { get; set; }
public virtual IList<BlogComment> Comments { get; set; }
}
public class BlogComment {
public virtual int Id { get; set; }
public virtual BlogPost Post { get; set; }
}
public class Keyword {
public virtual int Id { get; set; }
public virtual IList<BlogPost> BlogPosts { get; set; }
}
I want to load a paged-list of BlogPosts by their Keywords and comments-count. So I try this:
var entities = session.Query<BlogPost>()
.Where(t => t.Published)
.FetchMany(t => t.Keywords)
.OrderByDescending(t => t.UpdatedAt)
.Skip((pageNumber - 1) * pageSize).Take(pageSize)
.Select(t => new {
CommentsCount = t.Comments.Count(),
Post = t
})
.ToList();
But the folowing error occurs:
Specified method is not supported.
And when I remove .Skip((pageNumber - 1) * pageSize).Take(pageSize) it works! e.g.
var entities = session.Query<BlogPost>()
.Where(t => t.Published)
.FetchMany(t => t.Keywords)
.OrderByDescending(t => t.UpdatedAt)
// remove the below line
//.Skip((pageNumber - 1) * pageSize).Take(pageSize)
.Select(t => new {
CommentsCount = t.Comments.Count(),
Post = t
})
.ToList();
Have you any idea please to take a number of rows by including Keywords? Thanks for any suggestion.
I'm using NHibernate 3.2 mapping by code.
The problem is that the nhibernate linq provider isn't fully implemented yet.
You could move the skip / take calls to be after the ToList() but then you're going to be filtering on the entire result set rather than querying specifically for the records matching that range.
Alternatively you could use the QueryOver<> api which has proper support for Take and Skip as per this answer: https://stackoverflow.com/a/5073510/493
This should now be supported in 3.3.3.GA
http://sourceforge.net/p/nhibernate/news/2013/03/nhiberate-333ga-released/

NHibernate left join select count in one-to-many relationship

I've been looking for a week after a correct synthax whithout success.
I have 2 classes :
public class ArtworkData
{
public virtual Guid Id { get; set; }
public virtual string Name { get; set; }
public virtual IList<CommentData> Comments { get; set; }
}
public class CommentData
{
public virtual Guid Id { get; set; }
public virtual string Text { get; set; }
public virtual ProfileData Profile { get; set; }
public virtual ArtworkData Artwork { get; set; }
public virtual DateTime Created { get; set; }
}
I want to do this query :
SELECT this_.ArtworkId as ArtworkId3_3_,
this_.Name as Name3_3_,
this_.Description as Descript3_3_3_,
FROM Artwork this_
LEFT outer JOIN
(SELECT c.ArtworkIdFk, count(1) Cnt
FROM Comment c
GROUP BY c.ArtworkIdFk) as com
on com.ArtworkIdFk = this_.ArtworkId
ORDER BY 1 desc
But I don't find the way to.
At this moment I just have something like this :
ICriteria c = this.Session.CreateCriteria(typeof(ArtworkData));
if(filter.Category !=null)
{
c.CreateAlias("Categories", "cat")
.Add(Restrictions.Eq("cat.Id", filter.Category.Id));
}
DetachedCriteria crit = DetachedCriteria.For(typeof(CommentData), "comment")
.SetProjection(Projections.ProjectionList()
.Add(Projections.Count("comment.Id").As("cnt"))
.Add(Projections.GroupProperty("comment.Artwork.Id")));
c.Add(Expression.Gt(Projections.SubQuery(crit), 0));
c.AddOrder(Order.Desc(Projections.SubQuery(crit)));
But it's not what I want.
I want to get all Artworks order by the number of comments (but I don't need to get this number).
Please help me! I'm going crazy!
I don't understand what are you trying to do with this weird SQL but if you need to get all Artworks with number of comments you can try this query:
<query name="ArtworkWithCommentsCount">
SELECT artwork.Name, artwork.Comments.size
FROM Artwork artwork
</query>
If you use NHibernate 3 you could use this code:
var artworks = Session.Query<Artwork>().OrderBy(a => Comments.Count);
Or you could use HQL:
Session.CreateQuery("from Artwork a order by size(a.Comments)")
Try Detached Criteria. Take a look at this blogpost.