I have this 2 objects:
public class Parent
{
public virtual int Poid { get; set; }
public virtual IEnumerable<Child> Child { get; set; }
}
public class Child
{
public virtual int Poid { get; set; }
public virtual string Name {get; set;}
}
I want to use NHibernet QueryOver API to get a child based on the Parent Id and Child Id, That's mean something like give me the child with Id = x belonging to the parent with Id = y.
I tried something like this:
return Session.QueryOver<Parent>().Where(p => p.Poid == y)
.JoinQueryOver(p => p.WishesLists)
.Where(c => c.Poid == x)
.SingleOrDefault<Child>();
But I'm getting an exception that is not possible to convert an object of type Child to Parent.
How is the correct form to QueryOver starting with a Parent Entity but return a Child Entity?
I don't know if this is possible with QueryOver, I worked at it for a while without getting anywhere. It is possible with LINQ:
var child = session.Query<Parent>()
.Where(p => p.Poid == y)
.SelectMany(p => p.WishesLists)
.SingleOrDefault(c => c.Poid == x);
I strongly prefer the LINQ syntax over QueryOver.
See also NH-3176
Related
I have a parent class and a child class. One Child is always related to just one parent, but a parent can have multiple children:
public class Parent
{
public virtual string Id { get; set; }
public virtual string Name { get; set; }
public virtual IList<Child> Children { get; set; } = new List<Child>();
}
public class Child
{
public virtual int Id { get; set; }
public virtual string ParentId { get; set; }
public virtual string Name { get; set; }
}
I'm using the latest version of NHibernate 5.1.3 and mapping by code:
internal class ParentMapping : ClassMapping<Parent>
{
public ParentMapping()
{
Table("Parent");
Id(x => x.Id);
Property(x => x.Name);
Bag(
x => x.Children,
map =>
{
map.Key(km => km.Column("ParentId"));
map.Lazy(CollectionLazy.NoLazy);
map.Cascade(Cascade.Persist);
map.Inverse(true);
},
x => x.OneToMany());
}
}
internal class ChildMapping : ClassMapping<Child>
{
public ChildMapping()
{
Table("Child");
Id(x => x.Id, x => x.Generator(Generators.Identity));
Property(x => x.ParentId);
Property(x => x.Name);
}
}
Queries work but are quite inefficient. Instead of creating a single JOIN statement to query the children together with their parent, an explicit SELECT is made to retrieve the Child objects.
Even worse, inserts result in the following error:
NHibernate.StaleStateException: 'Batch update returned unexpected row count from update; actual row count: 0; expected: 3'
Here's a sample of a query:
using (var session = _sessionProvider.GetSession())
return session.Query<T>().ToList();
And that's the code to save a new item:
using (var session = _sessionProvider.GetSession())
{
session.Transaction.Begin();
session.Save(newEntity);
session.Transaction.Commit();
}
So everything's pretty easy.
I assume, the Bag() configuration in ParentMapping needs to be fixed. What am I doing wrong?
Change the fetch strategy of the bag mapping to join will generate a join query, like this:
Bag(
e => e.Children,
map => {
map.Key(km => km.Column("ParentId"));
// map.Lazy(CollectionLazy.NoLazy);
// change fetch str
map.Fetch(CollectionFetchMode.Join);
map.Cascade(Cascade.Persist);
map.Inverse(true);
},
x => x.OneToMany()
);
And you have the collection persist as inverse (map.Inverse(true);), you should have a many to one mapping for Parent in your Child class like this:
public class Child {
public virtual int Id { get; set; }
// public virtual string ParentId { get; set; }
public virtual Parent Parent { get; set; }
public virtual string Name { get; set; }
}
Then map the Parent property as ManyToOne like this:
public ChildMapping() {
// other mapping goes here
ManyToOne(
x => x.Parent,
map => {
map.Column("ParentId");
map.Class(typeof(Parent));
map.Fetch(FetchKind.Join);
}
);
}
But nhibernate do not include children of parent by default (maybe it is too heavy), and if you want to query children with parent instance, you can query like this:
using (var session = OpenSession()) {
var query = session.Query<Parent>().Select(p => new {
Parent = p, Children = p.Children
});
var data = query.ToList();
}
For saving entities to database, should do like this:
try {
// save parent first
var parent = new Parent();
parent.Name = "Parent object";
session.Save(parent);
// then save child
var child = new Child();
child.Name = "Child object";
child.Parent = parent;
session.Save(child);
session.Flush();
tx.Commit();
}
catch (Exception) {
tx.Rollback();
throw;
}
I am trying to join 2 tables, and project directly to DTOs (NHibernate 5).
I have following entities:
public class Person {
public Guid Id {get;set;}
public string Name {get;set;}
}
public class Car {
public Guid Id {get;set;}
public string Brand {get;set;}
public Person Owner {get;set;}
}
as we see, there is just reference from Car to Person (car knows its owner), and this is ok in my whole project.
However, there is one place, where I need to query all Persons, and make each person with collection of owned cars.
I created such DTOs:
public class PersonDto {
public Guid Id {get;set;}
public string Name {get;set;}
public IList<CarDto> {get;set;}
}
public class CarDto {
public Guid Id {get;set;}
public string Brand {get;set;}
}
it is kind of present the data linked together upside-down.
This task seems trivial using SQL or LINQ (GroupJoin) however I found it extremly hard to do in NH, since GroupJoin is not implemented in NH.
Can you please help me how to solve above issue?
Just add a Car collection to the existing Person entity and mark the collection inverse. Collections in NHibernate are lazy by default, so when you query Person, it won't read the cars from the DB, until you start iterating them. In other words adding the Car collection won't affect the way your code works.
When you want to efficiently query persons together with cars, force NH to do a join
.QueryOver<Person>.Fetch(person => person.Cars).Eager
I'd like to answer my own question. Thanks Rafal Rutkowski for his input.
To get data that has association in only 1 direction, we need to introduce a new data model, and then, manually convert result to our entities. I hope following example is best possible answer:
1) create such data model to store NH response:
private class PersonCarModelDto
{
public Guid PersonId { get; set; }
public string PersonName { get; set; }
public Guid CarId { get; set; }
public string CarBrand { get; set; }
}
2) create such a model to store hierarchical data as output:
private class PersonModel
{
public Guid Id { get; set; }
public string Name { get; set; }
public IList<CarModel> Cars { get; set; }
}
private class CarModel
{
public Guid Id { get; set; }
public string Brand { get; set; }
}
3) now the NH query:
Person personAlias = null;
Car carAlias = null;
PersonCarModelDto resultAlias = null;
var response = GetCurrentSession().QueryOver<Car>(() => carAlias) // notice we are going from from 'downside' entity to 'up'
.Right.JoinAlias(c => c.Owner, () => personAlias) // notice Right join here, to have also Persons without any car
.SelectList(list => list
.Select(() => personAlias.Id).WithAlias(() => resultAlias.PersonId)
.Select(() => personAlias.Name).WithAlias(() => resultAlias.PersonName)
.Select(() => carAlias.Id).WithAlias(() => resultAlias.CarId)
.Select(() => carAlias.Brand).WithAlias(() => resultAlias.CarBrand)
.TransformUsing(Transformers.AliasToBean<PersonCarModelDto>())
.List<PersonCarModelDto>()
;
4) now we have flat data as a list of PersonCarModelDto, but we want to make output model:
var modelResult = response.GroupBy(p => p.PersonId)
.Select(x => new PersonModel
{
Id = x.Key,
Name = x.Select(y => y.PersonName).First(), // First() because each PersonName in that group is the same
Cars = x.Select(y => new CarModel
{
Id = y.CarId,
Name = y.CarBrand
})
.ToList()
})
.ToList()
;
Conclusions:
the problem is much easier to solve having bidirectional associations
if for some reason, you don't want bidirectional associations, use this technique
this approach is also useful if your entities has a lot of other properties, but for some reason you need just a small part of them
(optimiziation of data returned for DB)
Hello I am updating my question for better explanation.
I have table structure having parent child relationship like this
i have created module class like this
class MenuModel
{
public int ID { get; set; }
public int MenuID { get; set; }
public string MenuItem { get; set; }
public Nullable<int> ParentID { get; set; }
public string ImagePath { get; set; }
public List<MenuModel> Children { get; set; }
}
now i want to list of root node Reordering Services having menuid 10.
This is what i have done yet
List<MenuModel> lstMenu = new List<MenuModel>();
NeelamWPFEntities _entity = new NeelamWPFEntities();
var recordlist = _entity.Menus.Where(x => x.MenuID == 10 || x.ParentID == 10).ToList();
foreach (var item in recordlist)
{
MenuModel objMenu = new MenuModel();
objMenu.ID = item.ID;
objMenu.MenuID = item.MenuID;
objMenu.MenuItem = item.MenuItem;
objMenu.ParentID = item.ParentID;
objMenu.ImagePath = item.ImagePath;
lstMenu.Add(objMenu);
}
lstMenu.ForEach(v => v.Children = lstMenu.Where(vv => vv.ParentID.Equals(v.MenuID)).ToList());
lstMenu = lstMenu.Where(u => u.ParentID == null).ToList();
return lstMenu;
by this way i will get 2 level child only.how do i get N level childs of root Reordering Services.
Plaese Help,
Thankx
Can't you do some thing like that
foreach(var correctParent in recordlist){
var grandChild = _entity.Menus.Where(x => x.ParentID == correctParent.MenuID).ToList();
//add your grandChild in a listOfAllGrandChild ...
}
Adding all your grandChild in a single entity and then sum both your grandChild and recordList, and do a distinct on it ?
I have a child table containing an id to the parent. This is a one to one mapping, but the child table might be missing values. I'm having problems mapping this without getting an error though... I've tried several things; mapping the same column, having distinct properties etc..
Parent table
int id
Child table
int parentid
Parent class
int id
Child class
Parent parent // note I'm referencing parent, not using an int id..
Mapping
Id(x => x.Parent)
.Column("parentid"); // fails
Id(x => x.Parent.Id)
.Column("parentid"); // fails
References(x => x.Parent)
.Column("parentid"); // fails - missing id
// Adding an id field in addition to parent for
// child class (id is then the same as parent.id)
// fails on save
Id( x => x.Id )
.Column("parentid");
References(x => x.Parent)
.Column("parentid");
I would like the child class not to have a distinct Id field, but rather only a reference to parent as there can never be a child without a parent. In the database however, I want to just store the parent's id.
Any ideas how I might do this?
The following works:
Id(x => x.Parent.Id).Column("MemberID");
References(x => x.Parent).Column("MemberID").ReadOnly();
The ReadOnly for the reference is important to not get an exception
EDIT: Wasn't so simple...
My child class still had the Id property being called. Seems the Id reference for Parent.Id confuses nhibernate, and it tries to call child.Id instead.
I added the following to child, and now it seems to work.. A pretty ugly hack though.
public virtual int Id {
get { return Parent.Id; }
set { Debug.Assert(value == Parent.Id); }
}
FluentNHibernate's API has changed over the years so I'm not sure if this syntax was available when this question was originally asked but you can now use a reference as an id if you map it as a composite id. I wouldn't call this a hack but it is a little strange that you have to map the reference to the parent entity as part of a composite id. Here's a full example:
public class ParentMap : ClassMap<Parent>
{
public ParentMap()
{
Table( "StackOverflowExamples.dbo.Parent" );
Id( x => x.ParentId );
Map( x => x.FirstName );
Map( x => x.LastName );
}
}
public class OnlyChildOfParentMap : ClassMap<OnlyChildOfParent>
{
public OnlyChildOfParentMap()
{
Table( "StackOverflowExamples.dbo.OnlyChildOfParent" );
CompositeId().KeyReference( x => x.Parent, "ParentId" );
Map( x => x.SomeStuff );
Map( x => x.SomeOtherStuff );
}
}
public class Parent
{
public virtual int ParentId { get; set; }
public virtual string FirstName { get; set; }
public virtual string LastName { get; set; }
}
public class OnlyChildOfParent
{
public virtual Parent Parent { get; set; }
public virtual string SomeStuff { get; set; }
public virtual string SomeOtherStuff { get; set; }
#region Overrides
public override bool Equals( object obj )
{
if ( obj == null || GetType() != obj.GetType() )
return false;
var child = obj as OnlyChildOfParent;
if ( child != null && child.Parent != null )
{
return child.Parent.ParentId == Parent.ParentId;
}
return false;
}
public override int GetHashCode()
{
return Parent.ParentId;
}
#endregion Overrides
}
Maybe this post can help.I've used the annotation .Cascade.SaveUpdate().My case was with a hasone in the parent putting the annotation on both sides
Obs: Language PT-BR
If I have the following class structure what is the NHibernate criteria to select a parent if one of it's children has a specific name?
public class Child
{
public int Id { get; set; }
public int Name { get; set; }
}
public class Parent
{
public int Id { get; set; }
public IList<Child> Children { get; set; }
}
I'd just create an alias to the collection and add restrictions.
var parentsWithKidName = session.CreateCriteria<Parent>()
.CreateAlias("Children", "c", JoinType.InnerJoin)
.Add(Restrictions.Eq("c.Name", childName))
.SetResultTransformer(Transformers.DistinctRootEntity())
.List<Parent>();
This would result in
select p.*
from parent p
inner join child c on /* however it's mapped? */
where c.Name = ?
The distinct root entity transformer will process the result set and remove duplicated parents. They still come across the wire though.
What I did was to create a criteria query for the parent type, use the return to create a criteria query for the child type, and then add the specific conditions to the child type sub query.
public virtual IList<T> GetByChildCriteria(string childName,
params ICriterion[] criterion)
{
ICriteria criteria = NHibernateSession
.CreateCriteria(persitentType)
.CreateCriteria(childName);
foreach (ICriterion criterium in criterion)
{
criteria.Add(criterium);
}
return criteria.List<T>();
}
Note: The NHibernateSession variable is of type ISession.