Fluent / NHibernate Collections of the same class - c#

I am new to NHibernate and I am having trouble mapping the following relationships within this class.
public class Category : IAuditable
{
public virtual int Id { get; set; }
public virtual string Name{ get; set; }
public virtual Category ParentCategory { get; set; }
public virtual IList<Category> SubCategories { get; set; }
public Category()
{
this.Name = string.Empty;
this.SubCategories = new List<Category>();
}
}
Class Maps (although, these are practically guesses)
public class CategoryMap : ClassMap<Category>
{
public CategoryMap()
{
Id(x => x.Id);
Map(x => x.Name);
References(x => x.ParentCategory)
.Nullable()
.Not.LazyLoad();
HasMany(x => x.SubCategories)
.Cascade.All();
}
}
Each Category may have a parent category, some Categories have many subCategories, etc, etc
I can get the Category to Save correctly (correct subcategories and parent category fk exist in the database) but when loading, it returns itself as the parent category.
I am using Fluent for the class mapping, but if someone could point me in the right direction for just plain NHibernate that would work as well.

By convention, Fluent NHibernate will look at "Category_Id" as foreign key column. It won't figure out your "ParentCategoryId" column. Unless you rename your self-referencing column to "Category_Id", you have to clarify the column name for both parent and child relationships.
For category without parent (absolute-parent category), whose reference column is null, it's reasonable to return itself as parent or null depending on how NHibernate handles it since you choose eager loading.
public class CategoryMap : ClassMap<Category>
{
public CategoryMap()
{
Id(x => x.Id);
Map(x => x.Name);
References(x => x.ParentCategory)
.Column("ParentCategoryId") // Many-To-One : parent
.Nullable()
.Not.LazyLoad();
HasMany(x => x.SubCategories)
.Cascade.All().Inverse().KeyColumn("ParentCategoryId"); //One-To-Many : chidren
}
}

Ok so on the HasMany(x=>x.SubCategories) you need to add Inverse() to the call chain and also give it the column name which I'm assuming is "ParentCategoryId" given the mapping of the parent category, of course this depends on your conventions too.
If you were to post your table stucture I can give you a complete solution.

Related

TPC in Entity Framework v6

I'm trying to do a pretty simple thing in Entity Framework.
I have a product that has zero or more parameters and these parameters will be mapped to their own tables. However, I'm unable to get this to work. I've been trying to get the mappings right and then use the migrations to see what the database is supposed to look like. I know that this is very simple in NHibernate, but I'm forced against my will to use Entity Framework v6.
Background
These are my entities:
namespace Entities
{
public class EntityState
{
public int Id { get; set; }
}
public class ProductState : EntityState
{
public virtual ICollection<ProductParameterState> Parameters { get; set; }
}
public abstract class ProductParameterState : EntityState
{
}
public class ColorParameterState : ProductParameterState
{
public virtual string Color { get; set; }
}
public class SizeParameterState : ProductParameterState
{
public virtual int Size { get; set; }
}
}
I would like to store this in the following schema:
How to do this?
My attempts
Table-per-class
I tried mapping using TPC:
namespace Mappings
{
public class ProductMap : EntityTypeConfiguration<ProductState>
{
public ProductMap()
{
HasKey(x => x.Id);
Property(x => x.Name);
HasMany(x => x.Parameters);
}
}
public class ColorParameterMap : EntityTypeConfiguration<ColorParameterState>
{
public ColorParameterMap()
{
HasKey(x => x.Id);
Property(x => x.Color);
Map(x =>
{
x.ToTable("ColorParameters");
x.MapInheritedProperties();
});
}
}
public class SizeParameterMap : EntityTypeConfiguration<SizeParameterState>
{
public SizeParameterMap()
{
HasKey(x => x.Id);
Property(x => x.Size);
Map(x =>
{
x.ToTable("SizeParameters");
x.MapInheritedProperties();
});
}
}
}
But this gives the error The association 'ProductState_Parameters' between entity types 'ProductState' and 'ProductParameterState' is invalid. In a TPC hierarchy independent associations are only allowed on the most derived types..
Don't use an inheritence strategy
So I tried to remove the MapInheritedProperties, but then it wants to create an additional, and unwanted, table:
CreateTable(
"dbo.ProductParameterStates",
c => new
{
Id = c.Int(nullable: false, identity: true),
ProductState_Id = c.Int(),
})
.PrimaryKey(t => t.Id)
.ForeignKey("dbo.ProductStates", t => t.ProductState_Id)
.Index(t => t.ProductState_Id);
I don't want this. I am able to get rid of this one by removing the Parameters property in Product, but then I'm not able to use the Parameters of a Product.
Am I asking for too much or is it possible?
You can use TPC, but the relationship must be bidirectional with explicit FK defined (which I guess is the opposite of "independent association" mentioned in the error message).
Add inverse navigation property and FK property to your base entity:
public abstract class ProductParameterState : EntityState
{
public int ProductId { get; set; }
public ProductState Product { get; set; }
}
and use the same entity configurations as in your first attempt, except for the ProductMap where you either remove the following
HasMany(x => x.Parameters);
or change it to
HasMany(e => e.Parameters)
.WithRequired(e => e.Product)
.HasForeignKey(e => e.ProductId);

Save parent with children in one query ( children have null value reference for parent)

Good day. I can't save parent with children in same query. But children have null reference for parent...
Parent in my DB is table "food_in_the_cart" and model for it is here:
public class FoodInTheCartModel
{
public virtual int ID { get; set; }
public virtual ClientModel Client { get; set; }
public virtual EstablishmentModel Stocked { get; set; }
public virtual ProductModel DesirableProduct { get; set; }
public virtual IList<CartAdditiveModel> Additives { get; set; } //children
public virtual void AddAdditivesToTheCart(CartAdditiveModel a)
{
a.Cart = this;
Additives.Add(a);
}
public FoodInTheCartModel()
{
this.Additives = new List<CartAdditiveModel>();
}
}
Mapping also:
public FoodInTheCartMap()
{
Table("food_in_the_cart");
Id(x => x.ID)
.Column("id")
.GeneratedBy.Native("food_in_the_cart_id_seq");
References(x => x.Client)
.Column("fk_id_client")
.Not.Nullable()
.Not.LazyLoad();
References(x => x.DesirableProduct)
.Column("fk_id_product")
.Not.Nullable()
.Not.LazyLoad();
References(x => x.Stocked)
.Column("fk_id_stock")
.Not.Nullable()
.Not.LazyLoad();
HasMany(x => x.Additives)
.Table("cart_additive")
.Cascade.SaveUpdate()
.Cascade.All()
.Inverse()
.Not.LazyLoad();
}
And child is cart_additive. Model of cart_additive is type of CartAdditive and has reference for model of food_in_the_cart, also mapping this reference is:
References(x => x.Cart)
.Column("fk_id_cart")
.Not.LazyLoad();
Type relationship between these two tables is one to many: food_in_the_cart has many cart_additive. Seems I tried everything for saving query for parent with children... But children still have null value for parent. How to make parent reference in child not null?
I removed Insert() from has many in FoodInTheCartMap and and added AsBug() there, also allowed null values for fk of food_in_the_cart in cart_additives as for reference called Cart in model for this table. All works this way. Yay.

Fluent NHibernate: One to One Mapping - reverse from primary column to a foreign key

I have a "Group" class and a "GroupSummaryLevel" class, codes are given below. There is a one-to-one relation between these entities in DB. I need the "GroupSummaryLevel" as a property in Groups class. It is supposed to be a very simple join like
(SELECT g.Id FROM GroupSummaryLevel g WHERE g.AcctGroup = GroupID)
Unfortunately, I wasn't able to figure this out how to do with NHibernate. The many answers I saw here is no help to me. I would appreaciate any inputs from the more experienced NHibernate users out there. Thanks in advance.
public class Group : DomainEntity
{
public virtual string GroupId { get; set; }
public virtual string GroupName { get; set; }
public virtual GroupSummaryLevel GroupSummaryLevel { get; set; }
}
public class GroupSummaryLevel : DomainEntity
{
public virtual int Id { get; set; }
public virtual string AcctGroup { get; set; }
public virtual GroupSummaryLevel Parent { get; set; }
public virtual IList<GroupSummaryLevel> Children { get; set; }
public GroupSummaryLevel()
{
Children = new List<GroupSummaryLevel>();
}
}
The mapping I have done did not work so far. My mapping codes are as below:
public GroupMap()
{
Table("Groups");
LazyLoad();
Id(x => x.GroupId).GeneratedBy.Assigned().Column("GroupID").CustomType<TrimmedString>();
Map(x => x.GroupName).Column("GroupName").CustomType<TrimmedString>().Not.Nullable();
HasOne(x => x.GroupSummaryLevel).Cascade.None().ForeignKey("AcctGroup");
}
public GroupSummaryLevelMap()
{
Table("GroupSummaryLevel");
LazyLoad();
Id(x => x.Id).GeneratedBy.Identity().Column("Id");
Map(x => x.AcctGroup).Column("AcctGroup").CustomType<TrimmedString>().Not.Nullable();
//References(x => x.Parent).Column("ParentId");
//HasMany(x => x.Children).Cascade.All().KeyColumn("ParentId");
}
Note: I also need to do a self-join for GroupSummaryLevel, and no success with that either. Any recommendations for that will also be appreciated :)
I would say, that your one-to-one is not driven by primary/foreign keys, but by property-ref. So the Group should map the summary by saying something like this:
...if you want to find related SummaryLevel, pass my <id> into column mapped as AcctGroup
public GroupMap()
{
...
HasOne(x => x.GroupSummaryLevel)
.Cascade.None()
//.ForeignKey("AcctGroup")
.PropertyRef(gsl => gsl.AcctGroup)
;
}
public GroupSummaryLevelMap()
{
...
//References(x => x.Parent).Column("ParentId");
//HasMany(x => x.Children).Cascade.All().KeyColumn("ParentId");
References(x => x.Parent, "AcctGroup");
}
NOTEs for completeness, as discussed in comments:
In this scenario, when the "child" has reference to parent - it really calls for one-to-many/.HasMany() mapping.
The down side is, that child is represented as a colleciton of children: IList<GroupSummaryLevel>. It is not as straighforward to use, but we can create some virtual property, returning the .FirstOrDefault(). The benefit we get - is lazy loading (not in place with one-to-one).

NHibernate not loading child objects

I have following class and associated mappings (Fluent NHibernate):
public class Category
{
public virtual int Id { get; set; }
public virtual string Name { get; set; }
public virtual string Description { get; set; }
public virtual Category ParentCategory { get; set; }
public virtual IList<Category> ChildCategories { get; set; }
}
Mappings:
public class CategoryMap : ClassMap<Category>
{
public CategoryMap()
{
Table("Categories");
Id(x => x.Id).Column("Id").CustomType("Int32").Access.Property()
.CustomSqlType("int").Not.Nullable().Precision(10)
.GeneratedBy.Identity();
Map(x => x.Description).Column("Description").Nullable()
.Generated.Never().CustomType(typeof (string)).Access
.Property().Length(250);
Map(x => x.Name).Not.Nullable().Generated.Never().CustomType("string")
.Access.Property().Column("Name").Length(50);
References(x => x.ParentCategory).Column("ParentCategoryId");
HasMany(x => x.ChildCategories).KeyColumn("ParentCategoryId").Inverse()
.AsBag().Fetch.Select();
}
}
I creating two Category object as follows:
var c = new Category
{
Name = "Ebooks",
Description = "Contains awz, mobi, pdf, epub and other
electronic books"
};
var cc = new Category
{
Name = "Kindle Books",
Description = "Contains Kindle ebook reader format books
(awz, mobi)",
ParentCategory = c
};
session.SaveOrUpdate(c);
session.SaveOrUpdate(cc);
When I try to access saved objects:
var c = session.Load<Category>(1);
var cc = c.ChildCategories;
c contains the Category object with Id of 1 but its ChildCategories property is null.
What I am doing wrong?
PS:- This is my first experiment with NHibernate and so with Fluent NHibernate.
EDIT:- The following stuff worked. I needed to close the session before opening it again for reading. Otherwise it read just from memory nad as #Holf has pointed out I needed to add Child Category to Category like:
c.ChilCategories.Add(cc);
I just did as follows:
var session = sf.OpenSession();
CreateCategory(session);//ADDED TWO CATEGORIES EBooks, Kindle Ebooks
session.Close();
session = sf.OpenSession();
FetchCategories(session);//READ CATEGORY EBooks AND ASSOCIATED CHILDREN
session.Close();
Although you've handled one side of the relationship, by assigning 'c' as the ParentCategory of 'cc', I can't see anywhere that you've done the inverse.
I think you'll also need to do
c.ChildCategories.Add(cc);
before doing the SaveOrUpdate.
Can you update your map to the following:
public class CategoryMap : ClassMap<Category>
{
public CategoryMap()
{
Table("Categories");
Id(x => x.Id).Column("Id").CustomType("Int32").Access.Property()
.CustomSqlType("int").Not.Nullable().Precision(10)
.GeneratedBy.Identity();
Map(x => x.Description).Column("Description").Nullable()
.Generated.Never().CustomType(typeof (string)).Access
.Property().Length(250);
Map(x => x.Name).Not.Nullable().Generated.Never().CustomType("string")
.Access.Property().Column("Name").Length(50);
References(x => x.ParentCategory).Column("ParentCategoryId");
HasMany(x => x.ChildCategories).KeyColumn("ParentCategoryId").Inverse()
.Cascade.All();
}
}
Also you cannot be sure that the Id with value 1 refers to the Parent Category, use the LInq provider to load the correct object.

Linq to NHibernate - eager load grandchildren but not children

I am using NHibernate 3.2 with FluentNHibernate and Linq to NHibernate. I want use Linq to NHibernate to eager load all of the grandchildren of a collection without having to load the children. For example, say that I have the following classes:
public class Parent
{
public virtual int Id { get; set; }
public virtual IList<Child> Children { get; set; }
}
public class ParentMap : ClassMap<Parent>
{
Id(x => x.Id);
HasManyToMany(x => x.Children).ExtraLazyLoad();
}
public class Child
{
public virtual int Id { get; set; }
public virtual IList<Parent> Parents { get; set; }
public virtual IList<Grandchild> Grandchildren { get; set; }
public virtual ProhibitivelyLargeType ProhibitivelyLargeField { get; set; }
public virtual ProhibitivelyLargeType RarelyUsedLargeField { get; set; }
}
public class ChildMap : ClassMap<Child>
{
Id(x => x.Id);
HasManyToMany(x => x.Parents).ExtraLazyLoad();
HasManyToMany(x => x.Grandchildren).ExtraLazyLoad();
Map(x => x.ProhibitivelyLargeField);
Map(x => x.RarelyUsedField).LazyLoad();
}
public class Grandchild
{
public virtual int Id { get; set; }
public virtual IList<Child> Children { get; set; }
public virtual int Age { get; set; }
}
public class GrandchildMap : ClassMap<Grandchild>
{
Id(x => x.Id);
HasManyToMany(x => x.Children).ExtraLazyLoad();
Map(x => x.Age);
}
For each Parent, I want to find out the total combined age of all of that parent's grandchildren. I could do so using the following method:
Dictionary<Parent, int> grandchildAges = session.Query<Parent>()
.FetchMany(p => p.Children)
.ThenFetchMany(c => c.Grandchildren)
.AsEnumerable()
.ToDictionary(
p => p,
p => p.Children.SelectMany(c => c.Grandchildren).Sum(g => g.Age)
);
This method produces the correct results. However, it necessitates loading all of the Child objects. Child includes a field of type ProhibitivelyLargeType, which is not lazy loaded, so I would really prefer to not load anything about Child but its ID. If I don't use FetchMany/ThenFetchMany, however, then I have the N + 1 problem and it takes a trip to the database for each Child and Grandchild, which is also unacceptable.
Alternatively, I could make ProhibitivelyLargeField LazyLoad. However, most applications that use the Child class need to use ProhibitivelyLargeField, but they do not want to have to load RarelyUsedLargeField, which is already LazyLoad. As I understand it, loading one LazyLoad property causes all of them to be loaded, so this solution would bog down the normal use case.
Is there a way to get the just the information that I am looking for using Linq to NHibernate, or do I have to use the Criteria Query API?
Thanks!
edited to give an example of why making ProhibitivelyLargeField LazyLoad might be undesirable
the following is QueryOver. its only to show the idea of loading the results in two smaller steps. Maybe you can translate it into LINQ
// inititialize the dictionary
Grandchild grandchild = null;
Dictionary<Parent, int> dict = session.QueryOver<Parent>()
.JoinQueryOver(p => p.Childs)
.JoinAlias(c => c.GrandChilds, () => grandchild)
.Select(Projections.Group<Parent>(p => p.Id), Projections.Sum(() => grandchild.Age))
.AsEnumerable()
.Cast<object[]>()
.ToDictionary(
array => session.Load<Parent>(array[0]),
array => (int)array[1]
);
// initialize all Parent proxies
session.QueryOver<Patient>()
.WhereProperty(p => p.Id).In(dict.Keys.Select(p => p.Id))
.ToList();
I haven't used nhibernate, but I have used linq to entities, and from what I see you are doing tons of database queries. You should instead do a single line query which returns only the data you want:
from parent in session.Parents
let children = parent.Children
select new {parent = parent, children.SelectMany(c => c.Grandchildren).Sum(gc => gc.Age)}
Apologies if I got something wrong. Haven't done C# in a while and I'm on my phone.
If this approach doesn't work someone just tell me and I'll delete it.

Categories