I want to achieve following thing in Nhibernate, I have SQL query like this
select
rs.firstname, rs.lastname, COUNT(rs.id)
from
(select firstname, lastname, (select name from users where userid = '12345') as id
from person p) rs
group by rs.firstname, rs.lastname
How can i achieve this in Nhibernate? Any pointers will help.
#gdoron is partially correct, You have two options, a) use Session.CreateSqlQuery or b) use a named query which IMO is probably your best option.
In your XML mappings:-
<sql-query name="GetNameAndCount">
<![CDATA[
select
rs.Firstname, rs.Lastname, COUNT(rs.id) CountOf
from
(select firstname, lastname, (select name from users where userid = :id) as id
from person p) rs
group by rs.firstname, rs.lastname
]]>
</sql-query>
and to retreive the data
var results = Session
.GetNamedQuery("GetNameAndCount")
.SetInt32("id", id)
.SetResultTransformer(new AliasToBeanResultTransformer(typeof(NameCountDto)));
return results.List<NameCountDto>();
and your DTO would look like
class NameCountDto {
public virtual string Firstname { get; set;}
public virtual string Lastname { get; set;}
public virtual int CountOf { get; set;}
}
Be warned the column names in your query and your property names case must match.
You could also most likely solve this using HQL, Criteria or QueryOver (I think*) but we would need to see your class and mappings.
Related
Assume I have the following (the real case in more complex) CLR objects:
class Student {
public int Id {get;set;}
public int Age {get;set;}
public string Name {get;set;}
public ICollection<Grade> Grades {get;set;}
}
class Grade {
public int Id {get;set;}
public int Grade {get;set;}
public string Course {get;set;}
public virtual Student Student {get;set;}
public int StudentId {get;set;}
}
And the following stored procedure:
SELECT *
FROM Students AS S
INNER JOIN Grades AS G ON S.Id = G.StudentId
How do I execute the query from Entity Framework such that the resulting value will be a collection of Students with their Grades collections filled?
Obviously this could be achieved directly by using Entity Framework entities but the actual case is much more complex and the resulting EF query takes over 100 times more time than a stored procedure which achieves the same query. My only issue is how to receive the data when it returns
Just curious: is the stored proc imported in your EF-DbContext?
The result of this stored proc will be a complex result, in this case an object containing both the properties of the Student (Id, Age and Name) as well as the properties of the Grade (Id, Grade, Course, StudentId).
For each Grade, you will receive a separate record from the stored proc, which is mapped to the complex result i.e. object.
In order to get the list of Students with each having their own Grades-Collection, group the result object, e.g.
query = StoredProcResults.GroupBy(res => res.StudentId);
Help me translate this into proper NHibernate...
I have an Oracle database with 2 tables:
Employees:
Id (unique id)
FirstName (string)
LastName (string)
Location (string)
Locations:
Name (string)
Address (string)
As you can see the Employees table has a unique id, but the Locations table has no id whatsoever. The Name field is a regular field and is not unique.
In Oracle SQL I can run the following query:
SELECT *
FROM Employees e
LEFT OUTER JOIN Locations l
ON e.Location = l.Name
WHERE e.Id = 42
The location where the employee 42 is, has 2 rows in the Locations table, so this query returns 2 rows, one for each location found for employee 42.
Well, I can't figure out how to translate this into an NHibernate mapping + query (I use Fluent NHibernate). I tend to think that I should map Employees.Location as a Reference to Locations.Name, and so when running my HQL query it should return 2 objects, but NHibernate wouldn't let me retrieve a list from a Reference. So I tried HasMany but it doesn't work either, because NHibernate wants a field in Locations referring back to Employees, which kinda makes sense.
My HQL query looks like this:
select e
from Employees e
left join e.Locations l
where e.SGId like :sgId
Why can I do this in regular SQL and not in NHibernate?
Thanks.
I found the solution, you have to use HasMany(x => x.Locations) in conjunction with .PropertyRef("Location") so that hibernate knows that the field in Employee to be used for the join should be Location (and not the id of employee as is the default).
class Employee
{
public virtual int Id {get;set}
public virtual string FirstName {get;set;}
public virtual string LastName {get;set;}
public virtual string Location {get;set;}
public virtual IList<Location> Locations {get;set;}
}
class EmployeeMapping : ClassMap<Employee>
{
public EmployeeMapping()
{
Id(x=>x.Id);
Map(x=>x.FirstName);
Map(x=>x.LastName);
Map(x=>x.Location);
HasMany<Location>(x => x.Locations)
.PropertyRef("Location")
.KeyColumn("Name");
}
}
Also I found a few flaws in my approach due mainly to the weird legacy database I'm working with. That doesn't change the solution but I want to add that if one of the tables you're dealing with doesn't have unique ids, you're in trouble. Hibernate needs a unique id, so in my case I had to come up with a composite id made from multiple fields in order to make it unique. This is another debate but I wanted to mention it here to help whoever is researching this topic in the future.
For you mapping you need to use a collection/array/list/set object and a HasMany on your employees mapping for the location if it is going to return 2 locations for the one employee
class Employee
{
public virtual int Id {get;set}
public virtual string FirstName {get;set;}
public virtual string LastName {get;set;}
public virtual IList<Location> Location {get;set;}
}
class EmployeeMapping : ClassMap<Employee>
{
public EmployeeMapping()
{
Id(x=>x.Id);
Map(x=>x.FirstName);
Map(x=>x.LastName);
HasMany<Location>(x => x.Location)
.KeyColumn("Name")
.PropertyRef("Location")
.LazyLoad();
}
}
I'm new to Criteria API in NHibernate. Can someone generate this piece of SQL using Criteria API in NHibernate?
select count(*)
from result where Student_id
in(
SELECT s.Student_id
from Department as d
JOIN Student s ON d.Dept_id=s.Dept_id
where d.Dept_id=2
)
and how to proceed through the Criteria API in NHibernate. P.S I don't want to use HQL so without HQL is it possible to generate this kind of sql in nhibernate?
You can use linq-2-nhibernate as well.
Given the following class structure:
public class Result{
public virtual Student Student {get; set;}
}
public class Student{
public virtual Department Department {get; set;}
public virtual int Id { get; set;}
}
public virtual Department {
public virtual int Id {get; set;}
public virtual IList<Student> Students {get; set;}
}
Here is your query using the Criteria API:
var studentidquery = DetachedCriteria.For<Student>()
.Add(Restrictions.Eq("Department.Id"),2)
.SetProjection(Projections.Property("Id"));
var count = session.CreateCriteria<Result>()
.Add(Subqueries.PropertyIn("StudentId", studentidquery))
.UniqueResult<int>();
Using the QueryOver API it would look like this:
var studentidquery = QueryOver.Of<Student>()
.Where(x=>x.Department.Id==2)
.Select(x=>x.Id);
var count = session.QueryOver<Result>()
.WithSubquery.WhereProperty(x => x.Id).In(studentidquery)
.Select(Projections.Count<Result>(r=>r.Id))
.UniqueResult<int>();
Also I don't think you need the join to Department in your SQL query as you already have DepartmentId as a foreign key in the Student table. No sense in joining to extra tables for no good reason.
consider the following class:
class Order {
int OrderId {get; set;}
int CustomerId {get; set;}
string CustomerName {get; set;}
//other fields go here
}
which is mapped to Orders table. Is it possible to map the property CustomerName to the Customers table through the foreign key relation?
Yes, you can use the join mapping element for this. Another option is to map a view instead of a table. But if possible you should take the object-oriented approach and map the many-to-many relationship between Order and Customer.
I strongly suggest you don't use <join/> for this. Although it would accomplish what you requested, it creates other problems.
Instead, Order should have a relationship with Customer. You can then project the name if you want, although it's easier to just use order.Customer.Name.
So, it boils down to this:
1) Add Customer property to Order
public virtual Customer Customer { get; set; }
2) Map the property (in the example, CustomerId is the name of the FK column)
<many-to-one name="Customer" column="CustomerId"/>
3) If you specifically want to have a CustomerName property, project it from Customer:
public virtual string CustomerName { get { return Customer.Name; } }
I have the following issue in the project I am working on. Each transaction in the system is assigned to a given user. So there is a many to one relationship between transactions and users, like so:
public class User
{
public int ID { get; private set; }
public string FirstName { get; set; }
....
}
public class Transaction
{
public int ID { get; private set; }
public User CreatedBy { get; private set; }
...
}
I have mapped these entities with NHibernate so that there is a many-to-one mapping between the Transaction and the User classes. The User object doesn't have a list of transactions, but the Transaction has a reference to the User that created it.
Now I want to query to retrieve a list of the users who created the most transactions, but I can't figure out how to get the top 10 most referenced users using NHibernate.
Any ideas? I would like to be able to use ICriteria to complete this rather than HQL, but HQL would be ok if required.
Update
I tried sirrocco's suggestion with the query as...
DetachedCriteria topReferencedUsers = DetatchedCriteria.For(typeof(Transaction))
.SetProjection(Projections.GroupProperty("CreatedBy.Id"))
.SetProjection(Projections.Count("CreatedBy.Id").As("pcount" ))
.AddOrder(Order.Desc("pcount"))
.SetMaxResults(10);
and build that as the subquery...
GetSession().CreateCriteria(typeof (User))
.Add(Subqueries.PropertyIn("Id", topReferencedUsers))
.List<User>();
but this subquery does not group but returns the total number of transactions, which are then used as the IN clause to the User query. If I add the ProjectionList() with both projections, I get the output of the subquery that I want, but it fails because it tries to run the two column output into the IN clause of the User query. How do I get NHibernate to project both the ID and the Count, but only join on the ID?
Update (2)
I tried Sirrocco's SqlGroupProjection suggestion (thank you Sirrocco) but came up empty. First it gave me errors saying that it couldn't find the property pcount, which meant that I needed to remove the order by, which means it was ordering by some timestamp, which won't work. But even with that, it is still only outputing the count of the times that the user was referenced, not the user id with which to join the Users table. Any ideas? Thanks.
You can try it for yourself and see if you get the desired output.
var userIds = this.Session
.CreateQuery(#"
select a.User.Id
from Transaction as a
group by a.User
order by count(a.User) desc")
.SetMaxResults(10)
.List<int>().ToArray();
var users = this.Session.CreateCriteria(typeof(User))
.Add(Restrictions.InG("Id", userIds))
.List<Artist>();
return users;
The userId's that I get from the first queries are (90,22,50,55) but when passed to the second one I get my users in 22,50,55,90 order.
You could split the operation into two steps.
1) Execute topReferencedUsers, then extract the CreatedBy.Id projection into an int array in memory (since you're only dealing with 10).
2) Then Execute:
GetSession().CreateCriteria(typeof(User))
.Add(Expression.InG<int>("Id", topTenIdArray))
.List<User>();