Simple FluentNHibernate parent/child mapping - c#

New to FluentNHibernate =D
I have a parent/children classes as follows:
public class Parent
{
public virtual int ID { get; private set; }
public virtual string Name { get; set; }
public virtual IList<Child> Children { get; set; }
}
public class Child
{
public virtual int ID { get; private set; }
public virtual string Name { get; set; }
public virtual Parent ActiveParent { get; set; }
}
With mappings of:
public ParentMap()
{
Id(x => x.ID);
Map(x => x.Name);
HasMany(x => x.Children)
.Inverse();
.Cascade.All();
}
public ChildMap()
{
Id(x => x.ID);
Map(x => x.Name);
//Map(x => x.ActiveParent)
// .Column(ParentID);
}
The commented out area of the child map is the question I'm currently having trouble with. I'd like to be able to create a child object and call its' parent(ie, someChild.ActiveParent), but am unsure on how to map this via the fluent interface.
The table structure for the child table holds a parentid, with the intent of lazy loading the parent object if called. Any help is always greatly appreciated.

References(x => x.Parent);

Adding to mxmissile's answer, you will want to add a LazyLoad() to the end of the References() call, and also you might want to do something like this in your configuration:
.Mappings(m =>
m.FluentMappings.AddFromAssemblyOf<ParentMap>()
.ConventionDiscovery.Add(ForeignKey.EndsWith("ID")))
The last line instructs Fluent NHibernate to expect foreign keys named like ParentID rather than the default (Parent_Id ?), so you no longer need to specify the column name explicitly in every relationship mapping.

Related

Update NHibernate object with child collection

I am trying to update an nHibernate object with a child collection using the .Update() method found on a hibernate session. The only thing that I can do with the current setup is add children, I can not modify them or remove them.
For clarification the objects and their mapping are as follows:
public class Parent {
public virtual int Id { get; set; }
public virtual string Name { get; set; }
public virtual ISet<Child> Children { get; set; } = new HashSet<Child>();
}
public class ParentMap: ClassMap<Parent>
{
public ParentMap()
{
Id(x => x.Id);
Map(x => x.Name);
HasMany(x => x.Children)
.AsSet()
.Inverse()
.Cascade.AllDeleteOrphan();
}
}
public class Child {
public virtual int Id { get; set; }
public virtual string Name { get; set; }
public virtual Parent Parent { get; set; }
}
public class ChildMap: ClassMap<Child>
{
public ChildMap()
{
Id(x => x.Id);
Map(x => x.Name);
References(x => x.Parent);
}
}
When I get changes from my UI layer and try to update the already existing object using:
using (var tx = _session.BeginTransaction())
_session.Update(newParent);
tx.Commit();
}
Here newParent is a transient object (obtained from the database in an earlier session andd shown in the UI) containing the same identifier as the object I would like to update, but with changes to the child collection. Somehow using this approach I can only add children, but not modify or remove them.
Where is my mistake?
Most likely what is happening to you, is that instead of modifying the set that NHibernate has instantiated in your Parent entity, you are replacing it all together by a new instance of HashSet.
When you save or get an entity from NHibernate, your Children ISet gets loaded with an instance of a PersistentGenericSet (that implements ISet) which has the responsibility of helping with this change tracking for your collection.
In short, do not assign to the Children property. In fact, make the setter protected.
Just Add() or Remove() or Clear() it as required.

NHibernate with sqlite throws a GenericADOException "constraint failed\r\nFOREIGN KEY constraint failed" trying to save

I'm trying to set up an in-memory sqlite database for unit testing per Ayende's recommendation. Everything is working great except one entity, UserPermission, always causes NHibernate to throw an GenericADOException "constraint failed\r\nFOREIGN KEY constraint failed"
This is a standard one-to-many relationship; each User can have zero, one or many UserPermissions.
Here are my models:
public class User
{
public virtual int Id { get; set; }
public virtual string LastName { get; set; }
public virtual string FirstName { get; set; }
public virtual IList<UserPermission> UserPermissions { get; set; }
}
public class UserPermission
{
public virtual int Id { get; set; }
public virtual int PermissionId { get; set; }
public virtual User User { get; set; }
public virtual Organization Organization { get; set; }
}
Here are the mappings:
public class UserMap : ClassMap<User>
{
public UserMap()
{
Table("USERS");
Id(x => x.Id).Column("USER_ID").GeneratedBy.Native(x => x.AddParam("sequence", "SEQ_USERS"));
HasMany(x => x.UserPermissions);
Map(x => x.LastName);
Map(x => x.FirstName);
}
}
public class UserPermissionMap : ClassMap<UserPermission>{
public UserPermissionMap()
{
Table("USER_PERMISSION");
Id(x => x.Id).Column("USER_PERMISSION_ID").GeneratedBy.Native(x => x.AddParam("sequence", "SEQ_USER_PERMISSION"));
Map(x => x.PermissionId);
References(x => x.User).Column("USER_ID");
References(x => x.Organization).Column("ORG_ID").Nullable();
}
}
I have googled around quite a bit this afternoon and came across a few seemingly relevant posts but none of their suggestions worked for me. Specifically here is what I tried:
In UserMap I tried adding Inverse() to the HasMany(x => x.UserPermissions)
Also in UserMap I tried adding Cascade.All() to the HasMany(x => x.UserPermissions)
In UserPermissionMap I tried adding Cascade.All() to References(x => x.User)
Various combinations of the above
In my tests the Organization is always null but I have tried setting it to a dummy Organization and that did not fix it
The crazy thing is that I am able to create and save UserPermission objects against an Oracle database; this error only happens when I attempt to save a UserPermission against sqlite.
You may try to define your HasMany mapping as :
HasMany(x => x.UserPermissions)
.KeyColumn("User_ID")
.Cascade
.AllDeleteOrphan()
.Inverse();
The .AllDeleteOrphan would delete a UserPermission as part of its removal from the UserPermissions list.
You may replace it with a simple .All if it is not the desired behavior.
Got it figured out. We have a Permission model that had a HasMany mapped to UserPermission, but the UserPermission does not have a References to the Permission model. At one point the UserPermission model probably had a Permission property that would return the Permission object, then we changed it to an int and forgot to update the Permission mapping.
Here is what I found in my PermissionMap:
public class PermissionMap : ClassMap<Permission>
{
public PermissionMap()
{
Table("PERMISSION");
Id(x => x.Id).Column("PERMISSION_ID");
Map(x => x.Name);
HasMany(x => x.UserPermissions).LazyLoad(); // Removed this line to fix issue
}
}
Lesson learned: when you remove a relationship between to models, don't forget to remove it from BOTH sides.

fluent nhibernate one to many collection, filtered by enum

I think I have a design issue here.
essentially I have a class called office
class Office
{
public virtual long Id { get; set; }
public virtual string Code { get; set; }
public virtual IList<Person> Managers { get; set; }
public virtual IList<Person> Developers { get; set; }
public virtual IList<Person> TeaMakers { get; set; }
}
and a class called Person
class Person
{
public virtual long Id { get; set; }
public virtual string Name {get; set;}
public virtual StaffType Type { get; set;}
public virtual Office Office { get; set; }
}
and an enum called StaffType
public enum StaffType
{
MANAGER,
DEVELOPER,
TEAMAKER
}
Mapping the Person table is easy:
public class PersonMap: ClassMap<Person>
{
public PersonMap()
{
Table("Person");
Id(x => x.Id);
Map(x => x.Name);
References(x => x.Office).ForeignKey("Id").Not.Nullable()
Map(x => x.Type).CustomType<StaffType>();
}
}
but i am stumped on the office map. how to i get the map to use the enum to filter the 3 lists?
if i do this:
public class OfficeMap: ClassMap<Office>
{
public static string TableName = "Office";
public static string MappingColumn = TableName + "Id";
public OfficeMap()
{
Table(TableName);
Id(x => x.Id);
Map(x = x.Code);
HasMany(x => x.Managers)
.Cascade.AllDeleteOrphan()
.Fetch.Select()
.Inverse().KeyColumn(MappingColumn);
HasMany(x => x.Developers)
.Cascade.AllDeleteOrphan()
.Fetch.Select()
.Inverse().KeyColumn(MappingColumn);
HasMany(x => x.TeaMakers)
.Cascade.AllDeleteOrphan()
.Fetch.Select()
.Inverse().KeyColumn(MappingColumn);
}
}
fluency won't have the foggiest idea how to split the 3 collections up by the StaffType enum
Thanks for the help
Extra note: the Person table's Type field allways gets mapped as an int.
NHibernate supports filtering as a part of the mapping. Please, read here more 6.2. Mapping a Collection.
The trick is to add more SQL into the mapping. In fact, some WHERE Condition, to be evaluated during the collection load. Small extract from the documentation:
<map // or <set or <bag ...
name="propertyName" (1)
table="table_name" (2)
...
where="arbitrary sql where condition" (9)
And the description of the WHERE:
where (optional) specify an arbitrary SQL WHERE condition to be used
when retrieving or removing the collection (useful if the collection
should contain only a subset of the available data)
In your case, the fluent syntax is similar: ...Where("MyColumn = 'myValue' ");
A draft for your solution:
...
HasMany(x => x.Managers)
.Cascade.AllDeleteOrphan()
.Fetch.Select()
.Inverse().KeyColumn(MappingColumn)
.Where("Type = 1") // the Column name in the Person table
; // and the value 1 as the enum of the Manager
...
// The same for the others
I would model this as a simple one-to-many (Office has many Person) and add an extension method to IEnumerable<Person> to filter by StaffType. If needed, you can encapsulate access to the Person collection through AddManager etc. methods that enforce business rules.

Many to Many mapping not working - EF 4.1 RC

UPDATE: After a bit more research it seems a number of my many-to-many mappings aren't working. Hmmm...
I'm upgrading a data access project from EF 4.1 CTP4 to EF 4.1 RC and I'm having trouble with the new EntityTypeConfiguration<T> setup.
Specifically I'm having an issue with a Many-to-Many relationship. I'm getting a Sequence contains no elements exception when I'm trying to get the .First() item.
The particular exception isn't really that interesting. All it's saying is that there are no items BUT I know there should be items in the collection - so there must be an issue with my new mappings.
Here's the code I have so far:
Product Model
public class Product : DbTable
{
//Blah
public virtual ICollection<Tag> Categories { get; set; }
public Product()
{
//Blah
Categories = new List<Tag>();
}
}
BaseConfiguration
public class BaseConfiguration<T> : EntityTypeConfiguration<T> where T : DbTable
{
public BaseConfiguration()
{
this.HasKey(x => x.Id);
this.Property(x => x.Id).HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity);
this.Property(x => x.UpdatedOn);
this.Property(x => x.CreatedOn);
}
}
ProductConfiguration
public class ProductConfiguration : BaseConfiguration<Product>
{
public ProductConfiguration()
{
this.ToTable("Product");
//Blah
this.HasMany(x => x.Categories)
.WithMany()
.Map(m =>
{
m.MapLeftKey("Tag_Id");
m.MapRightKey("Product_Id");
m.ToTable("ProductCategory");
});
}
}
Previous CTP4 Mapping the worked!
this.HasMany(x => x.Categories)
.WithMany()
.Map("ProductCategory", (p, c) => new { Product_Id = p.Id, Tag_Id = c.Id });
Can anyone see anything that needs fixing? Let me know if you want me to provide more code.
EDIT: More Code
DbTable
public class DbTable : IDbTable
{
public int Id { get; set; }
public DateTime UpdatedOn { get; set; }
public DateTime CreatedOn { get; set; }
}
Tag
public class Tag
{
public int Id { get; set; }
public string Name { get; set; }
public string Slug { get; set; }
public bool Visible { get; set; }
public virtual TagType TagType { get; set; }
}
TagConfiguration
public class TagConfiguration : EntityTypeConfiguration<Tag>
{
public TagConfiguration()
{
this.ToTable("Tags");
this.HasKey(x => x.Id);
this.Property(x => x.Id).HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity).HasColumnName("tag_id");
this.Property(x => x.Name).HasMaxLength(300).HasColumnName("tag_name");
this.Property(x => x.Slug).HasMaxLength(500).HasColumnName("tag_slug");
this.Property(x => x.Visible).HasColumnName("tag_visible");
this.HasRequired(x => x.TagType).WithMany(tt => tt.Tags).Map(m => m.MapKey("tagtype_id"));
}
}
Yes, this is a legacy database with naming conventions up to boohai.
I know the Tag class must be wired up correctly because Product has another property Specialization which is also mapped to Tag and it loads correctly. But do note that it's mapped in a one-to-many manner. So it seems to be the many-to-many with Tag.
I'll start checking out if any many-to-many associations are working.
You need to specify both navigation properties to do many to many mapping.
Try adding the lambda in the WithMany property pointing back to the products:
this.HasMany(x => x.Categories)
.WithMany(category=>category.Products)
.Map(m =>
{
m.MapLeftKey(t => t.TagId, "Tag_Id");
m.MapRightKey(t => t.ProductId, "Product_Id");
m.ToTable("ProductCategory");
});
(crossing fingers...)
I haven't used the Code-First approach yet, but when working with POCOs I had to enable Lazy-Loading, to make Navigation Properties work. This is of course by design, but I don't know if you have to explicitly enable this behavior for Code-First.

Fluent NHibernate hasmany save insert null value

i'm new to nhibernate so maybe the response depends on my lack of knowledge.
I created these two tables:
(sorry for italian language, i hope you can understand withouth any problems).
Then, i have these object in my model:
[Serializable]
public class Profilo
{
public virtual int Id { get; set; }
public virtual string Matricola { get; set; }
public virtual string Ruolo { get; set; }
public virtual IList ListaSedi { get; set; }
public Profilo()
{
ListaSedi = new List();
}
}
[Serializable]
public class Sede
{
public virtual string CodiceSede { get; set; }
public virtual string DescrizioneSede { get; set; }
public virtual Profilo Profilo { get; set; }
}
This is the way i mapped entities using fluent nhibernate:
public class Map_Sede : FluentNHibernate.Mapping.ClassMap
{
public Map_Sede()
{
Table("TBA_Sede");
Id(x => x.CodiceSede).Column("codice_sede").GeneratedBy.Assigned();
Map(x => x.DescrizioneSede)
.Column("descrizione");
References(prof => prof.Profilo)
.Column("codice_sede");
}
}
public class Map_Profilo : FluentNHibernate.Mapping.ClassMap
{
public Map_Profilo()
{
Table("TBA_Profilo");
Id(x => x.Id).Column("id").GeneratedBy.Identity();
Map(x => x.Matricola)
.Column("matricola");
Map(x => x.Ruolo)
.Column("ruolo");
HasMany(x => x.ListaSedi)
.AsBag()
.KeyColumns.Add("codice_sede")
.Not.LazyLoad()
.Cascade.None();
}
}
Now, i'd like to insert a new Profilo instance on my. Everything seems to work but nhibernate does not insert values on TBA_Profilo.codice_sede column. I noticed that the insert statement is composed by two parameters (matricola, ruolo) - why does it forget the third parameter?
I read somewhere (on nhibernate mailing list) that's quite normal 'cause nhiberate insert values with null first and then update the same records with right values contained in the list property.
Is it right?
Am i doing any errors?
I hope to have clarified the situation.
thx guys
ps: I'm using Nhibernate 2.1 and fluent nhibernate 1.1
UPDATE: This is the code i use to save entity.
var sesionFactory = NHibernateHelper.createSessionFactory();
using (NHibernate.ISession session = sesionFactory.OpenSession())
{
using (var transaction = session.BeginTransaction())
{
try
{
session.SaveOrUpdate(entity);
transaction.Commit();
session.Flush();
}
catch (Exception)
{
transaction.Rollback();
throw;
}
finally
{
transaction.Dispose();
}
}
}
UPDATE 2: Following sly answer i slightly modified my solution. These are new model entities:
[Serializable]
public class Profilo
{
public virtual int Id { get; set; }
public virtual string Matricola { get; set; }
public virtual string Ruolo { get; set; }
public virtual IList ListaSedi { get; set; }
public Profilo()
{
ListaSedi = new List();
}
}
[Serializable]
public class Sede
{
public virtual string CodiceSede { get; set; }
public virtual string DescrizioneSede { get; set; }
public virtual IList ProfiliAssociati { get; set; }
}
And new mappings:
public class Map_Sede : FluentNHibernate.Mapping.ClassMap
{
public Map_Sede()
{
Table("TBA_Sede");
Id(x => x.CodiceSede).Column("codice_sede").GeneratedBy.Assigned();
Map(x => x.DescrizioneSede)
.Column("descrizione");
HasMany(x => x.ProfiliAssociati)
.AsBag()
.KeyColumns.Add("codice_sede")
.Cascade.All();
}
}
public class Map_Profilo : FluentNHibernate.Mapping.ClassMap
{
public Map_Profilo()
{
Table("TBA_Profilo");
Id(x => x.Id).Column("id").GeneratedBy.Identity();
Map(x => x.Matricola)
.Column("matricola");
Map(x => x.Ruolo)
.Column("ruolo");
HasMany(x => x.ListaSedi)
.AsBag()
.Inverse()
.KeyColumns.Add("codice_sede")
.Cascade.SaveUpdate();
}
}
Now it seems quite to work: i mean that nhibernate can write TBA_Profilo.codice_sede column even if it doesn't cicle on Profilo.IList (it inserts the last element of this List).
Any ideas?
KeyColumns mapping is applied only child table in many-to-one connection.
If you want to have connection you will need to use Id column from TBA_portfolio table and reference it from TBA_Sede.
Something like this:
Tba_portfolio
Id|matricola|ruolo
Tba_sede
Id|PorfolioId|descrizione
Your mapping is wrong, try this:
public class Map_Profilo : FluentNHibernate.Mapping.ClassMap
{
public Map_Profilo()
{
Table("TBA_Profilo");
Id(x => x.Id).Column("id").GeneratedBy.Identity();
Map(x => x.Matricola)
.Column("matricola");
Map(x => x.Ruolo)
.Column("ruolo");
HasMany(x => x.ListaSedi)
.AsBag()
.KeyColumns.Add("codice_sede")
.Not.LazyLoad()
.Cascade.SaveUpdate();
}
}
public class Map_Sede : FluentNHibernate.Mapping.ClassMap
{
public Map_Sede()
{
Table("TBA_Sede");
Id(x => x.CodiceSede).Column("codice_sede").GeneratedBy.Assigned();
Map(x => x.DescrizioneSede)
.Column("descrizione");
References(prof => prof.Profilo)
.Column("codice_sede")
.Cascade.None()
.Inverse();
}
}
The key is, you need the parent profilio to save its child items. Have the child items do nothing to its parent. Also, your table diagram looks wrong, profolio should not have a key to sede, but sede should have a fk to profolio.

Categories

Resources