Make query with one join instead of two - nhibernate

I've very simple data schema of two tables with many-to-many relationship:
CREATE TABLE Users
(
UserId int,
UserName varchar
)
CREATE TABLE Roles
(
RoleId int,
RoleName varchar
)
CREATE TABLE UserRoles
(
UserId int,
RoleId int
)
The data model and fluent mapping are also straightforward:
class UserEntity
{
public virtual int Id {get; set;}
public virtual string Name {get; set;}
public virtual IList<RoleEntity> Roles {get; set;}
}
class RoleEntity
{
public virtual int Id {get; set;}
public virtual string Name {get; set;}
public virtual IList<UserEntity> Users {get; set;}
}
class UserEntityMap : ClassMap<UserEntity>
{
public UserEntityMap()
{
Table("Users");
Id(x => x.Id).Column("UserId");
Map(x => x.Name).Column("UserName");
HasManyToMany(x => x.Roles)
.Table("dbo.UserRoles").ParentKeyColumn("UserId").ChildKeyColumn("RoleId");
}
}
class RoleEntityMap : ClassMap<RoleEntity>
{
public RoleEntityMap()
{
Table("Roles");
Id(x => x.Id).Column("RoleId");
Map(x => x.Name).Column("RoleName");
HasManyToMany(x => x.Users)
.Table("dbo.UserRoles").ParentKeyColumn("RoleId").ChildKeyColumn("UserId");
}
}
I like to query for all UserId that belong to role with RoleId 15 using NH Query. I do the following:
IList<int> list = Session.QueryOver<UserEntity>()
.Inner.JoinQueryOver<RoleEntity>(u => u.Roles)
.Where(r => r.Id == 15)
.Select(u => u.Id)
.List<int>();
The NHibernate Profiler shows that resulted SQL query is:
SELECT this_.UserId as y0_
FROM dbo.Users this_
inner join dbo.UserRoles userroles3_
on this_.UserId = userroles3_.UserId
inner join dbo.Roles user1_
on userroles3_.RoleId = user1_.RoleId
WHERE user1_.RoleId = 15 /* #p0 */
Please, advise how I can change the mapping or the query to have resulted SQL to have a single join, like this:
SELECT this_.UserId as y0_
FROM dbo.Users this_
inner join dbo.UserRoles userroles3_
on this_.UserId = userroles3_.UserId
WHERE userroles3_.RoleId = 15 /* #p0 */

I've found this post on StackOverflow that contains possible solution. The solution is to add an entity for UserRoles table and simply query over it like
IList<int> list = Session.QueryOver<UserRolesEntity>()
.Where(ur => ur.RoleId == 15)
.Select(ur => ur.UserId)
.List<int>();

Related

NHibernate using wrong key in queries, ObjectNotFoundByUniqueKeyException thrown

Using NHibernate 5.2, I'm getting an ObjectNotFoundByUniqueKeyException due to erroneous queries being executed with an invalid key when I do the following:
var session = sessionFactory.OpenSession();
var employee = new Employee(){
UserId = "joe",
Certifications = new List<Certification>()
};
employee.Certifications.Add(new Certification() { Employee = employee});
var id = session.Save(employee);
session.Flush();
session.Clear();
var emp = session.Get<Employee>(id);
foreach(var e in emp.Certifications)
{
Console.WriteLine(e.Id);
}
Queries executed
NHibernate: INSERT INTO Employee (UserId) VALUES (#p0); select SCOPE_IDENTITY();#p0 = 'joe' [Type: String (4000:0:0)]
NHibernate: INSERT INTO Certification (UserId) VALUES (#p0); select SCOPE_IDENTITY();#p0 = 'joe' [Type: String (4000:0:0)]
NHibernate: SELECT userquery_0_.Id as id1_0_0_, userquery_0_.UserId as userid2_0_0_ FROM Employee userquery_0_ WHERE userquery_0_.Id=#p0;#p0 = 1 [Type: Int32 (0:0:0)]
NHibernate: SELECT certificat0_.UserId as userid2_1_1_, certificat0_.Id as id1_1_1_, certificat0_.Id as id1_1_0_, certificat0_.UserId as userid2_1_0_ FROM Certification certificat0_ WHERE certificat0_.UserId=#p0;#p0 = 1 [Type: Int32 (0:0:0)]
NHibernate: SELECT userquery_0_.Id as id1_0_0_, userquery_0_.UserId as userid2_0_0_ FROM Employee userquery_0_ WHERE userquery_0_.UserId=#p0;#p0 = '1' [Type: String (4000:0:0)]
I'm expecting #p0 = 'joe' in the final two queries, not 1 and '1'. Can anyone see a problem with the mappings below that would explain this behavior?
Classes / Mappings
public class Employee
{
public virtual int Id { get; set; }
public virtual string UserId { get; set; }
public virtual ICollection<Certification> Certifications { get; set; }
}
public class EmployeeMap : ClassMap<Employee>
{
public EmployeeMap()
{
Table("Employee");
Id(x => x.Id);
Map(x => x.UserId);
HasMany(x => x.Certifications)
.KeyColumn("UserId")
.Cascade.All();
}
}
public class Certification
{
public virtual int Id { get; set; }
public virtual Employee Employee { get; set; }
}
public class CertificationMap : ClassMap<Certification>
{
public CertificationMap()
{
Table("Certification");
Id(x => x.Id);
References(x => x.Employee)
.Column("UserId")
.PropertyRef(x => x.UserId);
}
}
Collection is not using primary key of Employee, it is another property instead - PropertyRef. And we must inform one-to-many mapping as well as we do for many-to-one
HasMany(x => x.Certifications)
.KeyColumn("UserId")
.Cascade.All()
// use property, not the ID, when searching for my items
.PropertyRef("UserId")
;
A fact, that this mapping was missing, was causing the ID to be used (the magic 1) when NHibernate was loading collection

NHibernate Linq Startwith generates outer join

I have two entities that are connected through one to many relationship.
like this example:
public class Category
{
public int Id {get; set; }
public string Name {get; set;}
}
public class Product
{
public int Id {get; set;}
public string Name {get; set;}
public Category ProductCategory {get; set;}
}
and the mapping using fluent nhibernate
public class CategoryMap : ClassMap<Category>
{
....
}
public Class ProductMap: ClassMap<Product>
{
...
References(x => x.ProductCategory).Column("CategoryId");
}
When I do search using linq to NHibernate with where clause and equal, it generates inner join between product and category
so this
var query = session.Query<Product>()
.Where (x => x.ProductCategory.Name == "abc");
this will generate inner join
but when I do search with startwith like this
var query = session.Query<Product>()
.Where (x => x.ProductCategory.Name.StartWith("abc"));
this will generate outer join between the two.
Why, and how can I force to generate inner join?
You can do it using QueryOver, which is another method to create query in NHibernate. In that case, you specify the join type as you want.
Category category = null;
var result = session.QueryOver<Product>()
.JoinAlias(x => x.ProductCategory, () => category, JoinType.InnerJoin)
.Where(Restrictions.Like(Projections.Property(() => category.Name), "abc%", MatchMode.Start))
.List();
On the other hand, query over is more verbose code, you have to specify a lot of things you avoid using linq.

Select entities by condition column and clarify it by many to many condition

Select entities by condition column and clarify it by many to many condition
I have followed structure of classes (Entities):
class Order {
...
public virtual long OrderId {get; set;}
public virtual boolean ForFavorites {get; set;}
public virtual User Owner {get; set;}
...
}
class User {
...
public virtual long UserId { get; set; }
public virtual List<User> Favorites {get; set;}
public virtual List<User> FavoritesOrderers {get; set;}
...
}
How to write QueryOver or Criteria to get all orders where:
Order is not marker as ForFavorites AND
Order is marked as ForFavorites and some User is in Order.Owner.Favorites?
And opposite way:
How can I write QueryOver or Criteria to select all orders where Order.Owner in my Favorites?
--update: add Fluent Mapping
My mapping:
class OrderMap:ClassMap<Order> {
public OrderMap(){
Id(x=>x.OrderId).GeneratedBy.Identity();
Map(x => x.ForFavorites);
References(x => x.Owner, "UserId");
...
}
}
class UserMap:ClassMap<User> {
public UserMap() {
Id(x => x.UserId).GeneratedBy.Assigned();
HasManyToMany(x => x.Favorites)
.AsBag()
.LazyLoad()
.Table("Favorites")
.ParentKeyColumn("UserId")
.ChildKeyColumn("FavoriteId")
.Cascade.All();
HasManyToMany(x => x.FavoritesOrderers)
.AsBag()
.LazyLoad()
.Table("FavoriteOrderers")
.ParentKeyColumn("UserId")
.ChildKeyColumn("FavoriteId")
.Cascade.All();
...
}
}
--update: add some SQL
SQL may be like this:
:userId is id of favorite User
SELECT *
FROM [Order] o
JOIN [User] u ON o.owner = u.userId
WHERE (
(o.ForFavorites = false) ||
(o.ForFavorites = true && :userId IN
(SELECT favoriteId FROM [Favorites] f WHERE (f.UserId = u.userId))
))
Are there some other variations with better perfomance?..
For opposite way:
SELECT *
FROM [Order] o
JOIN [User] u ON o.owner = u.userId
INNER JOIN [FavoriteOrderers] fo ON fo.favoriteId=u.userId
WHERE (fo.userId = :userId)
I'm making a few assumptions about your mappings, but hopefully this will help:
Probably something like this for the first query:
session.QueryOver<Order>()
.JoinQueryOver(o => o.Owner)
.Where(
Restrictions.Or(
Restrictions.Where<Order>(o => !o.ForFavorites),
Restrictions.And(
Restrictions.Where<Order>(o => o.ForFavorites),
Subqueries.In(
userId,
QueryOver.Of<Favorite>()
.Where(f => f.UserId = userId)
.Select(f => f.FavoriteId)
.DetachedCriteria))))
.List<Order>();
I omitted the join to User since it wasn't used (and you said you wanted all Orders meeting the criteria).
Your second query could look like this:
session.QueryOver<Order>()
.JoinQueryOver(o => o.Owner)
.JoinQueryOver<FavoriteOrderers>(u => u.FavoriteOrderers)
.Where(fo => fo.UserId == userId)
After good response I found my solution, because in response poster uses own assumption on mappings,
which aren't right in my case, but may be better.
session.QueryOver<Order>()
.JoinAlias(o => o.Owner, () => owner)
.Where(
Restrictions.Or(
Restrictions.Where<Order>(o => !o.ForFavorites),
Restrictions.And(
Restrictions.Where<Order>(o => o.ForFavorites),
Subqueries.In(
userId,
QueryOver.Of<User>()
.Where(f => f.UserId = owner.UserId)
.JoinQueryOver<User>(u => u.Favorite, () => fav)
.Select(f => f.FavoriteId)
.DetachedCriteria))))
.List<Order>();

NHibernate JoinQueryOver query in Orchard

I have an Orchard website. There are two connected entities: agencies (AgencyPartRecord ) and facilities (FacilityPartRecord), they are connected n-to-n with AgencyFacilitiesPartRecord. Here are the corresponding records:
public class AgencyPartRecord : ContentPartRecord
{
...
public virtual IList<AgencyFacilitiesPartRecord> AgencyFacilitiesPartRecords { get; set; }
...
}
public class AgencyFacilitiesPartRecord
{
public virtual int Id { get; set; }
public virtual AgencyPartRecord AgencyPartRecord { get; set; }
public virtual FacilityPartRecord FacilityPartRecord { get; set; }
}
public class FacilityPartRecord : ContentPartRecord
{
public virtual string Name { get; set; }
public virtual string Description { get; set; }
}
Now I need to filter out agencies by the set of facilities, so that only agencies having all the facilities in list should by selected.
In the end I want to get SQL like this:
SELECT *
FROM AgencyPartRecord
WHERE Id IN
(
SELECT a.AgencyPartRecord_Id
FROM AgencyFacilitiesPartRecord a
WHERE a.FacilityPartRecord_Id IN (... filter values here ...)
GROUP BY a.AgencyPartRecord
HAVING COUNT(a.Id) = <number of filter values>
)
I have written the following query (filter.Facilities is of type List<int>):
IQueryOver<AgencyPartRecord, AgencyPartRecord> agencies = ... // apply other filters
AgencyFacilitiesPartRecord facility = null;
var fsub = QueryOver.Of<AgencyFacilitiesPartRecord>(() => facility)
.WhereRestrictionOn(r => r.FacilityPartRecord.Id).IsIn(filter.Facilities)
.SelectList(list =>
list
.SelectGroup(a => a.AgencyPartRecord.Id)
.SelectCount(a => a.FacilityPartRecord.Id))
.Where(Restrictions.Eq(
Projections.Count(
Projections.Property(() => facility.FacilityPartRecord.Id)), filter.Facilities.Count))
.SelectList(list => list.Select(a => a.AgencyPartRecord.Id));
agencies = agencies
.WithSubquery.WhereProperty(a => a.Id).In(fsub);
The problem is this query does not produce GROUP BY clause in SQL:
SELECT ...
FROM AgencyPartRecord this_
WHERE ...
and this_.Id in
(
SELECT this_0_.AgencyPartRecord_id as y0_
FROM AgencyFacilitiesPartRecord this_0_
WHERE this_0_.FacilityPartRecord_id in (#p2, #p3)
HAVING count(this_0_.FacilityPartRecord_id) = #p4
)
What am I doing wrong?
Thanks!
I finally got it! :)
The right code is:
AgencyFacilitiesPartRecord facility = null;
var fsub = QueryOver.Of<AgencyFacilitiesPartRecord>(() => facility)
.WhereRestrictionOn(r => r.FacilityPartRecord.Id).IsIn(filter.Facilities)
.SelectList(list => list
.SelectGroup(r => r.AgencyPartRecord.Id)
)
.Where(Restrictions.Eq(
Projections.Count(Projections.Property(() => facility.FacilityPartRecord.Id)), filter.Facilities.Count));
agencies = agencies.WithSubquery.WhereProperty(a => a.Id).In(fsub);
Andrew, thanks again for your QueryOver series (http://blog.andrewawhitaker.com/queryover-series/)!

(Fluent) NHibernate: Map field from separate table into object

I'm currently trying to get (Fluent)NHibernate to map an object to our legacy database schema. There are three tables involved.
Table a contains most information of the actual object I need to retrieve
Table b is a table which connects table a with table c
Table c has one additional field I need for the object
An SQL query to retrieve the information looks like this:
SELECT z.ID, z.ZANR, e.TDTEXT
FROM PUB.table_a z
JOIN PUB.table_b t ON (t.TDKEY = 602)
JOIN PUB.table_c e ON (e.ID = t.ID AND e.TDNR = z.ZANR)
WHERE z.ZANR = 1;
The main problem is how to specify these two join conditions in the mapping.
Entity for table a looks like this:
public class EntityA
{
public virtual long Id { get; set; }
public virtual int Number { get; set; }
public virtual string Name { get; set; }
}
Name should map to the column table_c.TDTEXT.
The mapping I have so far is this:
public class EntityAMap : ClassMap<EntityA>
{
public EntityAMap()
{
Table("PUB.table_a");
Id(x => x.Id).Column("ID");
Map(x => x.Number).Column("ZANR");
}
}
I tried to map the first join with the same strategy as in How to join table in fluent nhibernate, however this will not work, because I do not have a direct reference from table_a to table_b, the only thing connecting them is the constant number 602 (see SQL-query above).
I didn't find a way to specify that constant in the mapping somehow.
you could map the name as readonly property easily
public EntityAMap()
{
Table("PUB.table_a");
Id(x => x.Id).Column("ID");
// if ZANR always has to be 1
Where("ZANR = 1");
Map(x => x.Number).Column("ZANR");
Map(x => x.Name).Formula("(SELECT c.TDTEXT FROM PUB.table_b b JOIN PUB.table_c c ON (c.ID = b.ID AND b.TDKEY = 602 AND c.TDNR = ZANR))");
}
Update: i could only imagine a hidden reference and the name property delegating to there
public EntityAMap()
{
HasOne(Reveal.Member<EntityA>("hiddenEntityC"));
}
public class EntityB
{
public virtual int Id { get; set; }
public virtual EntityA EntityA { get; set; }
}
public EntityCMap()
{
Where("Id = (SELECT b.Id FROM PUB.table_b b WHERE b.TDKEY = 602)");
References(x => x.EntityA).PropertyRef(x => x.Number);
}