How to force nhibernate to fully load many-to-many data in one sql query.
I have tried this:
var list = session.CreateCriteria<Q>("q")
.CreateAlias("q.PList", "p", JoinType.LeftOuterJoin)
.List();
But it loads only q.PList, and when I try to access q.PList[0].QList[0].PList[0] NH executes additional select query.
Entities and how I mapped them:
public class P
{
public virtual Guid Id { get; set; }
public virtual IList<Q> QList { get; set; }
}
public class Q
{
public virtual Guid Id { get; set; }
public virtual IList<P> PList{ get; set; }
}
public class PMap : ClassMap<P>
{
public PMap()
{
Table("p");
Id(t => t.Id);
HasManyToMany(t => t.QList)
.Table("q2p").ParentKeyColumn("pId").ChildKeyColumn("qId").Inverse();
}
}
public class QMap : ClassMap<Q>
{
public QMap()
{
Table("q");
Id(t => t.Id);
HasManyToMany(t => t.PList)
.Table("q2p").ParentKeyColumn("qId").ChildKeyColumn("pId");
}
}
I believe what you are really trying to do is:
var list = session.CreateCriteria<Q>()
.SetFetchMode("PList", FetchMode.Join)
.List();
Update based on your comment:
It's cumbersome and usually not worth it to try to fetch a whole graph with many collections using joins. For this particular case, I suggest that you use batch-size on the collections and let NH do a batched lazy-loading.
Depending on your code, it's also possible that a HQL query retrieves the data you want better than navigating the object graph.
or use HQL to achieve the same
your query will look as follows
var hqlQuery="select p from Q as q inner join fetch q.PList as p";
You run the query as follows:
Sesssion.CreateQuery(hqlQuery).List<P>();
Hope that helps.
Related
I am trying to join 2 tables, and project directly to DTOs (NHibernate 5).
I have following entities:
public class Person {
public Guid Id {get;set;}
public string Name {get;set;}
}
public class Car {
public Guid Id {get;set;}
public string Brand {get;set;}
public Person Owner {get;set;}
}
as we see, there is just reference from Car to Person (car knows its owner), and this is ok in my whole project.
However, there is one place, where I need to query all Persons, and make each person with collection of owned cars.
I created such DTOs:
public class PersonDto {
public Guid Id {get;set;}
public string Name {get;set;}
public IList<CarDto> {get;set;}
}
public class CarDto {
public Guid Id {get;set;}
public string Brand {get;set;}
}
it is kind of present the data linked together upside-down.
This task seems trivial using SQL or LINQ (GroupJoin) however I found it extremly hard to do in NH, since GroupJoin is not implemented in NH.
Can you please help me how to solve above issue?
Just add a Car collection to the existing Person entity and mark the collection inverse. Collections in NHibernate are lazy by default, so when you query Person, it won't read the cars from the DB, until you start iterating them. In other words adding the Car collection won't affect the way your code works.
When you want to efficiently query persons together with cars, force NH to do a join
.QueryOver<Person>.Fetch(person => person.Cars).Eager
I'd like to answer my own question. Thanks Rafal Rutkowski for his input.
To get data that has association in only 1 direction, we need to introduce a new data model, and then, manually convert result to our entities. I hope following example is best possible answer:
1) create such data model to store NH response:
private class PersonCarModelDto
{
public Guid PersonId { get; set; }
public string PersonName { get; set; }
public Guid CarId { get; set; }
public string CarBrand { get; set; }
}
2) create such a model to store hierarchical data as output:
private class PersonModel
{
public Guid Id { get; set; }
public string Name { get; set; }
public IList<CarModel> Cars { get; set; }
}
private class CarModel
{
public Guid Id { get; set; }
public string Brand { get; set; }
}
3) now the NH query:
Person personAlias = null;
Car carAlias = null;
PersonCarModelDto resultAlias = null;
var response = GetCurrentSession().QueryOver<Car>(() => carAlias) // notice we are going from from 'downside' entity to 'up'
.Right.JoinAlias(c => c.Owner, () => personAlias) // notice Right join here, to have also Persons without any car
.SelectList(list => list
.Select(() => personAlias.Id).WithAlias(() => resultAlias.PersonId)
.Select(() => personAlias.Name).WithAlias(() => resultAlias.PersonName)
.Select(() => carAlias.Id).WithAlias(() => resultAlias.CarId)
.Select(() => carAlias.Brand).WithAlias(() => resultAlias.CarBrand)
.TransformUsing(Transformers.AliasToBean<PersonCarModelDto>())
.List<PersonCarModelDto>()
;
4) now we have flat data as a list of PersonCarModelDto, but we want to make output model:
var modelResult = response.GroupBy(p => p.PersonId)
.Select(x => new PersonModel
{
Id = x.Key,
Name = x.Select(y => y.PersonName).First(), // First() because each PersonName in that group is the same
Cars = x.Select(y => new CarModel
{
Id = y.CarId,
Name = y.CarBrand
})
.ToList()
})
.ToList()
;
Conclusions:
the problem is much easier to solve having bidirectional associations
if for some reason, you don't want bidirectional associations, use this technique
this approach is also useful if your entities has a lot of other properties, but for some reason you need just a small part of them
(optimiziation of data returned for DB)
I have these entities:
public class Parent
{
public int Foo { get; set; }
public Child C { get; set; }
}
public class Child
{
public string Name { get; set; }
}
I have query which fetches all Parent entities from the database. Then I keep them in memory, and filter them using LINQ queries.
I have noticed that when I do the DB query, NH selects all the Parent entities in one query (and of course fills the Foo property), and for each Parent I access with LINQ, NH fetches the infos of each Child.
How can I do to fetch all infos I need in one unique DB, and use the data with LINQ without it to generate additional DB trips?
Should I use the AliasToBeanResultTransformer? If so, must I create a DTO which will store the infos, like:
public class ParentDTO
{
public int Foo { get; set; }
public string ChildName { get; set; }
}
or must I still use the Parent class?
Thanks in advance
You can eagerly load the children for this query like this (using QueryOver syntax)
public IList<Parent> FindAllParentsWithChildren()
{
ISession s = // Get session
return s.QueryOver<Parent>()
.Fetch(p => p.C).Eager
.List<Parent>();
}
An alternative is to change your HBM files to indicate that Child is eagerly loaded by default. Then you won't need to alter your query.
You need to tell NHibernate not to use lazy loading for the relationship between the Parent and Child entities.
I'm building an ecommerce site using S#arp Architecture.
I'm trying to map a hierachy of categories and retrieve the top level categories.
I'm using NHibernate.Linq for this.
I have the following entity:
public class Category : Entity
{
#region Properties
[DomainSignature]
[NotNullNotEmpty]
public virtual string Name { get; set; }
public virtual string Description { get; set; }
public virtual int ListOrder { get; set; }
public virtual IList<Product> Products { get; set; }
public virtual IList<Category> ParentCategories { get; set; }
public virtual IList<Category> ChildCategories { get; set; }
#endregion
public Category()
{
Products = new List<Product>();
ParentCategories = new List<Category>();
ChildCategories = new List<Category>();
}
}
with the following Fluent NHibernate mapping:
public class CategoryMap : ClassMap<Category>
{
public CategoryMap()
{
Id(x => x.Id);
Map(x => x.Name);
HasManyToMany(p => p.Products)
.Cascade.All()
.Table("CategoryProduct");
HasManyToMany(c => c.ParentCategories)
.Table("CategoryHierarchy")
.ParentKeyColumn("Child")
.ChildKeyColumn("Parent")
.Cascade.SaveUpdate()
.AsBag();
HasManyToMany(c => c.ChildCategories)
.Table("CategoryHierarchy")
.ParentKeyColumn("Parent")
.ChildKeyColumn("Child")
.Cascade.SaveUpdate()
.Inverse()
.LazyLoad()
.AsBag();
}
}
I want to retrieve the root categories. I know I have eight in my db so here's my test:
[Test]
public void Can_get_root_categories()
{
// Arrange
var repository = new CategoryRepository();
// Act
var rootCategories = repository.GetRootCategories();
// Assert
Assert.IsNotNull(rootCategories);
Assert.AreEqual(8, rootCategories.Count());
}
I figure I just get all Categories where the ParentCategories list is empty (initialized in the ctor of Category). So here's my repository method:
public IQueryable<Category> GetRootCategories()
{
var session = NHibernateSession.Current;
// using NHibernate.Linq here
var categories = from c in session.Linq<Category>()
where c.ParentCategories.Count == 0
select c;
return categories;
}
When I run my test I get "NHibernate.QueryException : could not resolve property: ParentCategories.Id of: MyStore.Core.Category"
What am I doing wrong?
Here are related questions, but didn't quite solve my problem:
Fluent nHibernate: Need help with ManyToMany Self-referencing mapping
Querying a self referencing join with NHibernate Linq
Fluent NHibernate: ManyToMany Self-referencing mapping
Edit:
I think the problem lies with the .count in the Linq expression. This post related to this, but I'm not sure how to progress...
Got it. The problem was in the linq expression. It didn't like .Count for some reason. This might be a bug in NHibernate.Linq (NH2), but I hear NH3 linq is rock solid now.
Anyway, my solution is to use a Criteria instead:
var categories = session.CreateCriteria(typeof (Category))
.Add(Restrictions.IsEmpty("ParentCategories"))
.List();
Thanks to a blog post by nixsolutions.com for getting me on the right track. All green again.
You should be using the Count() extension method instead of the Count property. This works fine in NHibernate 2.
Regards
Jon
I have an Class that is named Show one of the properties "Country" is a reference to another table.
Show Class
public class Show
{
public virtual int ID { get; set; }
public virtual Country CountryOrigin { get; set; }
public virtual string EnglishName { get; set; }
}
Country Class
public class Country
{
public virtual int ID { get; set; }
public virtual string Name { get; set; }
}
I have it all mapped and working, but now I am wanting to get more specific results. I have used the criteria api to get all the data and sort it, but now I only want to get shows based on country name. Here is what I thought would work, but apprently doesn't.
public IList<Show> AllShowsByCountry(string countryName)
{
IList<Show> shows;
shows = _session.CreateCriteria(typeof(Show))
.Add(Restrictions.Eq("CountryOrigin.Name", "China" ))
.AddOrder(Order.Asc("EnglishName"))
.List<Show>();
return shows;
}
I was thinking that the first part of the restriction might work similar to HQL and you can use objects.
1) The question I guess is am I mis-understanding how HQL works or criteria or both?
2) Also how would you do this properly using criteria?
Update
Here is the error I am getting
could not resolve property: CountryOrigin.Name of: Entities.Show
For Criteria, use the following:
_session.CreateCriteria<Show>()
.CreateAlias("CountryOrigin", "country")
.Add(Restrictions.Eq("country.Name", countryName))
.AddOrder(Order.Asc("EnglishName"))
.List<Show>();
Of course HQL is easier when you are not constructing a dynamic query (search):
_session.CreateQuery(
#"
from Show
where CountryOrigin.Name = :countryName
order by EnglishName
")
.SetParameter("countryName", countryName)
.List<Show>();
And Linq always rocks:
_session.Query<Show>()
.Where(s => s.CountryOrigin.Name = countryName)
.OrderBy(s => EnglishName)
.ToList();
(.Query is for NH 3.x; for 2.x use .Linq)
I am trying to figure out what I thought was just a simple one to many mapping using fluent Nhibernate. I hoping someone can point me to the right directory to achieve this one to many relations
I have an articles table and a categories table
Many Articles can only belong to one Category
Now my Categores table has 4 Categories and Articles has one article associated with cateory1
here is my setup.
using FluentNHibernate.Mapping;
using System.Collections;
using System.Collections.Generic;
namespace FluentMapping
{
public class Article
{
public virtual int Id { get; private set; }
public virtual string Title { get; set; }
public virtual Category Category{get;set;}
}
public class Category
{
public virtual int Id { get; private set; }
public virtual string Description { get; set; }
public virtual IList<Article> Articles { get; set; }
public Category()
{
Articles=new List<Article>();
}
public virtual void AddArticle(Article article)
{
article.Category = this;
Articles.Add(article);
}
public virtual void RemoveArticle(Article article)
{
Articles.Remove(article);
}
}
public class ArticleMap:ClassMap<Article>
{
public ArticleMap()
{
Table("Articles");
Id(x => x.Id).GeneratedBy.Identity();
Map(x => x.Title);
References(x => x.Category).Column("CategoryId").LazyLoad();
}
public class CategoryMap:ClassMap<Category>
{
public CategoryMap()
{
Table("Categories");
Id(x => x.Id).GeneratedBy.Identity();
Map(x => x.Description);
HasMany(x => x.Articles).KeyColumn("CategoryId").Fetch.Join();
}
}
}
}
if I run this test
[Fact]
public void Can_Get_Categories()
{
using (var session = SessionManager.Instance.Current)
{
using (var transaction = session.BeginTransaction())
{
var categories = session.CreateCriteria(typeof(Category))
//.CreateCriteria("Articles").Add(NHibernate.Criterion.Restrictions.EqProperty("Category", "Id"))
.AddOrder(Order.Asc("Description"))
.List<Category>();
}
}
}
I am getting 7 Categories due to Left outer join used by Nhibernate
any idea what I am doing wrong in here?
Thanks
[Solution]
After a couple of hours reading nhibernate docs I here is what I came up with
var criteria = session.CreateCriteria(typeof (Category));
criteria.AddOrder(Order.Asc("Description"));
criteria.SetResultTransformer(new DistinctRootEntityResultTransformer());
var cats1 = criteria.List<Category>();
Using Nhibernate linq provider
var linq = session.Linq<Category>();
linq.QueryOptions.RegisterCustomAction(c => c.SetResultTransformer(new DistinctRootEntityResultTransformer()));
var cats2 = linq.ToList();
I don't really know what's the problem, because I don't know how you save the categories, but it might be caused by using the wrong cascade setting in the mapping?
Using Join on a HasMany is unusual; it's typically used on References, the many side of a one-to-many relationship. Instead of the solution you came up with, you should lazy load the collection or use Fetch.Select. Both will cause NH to issue two selects, one to load the Category and another to load its associated Articles.
Addendum:
The error you're getting is pretty straight-forward: the collection can't be loaded because the ISession that was used to load the parent is out of scope (or its connection was closed). Setting the fetch mode to Select will resolve this (I think, I haven't tried it). So your collection mapping would be:
HasMany(x => x.Articles).KeyColumn("CategoryId").Fetch.Select();
If you can keep the ISession open I would recommend lazy loading:
HasMany(x => x.Articles).KeyColumn("CategoryId").LazyLoad();
It's unusual to use Join on a collection mapping due to the problem you ran into. Issuing a join from the one side will return a parent object for each object in the collection, just as it would in SQL.