NHibernate Criteria select items by the group by and sum of itemid within another table - nhibernate

public class SearchText
{
public virtual int Id { get; set; }
public virtual string Text { get; set; }
}
public class SearchTextLog
{
public virtual int Id { get; set; }
public virtual SearchText SearchText { get; set; }
public virtual User User { get; set; }
public virtual int SearchCount { get; set; }
public virtual DateTime LastSearchDate { get; set; }
}
I am trying to select the top 5 SearchText items based on the sum of their count within the SearchTextLog. Currently I have only been able to resolve this by first performing a query to get the top 5 items, and then using the result within a second query. I was wondering if someone could show me the light and teach me how I could integrate these two seperate queries into a single unit.
Here is what I have currently:
var topSearchCriteria = Session.CreateCriteria(typeof (SearchTextLog))
.SetProjection(Projections.ProjectionList()
.Add(Projections.GroupProperty("SearchText.Id"))
.Add(Projections.Alias(Projections.Sum("SearchCount"), "SearchCount")))
.AddOrder(Order.Desc("SearchCount"))
.SetMaxResults(topSearchLimit)
.List<int>();
return Session.CreateCriteria<SearchText>()
.Add(Restrictions.In("Id", topSearchCriteria.ToArray()))
.List<SearchText>();
Edit:
Oh no, I just realised my current solution will lose the important order by of the results. So I will definitely have to incorporate the queries. :-/
Edit:
I tried a bidirectional mapping too to allow the following statement, however, I can't get it to return SearchText items. It simply complains that the SearchText properties aren't in a grouping.
return Session.CreateCriteria<SearchText>()
.CreateAlias("SearchTextLogs", "stl")
.AddOrder(Order.Desc(Projections.Sum("stl.SearchCount")))
.SetMaxResults(topSearchLimit)
.SetResultTransformer(Transformers.AliasToEntityMap)
.List<SearchText>();
Excuse my ignorance, but Nhibernate is completely new to me, and requires a completely different way of thinking.

Ok, I think I have figured out a solution.
My original solution as per my question won't work because NHibernate doesn't yet support the ability to do a group by property without adding it to the select clause (see: link text).
While fooling around however, I came across these cool things called ResultTransformers. Using the AliasToBean result transformer Nhibernate will automatically map the alias's I give to each projection item to properties by the same name within a type I specify. I simply specified my SearchText object (however, I had to add an additional TotalSearchCount property for the sum projection item). It populated my objects perfectly and returned them.
return Session.CreateCriteria(typeof(SearchTextLog))
.CreateAlias("SearchText", "st")
.SetProjection(Projections.ProjectionList()
.Add(Projections.Alias(Projections.GroupProperty("st.Id"), "Id"))
.Add(Projections.Alias(Projections.GroupProperty("st.Text"), "Text"))
.Add(Projections.Alias(Projections.Sum("SearchCount"), "TotalSearchCount")))
.SetMaxResults(topSearchLimit)
.AddOrder(Order.Desc("TotalSearchCount"))
.SetResultTransformer(Transformers.AliasToBean(typeof(SearchText)))
.List<SearchText>();
I am surprised this wasn't easier to do. It's taken me about 4 to 5 hours of research and dev to figure this one out. Hopefully my NHibernate experience will get easier with more and more experience.
I hope this helps someone else out there!

doesn't this work?
var critterRes = Session.CreateCriteria(typeof (SearchTextLog))
.SetProjection(Projections.ProjectionList()
.Add(Projections.GroupProperty("SearchText"))
.Add(Projections.Property("SearchText"))
.Add(Projections.Alias(Projections.Sum("SearchCount"), "SearchCount")))
.AddOrder(Order.Desc("SearchCount"))
.SetMaxResults(topSearchLimit)
.List<SearchText>()

Related

remove from collection without load all collection data. confused which collection mapping to use

I have a many-to-many relationship between Assignment and User
When trying to delete an user from an assignment, I see all users are loaded in the collection.
How to I avoid that?
public class User
{
public virtual int Id { get; private set; }
public virtual IList<Assignment> Assignments { get; set; }
}
public class Assignment
{
public virtual int Id { get; private set; }
public virtual ICollection<User> Users { get; set; }
}
Mappings:
HasManyToMany(user => user.Assignments).Table("UserToAssignment").ParentKeyColumn("UserId").ChildKeyColumn("AssignmentId").Inverse().ExtraLazyLoad();
HasManyToMany(productAssignment => productAssignment.Users).AsSet().Table("UserToAssignment").ParentKeyColumn("AssignmentId").ChildKeyColumn("UserId").LazyLoad();
Calling code:
assignment.Users.Remove(user)
Initially I used Bag instead of Set for Assignment mapping, but when updating it, it was deleting and then reinserting alot of rows in the AssignmentsToUsers table. So I changed to using Set.
But now I see a problem with using Set: it brings all data in memory.
What is the recommended way of doing this?
You can't avoid this and I would ignore it if performance is acceptable. If performance is a problem, there are three ways I can think of to tackle it:
If the other side of the collection (User.Assignments) is lighter weight then remove the assignment from the user instead.
Model the many-to-many table and delete the object directly. You would have to be certain that the Users collection is not going to be loaded prior to this because the in-memory representation will still contain the deleted record.
Direct delete using SQL -- this has the same caveat as #2.
You should use extra lazy mode also for Assignment.Users.

Is there something analogous on NHibernate regarding Entity Framework's navigation property?

Is there something analogous on NHibernate regarding Entity Framework's navigation property? For example, instead of:
s.Save(new Product { Category = s.Get<Category>("FD"), Name = "Pizza" });
I wish I could write:
s.Save(new Product { CategoryId = "FD", Name = "Pizza" });
Can I inform NHibernate not to use the Product's Category property as a mechanism to save the Product's category? I want to use CategoryId instead(Read: I don't want to use DTO). Entity Framework seems able to facilitate avoiding DTO patterns altogether, while at the same time offering the full benefit of ORM(can avoid joins using navigation properties). I want the EF's offering the best of both worlds(lean mechanism for saving objects, i.e. no need to retrieve the property's object) and navigation mechanism for querying stuff
Sample from EF: http://blogs.msdn.com/b/adonet/archive/2011/03/15/ef-4-1-code-first-walkthrough.aspx
public class Category
{
public virtual string CategoryId { get; set; }
public virtual string Name { get; set; }
public virtual IList<Product> Products { get; set; }
}
public class Product
{
public virtual int ProductId { get; set; }
public virtual string Name { get; set; }
public virtual string CategoryId { get; set; }
public virtual Category Category { get; set; }
}
[UPDATE]
Regarding James answer, I tried seeing the NHibernate's actions in SQL Server Profiler.
// this act didn't hit the Category table from the database
var c = s.Load<Category>("FD");
// neither this hit the Category table from the database
var px = new Product { Category = c, Name = "Pizza" };
// this too, neither hit the Category table from the database
s.Save(px);
Only when you actually access the Category object that NHibernate will hit the database
Console.WriteLine("{0} {1}", c.CategoryId, c.Name);
If I understand your question, you want to save a Product with a Category without hitting the database to load the Category object. NHibernate absolutely supports this and you almost have the right code. Here is how you do it in NHibernate:
s.Save(new Product { Category = s.Load<Category>("FD"), Name = "Pizza" });
This will not hit the database to fetch the actual Category, but it will simply save a Product with the correct Category.Id. Note that you don't need (and I would recommend getting rid of Product.CategoryId).
Now why does this work with session.Load(), but not session.Get()... With session.Get(), NHibernate has to return the object or null. In .NET, there is no way for an object to replace itself with null after the fact. So NHibernate is forced to go to the database (or L1 cache) to verify that the "FD" Category actually exists. If it exists, it returns an object. If not, it must return null.
Let's look at session.Load(). If the object is not present in the database, it throws an exception. So NHibernate can return a proxy object from session.Load() and delay actually hitting the database. When you actually access the object, NHibernate will check the database and can throw an exception at that point if the object doesn't exist. In this case, we're saving a Product to the database. All NHibernate needs is the Category's PK, which it has in the proxy. So it doesn't have to query the database for the Category object. NHibernate never actually needs to hydrate an actual Category object to satisfy the save request.

Why do I need to set an alias to my projection if it's optional?

I'm reading the documentation about DetachedCriteria. The documentation clearly shows that setting an alias for your projection is optional. However, whenever I omit the alias my model properties contain no data. Here are my two test models.
[ActiveRecord("INCIDENT")]
public class Incident : ActiveRecordBase<Incident>
{
[PrimaryKey(PrimaryKeyType.Native, "INCIDENT_ID", ColumnType = "Int32")]
public virtual int IncidentId { get; set; }
[Property("CREATION_DATETIME", ColumnType = "DateTime")]
public virtual DateTime? CreationDatetime { get; set; }
[BelongsTo("CAUSE_CD")]
public virtual Cause Cause { get; set; }
}
[ActiveRecord("CAUSE")]
public class Cause : ActiveRecordBase<Cause>
{
[PrimaryKey(PrimaryKeyType.Native, "CAUSE_CD", ColumnType = "String")]
public virtual string CauseCd { get; set; }
[Property("CAUSE_DESC", ColumnType = "String", NotNull = true)]
public virtual string CauseDesc { get; set; }
}
Here is what I use to query the database.
DetachedCriteria incidentCriteria = DetachedCriteria.For<Incident>("i")
.SetProjection(Projections.ProjectionList()
.Add(Projections.Property("i.IncidentId"))
.Add(Projections.Property("i.CreationDatetime"))
)
.SetResultTransformer(Transformers.AliasToBean<Incident>());
IList<Incident> incidents = Incident.FindAll(incidentCriteria);
Both projections properties do not get populated unless I set an alias. So my question is, why is the alias optional? I'm sure I'm simply missing something else. But if I put a random alias (e.g. abc) it'll return an error saying that it could not find property "abc" in the Incident class. Fair enough, I add the appropriate alias to match my property names. And voila! My properties are now property being populated.
Now comes the issue of when I want to query a lookup table. I add
.Add(Projections.Property("c.CauseDesc"), "CauseDesc")
to my ProjectionList and append
.CreateCriteria("i.Cause", "c")
But now it complains that it can't find "CauseDesc" from my Incident model.
What am I missing from this whole criteria ordeal?
Update:
The following code
IList<Incident> results = sess.CreateCriteria<Incident>("i")
.SetProjection(Projections.ProjectionList()
.Add(Projections.Property("i.IncidentId"), "IncidentId")
.Add(Projections.Property("i.CreationDatetime"), "CreationDatetime")
.Add(Projections.Property("c.CauseDesc"), "CauseDesc")
)
.Add(Expression.Gt("i.IncidentId", 1234567))
.CreateAlias("Cause", "c")
.List<Incident>();
This does create a valid query (I checked it with a profiler) but it seems to be having issues populating my generic list. It gives me error "The value \"System.Object[]\" is not of type \"oms_dal.Models.Incident\" and cannot be used in this generic collection.\r\nParameter name: value". However, all works fine if I don't use a projection but then it selects 50+ fields which I don't want. Does that mean I'm forced to use a DTO in this circumstance?
You need to specify the projection property name like...
.Add(Projections.Property("i.IncidentId"), "IncidentId")
also, in general you do not project into the same domain object. You should create an incident dto like...
public class IncidentDTO
{
public int IncidentID { get; set; }
public DateTime CreationDatetime { get; set; }
}
and then...
.SetProjection(Projections.ProjectionList()
.Add(Projections.Property("i.IncidentId"), "IncidentId")
.Add(Projections.Property("i.CreationDatetime"), "CreationDatetime")
)
.SetResultTransformer(Transformers.AliasToBean<IncidentDTO>());
If you want Incidents matching some criteria (not a DTO), then don't set projections/resulttransformer. Instead you simply do something like this...
IList<Incident> incidents = session.CreateCriteria<Incident>()
.CreateAlias("Cause", "c") //now you can access Cause properties via `c.`
.Add(Restrictions.Eq("c.CauseDesc", "some cause"))
.List<Incident>();
See how the root criteria object doesn't need an alias. If it helps, I only use CreateCriteria for the initial object. If I need to reference child objects, I use CreateAlias.

NHibernate Projections to retrieve a Collection?

I´m having some trouble retrieving a collection of strings in a projection:
say that I have the following classes
public class WorkSet {
public Guid Id { get; set; }
public string Title { get; set; }
public ISet<string> PartTitles { get; protected set; }
}
public class Work {
public Guid Id { get; set; }
public WorkSet WorkSet { get; set; }
//a bunch of other properties
}
I then have a list of Work ids I want to retrieve WorkSet.Title, WorkSet.PartTitles and Id for.
My tought was to do something like this:
var works = Session.CreateCriteria<Work>()
.Add(Restrictions.In("Id", hitIds))
.CreateAlias("WorkSet", "WorkSet")
.SetProjection(
Projections.ProjectionList()
.Add(Projections.Id())
.Add(Projections.Property("WorkSet.Title"))
.Add(Projections.Property("WorkSet.PartTitles")))
.List();
The Id and Title loads up just fine, but the PartTitles returns null.
Suggestions please!
This might not work using criteria. Most probably, it is because the set can not be retrieved by the same sql query that is generated by the criteria.
I would actually really consider to retrieve the whole object. It is much easier and if it is not a very very special case, it is not worth the troubles. (By the way, it could be faster to retrieve whole objects, the may be already in the cache.) What usually counts is the number of queries (and its complexity of course), not the number of columns retrieved.
It probably works with HQL, there is a elements function there. Consider to use HQL for static queries anyway, it is more powerful.
select
ws.Title,
elements(ws.PartTitles),
w.id
from
Work w
inner join w.WorkSet ws
where w.id in (:ids)
elements is allowed in the select clause, but I don't know what you'll get. You most probably get as many records as there are PartTitles in the result, because there is only one SQL statement built.

NHibernate N+1 select : architectural solution?

Considering the following "database" :
Post -> User
The entities :
class User
{
public virtual string Name { get; set; }
public virtual IList<Post> Posts { get; set; }
}
class Post
{
public virtual string Title { get; set; }
public virtual User Author { get; set; }
}
And the following service Layer:
class UserService
{
IRepositoryUsers repositoryUsers;
IList<User> GetUsers()
{
return this.repositoryUsers.GetAllUsers();
}
}
When I want to print all users, with associated post count, I get (no surprise here) a N+1 select problem, as for each line, it will create a select to get the posts for the users.
Now here is my question : what is the "best" way to handle this, as there are some cases when I don't want to eager load each user's posts.
Should I create as many methods in my repository (and service) to match those scenarios ?
In general, there's a few ways to get rid of a Select N+1 problem. With NHibernate, one of my favorite ways to do this is to use the Criteria API, which is very sensible and well thought-out.
session.CreateCriteria(typeof(Fruit)) // Get me some fruit.
.SetFetchMode("CitrusType", FetchMode.Eager) // Just get this one field.
.Add(Expression.Eq("Basket", 47)) // Only fruit that are in this basket.
.List(); // Get 'em all.
To read more about this and other strategies, I recommend a look at this interesting blog post.
One thing you could do is to have a fetching strategy for every scenario in which you need to retrieve the data.
This blog post explains a possible solution for this type of problem, the author uses his own version of IRepository but you use the same ideas in your repository (or check out his NCommon project to get some inspiration)