In my system Users own 0 or more Categories. Here is a simplified version of my model classes:
public class User
{
public virtual String Name {get; set;}
public virtual IList<Category> Categories { get; set; }
}
public class Category
{
public virtual String Title {get; set;}
}
I now want to create an ICriteria query to select all categories that aren't assigned to a user, but I'm stuck. Ideally I don't want to create a navigation property from Category to User, but with my beginner knowledge of NHibernate that's the only solution I can see.
Is there an ICriteria query that will do this with the current data model classes?
Thanks for your help.
This is off the top of my head, but might be a useful pointer.
var crit = _session.CreateCriteria<Category>("c")
.Add(
Subqueries.PropertyNotIn("c.id",
DetachedCriteria.For<User>("u")
.CreateCriteria("Categories","uc")
.SetProjection(Projections.Property("uc.id"))
));
var unassignedCategories = crit.List<Category>();
You can probably get a feel for the SQL that will be generated here:
select c.* from categories where c.id not in (select uc.id from usercategories)
Hope this helps, and sorry I haven't been able to test it :)
Tobin
Related
Maybe it's simple but I'm stuck with it and I didn't find any answer on how it could be done. I have a parent entity User with a collection of child entities Operations. These two entities are just for UI so they are a kinf of views. Here is the pseudo code
public class User
{
public int Id {get; set;}
public IEnumerable<Operation> Operations {get; set;}
public int TotalSuccessfulAccesses {get; set;} // not mapped to the database
public int TotalFailedAccesses {get; set;} // not mapped to the database
}
public class Operation
{
public int Id {get; set; }
public int UserId {get; set; } // FK
public int NbSuccessfulAccesses {get; set; }
public int NbFailedAccesses {get; set; }
}
What I would like to do it's to get the User with TotalSuccesfulAccesses and TotalFailedAccesses initialized from the child collection in one round trip to the database.
For each user we should calculate Sum(Operation.NbSuccessfulAccesses) and Sum(Operation.NbFailedAccesse) and make a projection respectively to the User.TotalSuccesfulAccesses and User.TotalFailedAccesses.
I tried to play with multicriteria and several queries but I'm not satisfied with it. I would like to know if maybe there is a simple way to do it with projection or something other. Or maybe I missed something.
What would you recommend ?
Thanks in advance for you help.
I was able to get rid of the magic alias strings in the following way:
UserViewModel userView = null;
Add(Projections.Sum<User>(x => operations.NbSuccessfulAccesses).WithAlias(() => userView.TotalSuccessfulAccesses))
You probably need to separate your view models and your domain entities. I assume in your domain you have got a User class having a list of Operation and these entities are mapped accordingly.
You could then create a view model:
public class UserViewModel
{
public int UserId { get; set; }
public int TotalSuccessfulAccesses { get; set; }
public int TotalFailedAccesses {get; set;}
}
Using ICriteria you can create the following query:
var criteria = Session.CreateCriteria(typeof(User));
criteria.CreateAlias("Operations", "operations", JoinType.LeftOuterJoin);
var projList = Projections.ProjectionList();
projList.Add(Projections.GroupProperty("Id"));
projList.Add(Projections.Sum("operations.NbSuccessfulAccesses"), "TotalSuccessfulAccesses");
projList.Add(Projections.Sum("operations.NbFailedAccesses"), "TotalFailedAccesses");
criteria.SetProjection(projList);
criteria.SetResultTransformer(Transformers.AliasToBean<UserViewModel>());
var ret = criteria.List<UserViewModel>();
Create the view model according to your needs and add any properties in the projection list accordingly.
Hope that helps.
Thanks to Kay, I came up with the following translation :
Operation operations = null;
var q = GetSession().QueryOver<User>().Where(u => u.AccessKeyId == accessKeyId)
.Left.JoinQueryOver(x => x.Operations, () => operations)
.Select(Projections.ProjectionList()
.Add(Projections.Sum<User>(x => operations.NbSuccessfulAccesses), "TotalSuccessfulAccesses"))
.Add(Projections.Sum<User>(x => operations.NbFailedAccesses), "TotalFailedAccesses"))
.TransformUsing(Transformers.AliasToBean<UserViewModel>()).List< UserViewModel >();
However I would like to know if there is a mean to get rid of the magic string "TotalSuccessfulAccesses" and "TotalFailedAccesses".
if I use something like that
UserViewModel userView = null;
Add(Projections.Sum<User>(x => operations.NbSuccessfulAccesses), () => userView.TotalSuccessfulAccesses)
NHibernate yields an error :
Could not find a setter for property 'userView.TotalSuccessfulAccesses' in class 'Domain.Query.UserViewModel'
which is not true because there is a setter for TotalSuccessfulAccesses' property.
Any ideas ?
Thanks
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'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.
I'm trying to do the following thing:
ICriteria criteriaSelect =
session
.CreateCriteria(typeof(Employees))
.CreateCriteria("Orders")
;
var test = criteriaSelect.List<Orders>();
With:
public class Orders{
public virtual int OrderID { get; private set;}
}
public class Employees{
public virtual int EmployeeID { get; private set;}
public virtual IList<Orders> Orders { get; private set; }
}
And I get the error: "No persister for: Employees".
Please note that for decoupling reason, I don't want Orders to
reference Employees.
Thanks for your help,
Stephane
The Criteria API is for indicating the specification you want during the query. You will need to establish mappings for your entities using either the older hbm.xml files or using Fluent NHibernate. See chapter 5 on Basic O/R Mapping for more details.