I have something like this :
public class User
{
/* some properties */
public virtual int Id { get; set; }
public abstract string LastName { get; }
}
public class Student
{
public virtual int Id { get; set; }
public virtual string LastName{ get; set; }
}
public class StudentUser : User
{
public virtual Student Student { set; get; }
public override string LastName
{
get { return Student.LastName; }
}
}
public class foo
{
public virtual int Id { get; set; }
public virtual IList<User> Users { get; set; }
}
1. I would like to do the following using QueryOver :
var query = session.QueryOver<foo>();
User User = null;
//case 1: This is not working
query.JoinAlias(x=> x.Users , () => User )
.Where(() => ((StudentUser)User).Student.LastName == "smith" );
//case 2: Also This is not working
query.JoinAlias(x=> x.Users , () => User )
.Where(() => ((StudentUser)User).LastName == "smith" );
//case 3: This is working
query.JoinAlias(x=> x.Users , () => User )
.Where(() => ((StudentUser)User).Id == 123 );
When I use query.JoinAlias(x=> x.Users , () => User ) more than one time, I get this exception An item with the same key has already been added.
Also I get this exception : could not resolve property: Student.LastName of: Entities.User in case 1 and case 2
But it is working with the Id property (case 3)
It might be look like :
SELECT *
FROM Foo INNER JOIN
Users ON Foo.Id = Users.FooId_FK INNER JOIN
Students ON Users.StudentId_FK = Students.Id
where Students.LastName = 'smith'
How can I get the LastName property value?
I think this should work:
session.QueryOver<foo>()
.JoinQueryOver(f => f.Users)
.JoinQueryOver(u => ((StudentUser)u).Student)
.Where(st => st.LastName == "smith")
Or keeping with JoinAlias:
User userAlias = null;
Student studentAlias = null;
session.QueryOver<foo>()
.JoinAlias(f => f.Users, () => userAlias)
.JoinAlias(() => ((StudentUser)userAlias).Student, () => studentAlias)
.Where(() => studentAlias.LastName == "smith")
Related
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/)!
I have the following parent entity Department which contains a collection of child entities Sections
public class Department
{
private Iesi.Collections.Generic.ISet<Section> _sections;
public Department()
{
_sections = new HashedSet<Section>();
}
public virtual Guid Id { get; protected set; }
public virtual string Name { get; set; }
public virtual ICollection<Section> Sections
{
get { return _sections; }
}
public virtual int Version { get; set; }
}
public partial class Section
{
public Section()
{
_employees = new HashedSet<Employee>();
}
public virtual Guid Id { get; protected set; }
public virtual string Name { get; set; }
public virtual Department Department { get; protected set; }
public virtual int Version { get; set; }
}
I would like to transform (flatten) it to the following DTO
public class SectionViewModel
{
public string DepartmentName { get; set; }
public string SectionName { get; set; }
}
Using the following code.
SectionModel sectionModel = null;
Section sections = null;
var result = _session.QueryOver<Department>().Where(d => d.Company.Id == companyId)
.Left.JoinQueryOver(x => x.Sections, () => sections)
.Select(
Projections.ProjectionList()
.Add(Projections.Property<Department>(d => sections.Department.Name).WithAlias(() => sectionModel.DepartmentName))
.Add(Projections.Property<Department>(s => sections.Name).WithAlias(() => sectionModel.SectionName))
)
.TransformUsing(Transformers.AliasToBean<SectionModel>())
.List<SectionModel>();
I am however getting the following exception: could not resolve property: Department.Name of: Domain.Section
I have even tried the following LINQ expression
var result = (from d in _session.Query<Department>()
join s in _session.Query<Section>()
on d.Id equals s.Department.Id into ds
from sm in ds.DefaultIfEmpty()
select new SectionModel
{
DepartmentName = d.Name,
SectionName = sm.Name ?? null
}).ToList();
Mappings
public class DepartmentMap : ClassMapping<Department>
{
public DepartmentMap()
{
Id(x => x.Id, m => m.Generator(Generators.GuidComb));
Property(x => x.Name,
m =>
{
m.Length(100);
m.NotNullable(true);
});
Set(x => x.Sections,
m =>
{
m.Access(Accessor.Field);
m.Inverse(true);
m.BatchSize(20);
m.Key(k => { k.Column("DeptId"); k.NotNullable(true); });
m.Table("Section");
m.Cascade( Cascade.All | Cascade.DeleteOrphans);
},
ce => ce.OneToMany());
}
}
public class SectionMap : ClassMapping<Section>
{
public SectionMap()
{
Id(x => x.Id, m => m.Generator(Generators.GuidComb));
Property(x => x.Name,
m =>
{
m.Length(100);
m.NotNullable(true);
});
ManyToOne(x => x.Department,
m =>
{
m.Column("DeptId");
m.NotNullable(true);
});
}
}
But this throws a method or operation is not implemented.
Seeking guidance on what I am doing wrong or missing.
NHibernate doesn't know how to access a child property's child through the parent entity. A useful thing to remember about QueryOver is that it gets translated directly into SQL. You couldn't write the following SQL:
select [Section].[Department].[Name]
right? Therefore you can't do the same thing in QueryOver. I would create an alias for the Department entity you start on and use that in your projection list:
Department department;
Section sections;
var result = _session.QueryOver<Department>(() => department)
.Where(d => d.Company.Id == companyId)
.Left.JoinQueryOver(x => x.Sections, () => sections)
.Select(
Projections.ProjectionList()
.Add(Projections.Property(() => department.Name).WithAlias(() => sectionModel.DepartmentName))
.Add(Projections.Property(() => sections.Name).WithAlias(() => sectionModel.SectionName))
)
.TransformUsing(Transformers.AliasToBean<SectionModel>())
.List<SectionModel>();
I noticed in your comment you'd like an order by clause. Let me know if you need help with that and I can probably come up with it.
Hope that helps!
This may be now fixed in 3.3.3. Look for
New Feature
[NH-2986] - Add ability to include collections into projections
Not sure but if this is your problem specifically but if you are not using 3.3.3 then upgrade and check it out.
Aslo check out the JIRA
Have you tried a linq query like
from d in Departments
from s in d.Sections
select new SectionModel
{
DepartmentName = d.Name,
SectionName = s == null ? String.Empty : s.Name
}
Can't believe I've found no answer to this but how can you do a query like
SELECT LTRIM(RTRIM("ColumnName")) FROM ....
in NHibernate
thanks
Having an example of Bank as POCO:
public class Bank
{
public virtual int ID { get; set; }
public virtual string City { get; set; }
public virtual string Street { get; set; }
}
There is a syntax for the LTRIM(RTRIM...
Bank bank = null;
var session = ...;
var query = session.QueryOver<BankAddress>()
.SelectList(l => l
// properties ID, City
.Select(c => c.ID).WithAlias(() => bank.ID)
.Select(c => c.City).WithAlias(() => bank.City)
// projection Street
.Select(Projections.SqlProjection(
" LTRIM(RTRIM({alias}.Street)) as Street" // applying LTRIM(RTRIM
, new string[] { "Street" }
, new IType[] { NHibernate.NHibernateUtil.String }
))
.TransformUsing(Transformers.AliasToBean<Bank>())
;
var list = query.List<Bank>();
Say I have a User class like this:
public class User
{
public string Id {get; set;}
public string Name {get; set;}
}
Each User can be either a Mentor, a Mentee or both. This is represented by a Relationship class:
public class Relationship
{
public string MentorId {get; set;} // This is a User.Id
public string MenteeId {get; set;} // This is another User.Id
}
Now I would like to generate a report that lists all of my Users and contains a field called Mentor Count and another field called Mentee Count. To achieve this I have created a UserReportDTO class to hold my report data.
public class UserReportDTO
{
public string Name {get; set;}
public string MentorCount {get; set;}
public string MenteeCount {get; set;}
}
I then query my RavenDB to get a list of all the Users and transform this into a list of UserReportDTO instances.
UserService
public List<UserReportDTO> GetReportChunk(
IDocumentSession db,
int skip = 0,
int take = 1024)
{
return db.Query<User>()
.OrderBy(u => u.Id)
.Skip(skip)
.Take(take)
.ToList()
.Select(user =>
new UserReportDTO
{
Name = user.Name,
MentorCount = // What goes here?
MenteeCount = // What goes here?
})
.ToList();
}
As you can see, I am struggling to work out the best way to retrieve the MentorCount and MenteeCount values. I have written some Map/Reduce Indexes that I think should be doing the job but I am unsure how to use them to achieve the result I want.
Question
What is the best way to include multiple aggregate fields into a single query?
EDIT 1
#Matt Johnson: I have implemented your index (see end) and now have a working Report Query which, in case anybody is interested, looks like this:
Working User Report Query
public List<UserDTO> GetReportChunk(IDocumentSession db, Claim claim, int skip = 0, int take = 1024)
{
var results = new List<UserDTO>();
db.Query<RavenIndexes.Users_WithRelationships.Result, RavenIndexes.Users_WithRelationships>()
.Include(o => o.UserId)
.Where(x => x.Claims.Any(c => c == claim.ToString()))
.OrderBy(x => x.UserId)
.Skip(skip)
.Take(take)
.ToList()
.ForEach(p =>
{
var user = db.Load<User>(p.UserId);
results.Add(new UserDTO
{
UserName = user.UserName,
Email = user.Email,
// Lots of other User properties
MentorCount = p.MentorCount.ToString(),
MenteeCount = p.MenteeCount.ToString()
});
});
return results;
}
MultiMap Index
public class Users_WithRelationships :
AbstractMultiMapIndexCreationTask<Users_WithRelationships.Result>
{
public class Result
{
public string UserId { get; set; }
public string[] Claims { get; set; }
public int MentorCount { get; set; }
public int MenteeCount { get; set; }
}
public Users_WithRelationships()
{
AddMap<User>(users => users.Select(user => new
{
UserId = user.Id,
user.Claims,
MentorCount = 0,
MenteeCount = 0
}));
AddMap<Relationship>(relationships => relationships.Select(relationship => new
{
UserId = relationship.MentorId,
Claims = (string[]) null,
MentorCount = 0,
MenteeCount = 1
}));
AddMap<Relationship>(relationships => relationships.Select(relationship => new
{
UserId = relationship.MenteeId,
Claims = (string[]) null,
MentorCount = 1,
MenteeCount = 0
}));
Reduce = results => results.GroupBy(result => result.UserId).Select(g => new
{
UserId = g.Key,
Claims = g.Select(x => x.Claims).FirstOrDefault(x => x != null),
MentorCount = g.Sum(x => x.MentorCount),
MenteeCount = g.Sum(x => x.MenteeCount)
});
}
}
You might be better served with a model that already has your relationship data kept with the user. This might look something like:
public class User
{
public string Id { get; set; }
public string Name { get; set; }
public string[] MentorUserIds { get; set; }
public string[] MenteeUserIds { get; set; }
}
However, if you want to stick with the model you described, the solution is to get rid of the multiple separate indexes and create a single multi-map index that has the data you need.
public class Users_WithRelationships
: AbstractMultiMapIndexCreationTask<Users_WithRelationships.Result>
{
public class Result
{
public string UserId { get; set; }
public string Name { get; set; }
public int MentorCount { get; set; }
public int MenteeCount { get; set; }
}
public Users_WithRelationships()
{
AddMap<User>(users => from user in users
select new
{
UserId = user.Id,
Name = user.Name,
MentorCount = 0,
MenteeCount = 0
});
AddMap<Relationship>(relationships => from relationship in relationships
select new
{
UserId = relationship.MentorId,
Name = (string)null,
MentorCount = 1,
MenteeCount = 0
});
AddMap<Relationship>(relationships => from relationship in relationships
select new
{
UserId = relationship.MenteeId,
Name = (string)null,
MentorCount = 0,
MenteeCount = 1
});
Reduce = results =>
from result in results
group result by result.UserId
into g
select new
{
UserId = g.Key,
Name = g.Select(x => x.Name).FirstOrDefault(x => x != null),
MentorCount = g.Sum(x => x.MentorCount),
MenteeCount = g.Sum(x => x.MenteeCount)
};
}
}
Then you can update your GetReportChunk method to query against the one index if you still want to project a custom DTO.
return db.Query<Users_WithRelationships.Result, Users_WithRelationships>()
.OrderBy(x => x.UserId)
.Skip(skip)
.Take(take)
.Select(x =>
new UserReportDTO
{
Name = x.Name,
MentorCount = x.MentorCount,
MenteeCount = x.MenteeCount,
})
.ToList();
I got an exception "null id generated for AccountDetail" when mapping one-to-one relationship by using many-to-one with unique constraint.
Here's my SQL tables:
Account(Id, Name)
AccountDetail(AccountId, Remark)
AccountId is both primary and foreign key.
Here's my Domain Model (Account and AccountDetail):
public class Account
{
public virtual int Id { get; set; }
public virtual string Name { get; set; }
public virtual AccountDetail Detail { get; set; }
public Account()
{
Detail = new AccountDetail
{
Account = this
};
}
}
public class AccountDetail
{
public virtual int AccountId { get; set; }
public virtual Account Account { get; set; }
public virtual string Remark { get; set; }
}
Mapping (NHibenrate 3.3 mapping by code):
class AccountMap : ClassMapping<Account>
{
public AccountMap()
{
Table(typeof(Account).Name);
Id(c => c.Id, m => m.Generator(Generators.Native));
Property(c => c.Name);
OneToOne(c => c.Detail, m =>
{
m.Constrained(true);
m.Cascade(Cascade.All);
m.PropertyReference(typeof(AccountDetail).GetPropertyOrFieldMatchingName("Account"));
});
}
}
class AccountDetailMap : ClassMapping<AccountDetail>
{
public AccountDetailMap()
{
Table(typeof(AccountDetail).Name);
Id(c => c.AccountId, m =>
{
m.Column("AccountId");
m.Generator(Generators.Foreign<AccountDetail>(x => x.Account));
});
Property(c => c.Remark);
ManyToOne(c => c.Account, m =>
{
m.Column("AccountId");
m.Unique(true);
});
}
}
BTW: Can I remove the AccountId property in AccountDetail? That is, only use the Account property. Using both AccountId and Account properties in AccountDetail class looks not so object-oriented.
Thanks!
I can't say what's actually wrong, but comparing with my working one-to-one relation, I would map it like this:
class AccountMap : ClassMapping<Account>
{
public AccountMap()
{
Table(typeof(Account).Name);
// creates a auto-counter column "id"
Id(c => c.Id, m => m.Generator(Generators.Native));
// doesn't require a column, one-to-one always means to couple primary keys.
OneToOne(c => c.Detail, m =>
{
// don't know if this has any effect
m.Constrained(true);
// cascade should be fine
m.Cascade(Cascade.All);
});
}
}
class AccountDetailMap : ClassMapping<AccountDetail>
{
public AccountDetailMap()
{
Id(c => c.AccountId, m =>
{
// creates an id column called "AccountId" with the value from
// the Account property.
m.Column("AccountId");
m.Generator(Generators.Foreign(x => x.Account));
});
// should be one-to-one because you don't use another foreign-key.
OneToOne(c => c.Account);
}
}