How do you map aggregate functions in NHibernate? - nhibernate

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.

Related

Get aggregate data from RavenDB after query and/or load of documents

I've the following document model:
public class Product
{
public int Id {get;set;}
public string Name {get;set;}
// ...
}
public class Customer
{
public int Id {get;set;}
public string Name {get;set;}
// ...
}
public class ProductOrder
{
public int Id {get;set;}
public int ProductId {get;set;}
public int CustomerId {get;set;}
public int Quantity {get;set;}
// ...
}
When quering for a Product i also want to have the number of ordered products (Quantity) and unique customers. Same for the Customer, i want to get the number of ordered products.
How to achieve this with RavenDB? With an index i've only the possibility to Load<> another entity, not all entities that reference the entity i query for.
Since the number of orders for a product can be a very high number i havn't added them to the Productdocument and so i don't have access to the orders during indexing with Include<> or LoadDocument<>.
Is my Model wrong for a DocumentDB or is there another way to get those information? If so, how to do this better?
Thanks.
Update
Here is my index for the number of orders:
public class ProductOrder_CountByProduct : AbstractIndexCreationTask<ProductOrder, ProductOrder_CountByProduct.Result>
{
public class Result
{
public int ProductId { get; set; }
public int NumberOfOrders { get; set; }
}
public ProductOrder_CountByProduct()
{
Map = orders => from order in orders
select new
{
ProductId = order.ProductId,
NumberOfOrders = order.Quantity
};
Reduce = results => from result in results
group result by result.ProductId
into g
select new
{
ProductId = g.Key,
NumberOfOrders = g.Sum(p => p.NumberOfOrders)
};
}
}
Now i can get the number of orders by product id:
var result = documentSession.Query<ProductOrder_CountByProduct.Result, ProductOrder_CountByProduct>()
.FirstOrDefault(p => p.ProductId == product.Id)
?? new ProductOrder_CountByProduct.Result();
var count = result.NumberOfOrders;
Same for the CountByCustomer query/index.
So far so good for a single result of Product.
But my goal was to query for products (by name or description) and fill an array of:
public class ProductViewModel
{
public int Id { get; set; }
public string Name { get; set; }
public string Description { get; set; }
public int NumberOfOrders { get; set; }
public int NumberOfCustomers { get; set; }
//... maybe NumberOfCustomersFromUSA, etc.
}
With the CountByProduct and CountByCustomer indexes i need then to iterate over every result of a product query and execute 2 CountByXYqueries for each result.
Is this the way to go?
You really don't want to be making a call for each item. That's a "select N+1" issue. Raven will actually prevent you from doing this, by limiting the number of requests you can make in a single session to 30.
There are a few different techniques that you can use instead.
Option 1
If you want to be able to query on name and description fields:
Include the name and description in your index map.
Include them in the reduce key, and pass them through unmodified.
Then you can include them in the Where predicate of a query.
Option 2
If you just want name and description fields in your output, but you don't want to query by them:
Add a TransformResults step to your index.
In the transform, load the product by its id that you grouped by.
Return the name and description fields from the loaded product, and the original aggregated counts.
Option 3
Same scenario as option 2, but you can do it without a transform
When querying, specify an Include in the query customization.
When you iterate through the results, you can load the product details without actually hitting the server, since they were included. Read more on includes here.
Option 4
You might want to consider using the Indexed Properties Bundle to write the results of these queries back to properties on the product documents. That way, you can just query for products and your data is all in one place - even if they were aggregated in multiple indexes.
--
You may want to combine multiple of the above techniques, for example - you may want to have the product name in the query so you can query by it, but you might want the product description for display only so you transform the query results to return it.
One thing you said didn't sound quite right though. Why would you return a count of your customers in your product view model? Unless its "number of customers that have ordered this product" - then it probably doesn't belong there.

Fluent nHibernate some kind of flat table

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

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.

Cannot use collections with InExpression

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.

How to retrieve unique entities through NHibernate Criteria API?

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