I've just delved into a bit of NHibernate and I'm having trouble with one of the more 'complex' (to me!) queries I have to write. The scenario is:
I've got a 'Staff' object which has a collection of 'Skills' attached. I'd like to pass in a list of 'Skills' to query against (e.g. if I only want people that can either 'Cook' or 'Code', or both) and return a list of matching Staff, but I'm having a little trouble....
What I've got object-wise is:
public class StaffMember : Resource
{
public virtual string EmployeeId { get; set; }
public virtual bool IsTeamLeader { get; set; }
public virtual StaffMember TeamLeader { get; set; }
public virtual IList<Skill> Skills { get; set; }
}
public class Skill : BaseDomainObject
{
public virtual string Name { get; set; }
}
And I guess the SQL would go something like:
select distinct st.*
from staff st, resource re
inner join staffskills sks on re.id = sks.staffresourceid
inner join skill ski on ski.id = sks.skillid
where st.resourceid = re.id
and ski.id in (1,2,3,4)
I tried to use "Expression.InG("Skills", skillsSearchList)" in the criteria, but that can't be used when two collections are in play (e.g. if they only had one skill, it would be fine!)... any pointers?
You need to
.CreateAlias("Skills", "sks")
.Add(Restrictions.In("sks.id", skillIdList))
I'm not sure if this can be done with a list of skill objects. Either way, the sql is going to end up the same as above.
Note that this will create a Cartesian product so you might want to use an exists subquery or .SetResultTransformer(new DistinctRootEntityResultTransformer()) to get back a list of distinct Staff.
Related
i have one problem (obviously :) )
Is it possible to make dynamic queries in nHibernate in that way...
I have many tables (let we say: User, City, Country, Continet,...) is it possible to flaten this data so i do not need to know joins between this tables (get continent for user, without making join user.city, city.country, coutry.continent)?
The point is i want to some kind flatten data, so user can dynamically select data on user interface without knowing data model behind application?
It will be great that someone gave me at least idea how to make this, or if it's possible...
One example on web is GoogleAnalytics Custom reports (you can drag dimensions and metrics on UI and get results)
You said you're using Fluent NHibernate, which means that, assuming your domain model is structured correctly, you should not need to use any joins.
"Flattening" the data is a UI concern, not a database concern, so you shouldn't flatten your data model or simplify it for the UI unless you absolutely have to.
Let's assume you have the following entities:
public class User
{
public virtual string Name { get; set; }
public virtual City City { get; set; }
}
public class City
{
public virtual string Name { get; set; }
public virtual Country { get; set; }
}
public class Country
{
public virtual string Name { get; set; }
}
If you want to filter users by a certain country, the LINQ query for this (assuming NHibernate 3) would be:
var country = session.Single<Country>(x => x.Name == "Africa");
session.Query<User>().Where(x => x.City.Country == country);
How can I do this query with NHibernate
select top 10 count(distinct classedition.createdby_id) as editions, class.id,
class.name, class.createdon, class.createdby_id
from class
inner join classedition on class.id = classedition.class_id
group by class.id, class.name, class.createdon, class.createdby_id
order by editions desc, class.createdon desc
I'm using NHibernate 3. I tried to do it with the new Linq provider without success. I don't care about the way of doing it as long as it produce the exact sql query above. I would prefer writing a strongly typed query, without magic string if possible.
I'm new to NHibernate so this question may be simple.
Here is a little more info
I use Fluent NHibernate with AutoMappings. The C# classes are very simple:
public class Class
{
public virtual int Id { get; set; }
public virtual string Name { get; set; }
public virtual DateTime CreatedOn { get; set; }
}
public class ClassEdition
{
public virtual int Id { get; set; }
public virtual Class Class { get; set; }
public virtual User CreatedBy { get; set; }
}
public class User
{
public virtual int Id { get; set; }
public virtual string Name { get; set; }
}
I finally resolved the problem by using a View.
I don't want to be rude, but it seems like the NHibernate community is more inclined to argue on the way I ask a question then responding to the question itself. See the comments for Diego Mijelshon's answer. I received the same reproaches (about using unit tests) on nhusers (Google Groups): http://groups.google.com/group/nhusers/browse_thread/thread/4c74269aefb918fc
HQL queries are strongly typed and object oriented.
var results = session.CreateQuery(#"
select count(distinct e.CreatedBy), c.Id,
c.Name, c.CreatedOn, c.CreatedBy
from ClassEditions e
join e.Class c
group by c.Id, c.Name, c.CreatedOn, c.CreatedBy
order by 1 desc, c.CreatedOn desc
")
.SetMaxResults(10)
.List();
Your C# code is a string too. NH also has a query compiler.
In fact, if you put the query in a mapping file, you can even get intellisense and real-time error checking by installing the HQL Language Service for Visual Studio.
And a simple unit test that does nothing more than build the SessionFactory will tell you if anything broke because of a change. Not to mention modern refactoring tools (like Resharper) are able to rename identifiers in strings, bindings, or any kind of files without a problem.
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)
My entities look something like that (simplified):
public class Person
{
public Guid Id { get; set; }
public string Name { get; set; }
public IList<Department> Departments { get; set; }
}
public class Department
{
public Guid Id { get; set; }
public string Name { get; set; }
}
I'm querying the database through criteria api for all persons that have a department with a certain name that should match a like-pattern.
It happens that a person contains two or more departments whose names contain the same character sequence which is used by the query. Therefore the same person is returned multiple times. To surpress this, I know that I can use criteria.SetResultTransformer(Transformers.DistinctRootEntity); but this works only as long as the result is not paged.
When I'm paging the result I don't only need to get the first page but I also need to know how many entities there are in total. Unfortunately the result transformer does not work when calling criteria.SetProjection(Projections.RowCount()) as there is no result to be transformed.
Can I somehow avoid retrieving the whole list of person with the result transformer and then manually taking the right part out of the collection?
Best Regards
Oliver Hanappi
You need to include distinct in your sql request. Some information you can find here. Second answer mostly
I'm new to NHibernate and trying to create my first mapping.
I have created a class like this (my example is simplified):
public class Buyer
{
public int BuyerID { get; set; }
public string Name { get; set; }
public decimal AverageOrderAmount { get; private set; }
public DateTime LastOrderDate { get; private set; }
}
Normally, to get this data out of SQL Server, I would write a query using aggregate functions like this:
select b.BuyerID, b.Name,
avg(o.OrderTotal) as AverageOrderAmount, max(o.OrderDate) as LastOrderDate
from Buyers b
join Orders o on o.BuyerID = b.BuyerID
where BuyerID=#BuyerID
group by b.BuyerID, b.Name
My question is, how do I communicate this in my mapping? Is this possible?
I supposed I could store these calculated values in the Buyers cable and recalculate them as needed, but that doesn't feel right.
From what I know, you can't map that using nhibernate. An entity represents a table(most of the times) , what you have there is more along the lines of a report.
To have access to that class I would create a view in the database and then create a separate entity. In your normal use of Buyer you probably don't always need AverageOrderAmount and LastOrderDate - I think that you're using that to display this information on the interface , in which case you should create and map a DB view.