(Fluent) NHibernate: HasMany/References (bidirectional) + Cascade Save - c#

How do I configure mappings for bidirectional mapping in the scenario of X references Y, Y has many Xs, so that when I add many Xs to the Y instance and save Y instance, it'll work properly?
to make the problem clear, here's the code:
ClientCompany model has HasMany ContactPerson related models:
public class ClientCompany
{
// ....
public virtual ISet<ContactPerson> ContactPeople { get; set; }
}
// mapping:
HasMany(x => x.ContactPeople)
.AsSet()
.Inverse()
.Cascade.All();
the ContactPerson has not-nullable "ClientCompany" field referencing the parent company:
public class ContactPerson
{
// ....
public virtual ClientCompany ClientCompany { get; set; }
}
References(x => x.ClientCompany).Not.Nullable();
calling this code:
sess.Save(new ClientCompany()
{
ContactPeople = new HashSet<ContactPerson>()
{
new ContactPerson()
}
});
causes this exception:
not-null property references a null or transient value
xxxx.ContactPerson.ClientCompany
this is just simplified case, I'm using AutoMapper in my real-world project so setting the reference manually is not the solution

The most important thinkg in ORM world, namely when using NHibernate, is:
ALWAYS set both sides of relation.
This is valid way, how to set both sides:
var person = new ContactPerson();
var company = new ClientCompany
{
ContactPeople = new HashSet<ContactPerson>()
{
person,
},
};
person.ClientCompany = company
sess.Save(company);
By mapping is the Person declared as responsible for its relation (the inverse mapping .Inverse()).
And that means, that it must have set the relation to its parent. This relation is used later for INSERT statement... and it cannot be null.
There is no need to save company and person separatelly. The only essential and absolutely important thing is to set both reference sides. That is a MUST.
Not sure what you can do with auto mapper... but some kind of wrapping/shielding of the bi-directional setting could be some special method on the POCO object:
public class ClientCompany
{
// ....
public virtual ISet<ContactPerson> ContactPeople { get; set; }
public virtual AddPerson(ContactPerson person)
{
ContactPeople = ContactPeople ?? new HashSet<ContactPerson>();
ContactPeople.Add(person);
person.ClientCompany = this;
}
}
Now we just have to call AddPerson() at the right time...

Related

Tracking changes in Entity Framework for many-to-many relationships with behavior

I'm currently attempting to use Entity Framework's ChangeTracker for auditing purposes. I'm overriding the SaveChanges() method in my DbContext and creating logs for entities that have been added, modified, or deleted. Here is the code for that FWIW:
public override int SaveChanges()
{
var validStates = new EntityState[] { EntityState.Added, EntityState.Modified, EntityState.Deleted };
var entities = ChangeTracker.Entries().Where(x => x.Entity is BaseEntity && validStates.Contains(x.State));
var entriesToAudit = new Dictionary<object, EntityState>();
foreach (var entity in entities)
{
entriesToAudit.Add(entity.Entity, entity.State);
}
//Save entries first so the IDs of new records will be populated
var result = base.SaveChanges();
createAuditLogs(entriesToAudit, entityRelationshipsToAudit, changeUserId);
return result;
}
This works great for "normal" entities. For simple many-to-many relationships, however, I had to extend this implementation to include "Independent Associations" as described in this fantastic SO answer which accesses changes via the ObjectContext like so:
private static IEnumerable<EntityRelationship> GetRelationships(this DbContext context, EntityState relationshipState, Func<ObjectStateEntry, int, object> getValue)
{
context.ChangeTracker.DetectChanges();
var objectContext = ((IObjectContextAdapter)context).ObjectContext;
return objectContext
.ObjectStateManager
.GetObjectStateEntries(relationshipState)
.Where(e => e.IsRelationship)
.Select(
e => new EntityRelationship(
e.EntitySet.Name,
objectContext.GetObjectByKey((EntityKey)getValue(e, 0)),
objectContext.GetObjectByKey((EntityKey)getValue(e, 1))));
}
Once implemented, this also worked great, but only for many-to-many relationships that use a junction table. By this, I'm referring to a situation where the relationship is not represented by a class/entity, but only a database table with two columns - one for each foreign key.
There are certain many-to-many relationships in my data model, however, where the relationship has "behavior" (properties). In this example, ProgramGroup is the many-to-many relationship which has a Pin property:
public class Program
{
public int ProgramId { get; set; }
public List<ProgramGroup> ProgramGroups { get; set; }
}
public class Group
{
public int GroupId { get; set; }
public IList<ProgramGroup> ProgramGroups { get; set; }
}
public class ProgramGroup
{
public int ProgramGroupId { get; set; }
public int ProgramId { get; set; }
public int GroupId { get; set; }
public string Pin { get; set; }
}
In this situation, I'm not seeing a change to a ProgramGroup (eg. if the Pin is changed) in either the "normal" DbContext ChangeTracker, nor the ObjectContext relationship method. As I step through the code, though, I can see that the change is in the ObjectContext's StateEntries, but it's entry has IsRelationship=false which, of course, fails the .Where(e => e.IsRelationship) condition.
My question is why is a many-to-many relationship with behavior not appearing in the normal DbContext ChangeTracker since it's represented by an actual class/entity and why is it not marked as a relationship in the ObjectContext StateEntries? Also, what is the best practice for accessing these type of changes?
Thanks in advance.
EDIT:
In response to #FrancescCastells's comment that perhaps not explicitly defining a configuration for the ProgramGroup is cause of the problem, I added the following configuration:
public class ProgramGroupConfiguration : EntityTypeConfiguration<ProgramGroup>
{
public ProgramGroupConfiguration()
{
ToTable("ProgramGroups");
HasKey(p => p.ProgramGroupId);
Property(p => p.ProgramGroupId).IsRequired();
Property(p => p.ProgramId).IsRequired();
Property(p => p.GroupId).IsRequired();
Property(p => p.Pin).HasMaxLength(50).IsRequired();
}
And here are my other configurations:
public class ProgramConfiguration : EntityTypeConfiguration<Program>
{
public ProgramConfiguration()
{
ToTable("Programs");
HasKey(p => p.ProgramId);
Property(p => p.ProgramId).IsRequired();
HasMany(p => p.ProgramGroups).WithRequired(p => p.Program).HasForeignKey(p => p.ProgramId);
}
}
public class GroupConfiguration : EntityTypeConfiguration<Group>
{
public GroupConfiguration()
{
ToTable("Groups");
HasKey(p => p.GroupId);
Property(p => p.GroupId).IsRequired();
HasMany(p => p.ProgramGroups).WithRequired(p => p.Group).HasForeignKey(p => p.GroupId);
}
When these are implemented, EF still does not show the modified ProgramGroup in the ChangeTracker.
While the concept of "relationship with attributes" is mentioned in the theory of entity-relationship modelling, as far as Entity Framework is concerned, your ProgramGroup class is an entity. You're probably unwittingly filtering it out with the x.Entity is BaseEntity check in the first code snippet.
I believe the problem lies in the definition of your Program and Group class and overridden SaveChanges method. With the current definition of the classes the EF is unable to use change tracking proxies, that catch changes as they are being made. Instead of that the EF relies on the snapshot change detection, that is done as part of SaveChanges method. Since you call base.SaveChanges() at the end of the overridden method, the changes are not detected yet when you request them from ChangeTracker.
You have two options - you can either call ChangeTracker.DetectChanges(); at the beginning of the SaveChanges method or change definition of your classes to support change tracking proxies.
public class Program {
public int ProgramId { get; set; }
public virtual ICollection<ProgramGroup> ProgramGroups { get; set; }
}
public class Group {
public int GroupId { get; set; }
public virtual ICollection<ProgramGroup> ProgramGroups { get; set; }
}
The basic requirements for creating change tracking proxies are:
A class must be declared as public
A class must not be sealed
A class must not be abstract
A class must have a public or protected constructor that does not have parameters.
A navigation property that represents the "many" end of a relationship must have public virtual get and set accessors
A navigation property that represents the "many" end of a relationship must be defined as ICollection<T>
Entity Framework represents many-to-many relationships by not having entityset for the joining table in CSDL, instead it manages this through mapping.
Note: Entity framework supports many-to-many relationship only when the joining table does NOT include any columns other than PKs of both the tables
you should have to define navigation property yourself to coupe with this proplem.
this link can be of your help.

Fluent NHibernate Mapping / Parent/Child delete fail using composite key

I'm struggling to understand why when I remove a child Settings object from MyUser.Settings and SAVE MyUser I get SQL errors like below:
Cannot insert the value NULL into column 'MyUserId', table '###.Settings'; column does not allow nulls. UPDATE fails.
The statement has been terminated.
What I would expect to happen is that removing the item from the collection, then saving MyUser causes NHibernate to issue a DELETE command for the given child. However, what it does is UPDATE the relevant row for the Settings object, setting MyUserId to NULL - which isn't allowed as I'm using a Composite Key.
I've tried so many combinations of Inverse() and the various Cascade options but nothing seems to work. I should point out that Adding to the collection works perfectly when I save MyUser.
I'm totally baffled!
Below is pseudo code to try and explain my entities and mappings.
public class SettingType
{
public virtual int SettingTypeId { get; set; }
public virtual string Name { get; set; }
public virtual bool Active { get; set; }
}
public class Setting
{
public virtual MyUser MyUser { get; set; }
public virtual SettingType SettingType { get; set; }
public virtual DateTime Created { get; set; }
}
public class MyUser
{
public virtual int MyUserId { get; set; }
public virtual IList<Setting> Settings { get; set; }
public virtual string Email { get; set; }
public void AddSetting(SettingType settingType, DateTime now)
{
var existing = _settings.SingleOrDefault(s => s.SettingType.SettingTypeId == settingType.SettingTypeId);
if (existing != null)
{
existing.Updated = now;
}
else
{
var setting = new Setting
{
MyUser = this,
SettingType = settingType,
Created = now,
};
_settings.Add(setting);
}
}
public void RemoveSetting(SettingType settingType)
{
var existingPref = _settings.SingleOrDefault(s => s.SettingType.SettingTypeId == settingType.SettingTypeId);
if (existingPref != null)
{
_settings.Remove(existingPref);
}
}
private readonly IList<Setting> _settings = new List<Setting>();
}
And my mappings:
public class SettingTypeMap : IAutoMappingOverride<SettingType>
{
public void Override(AutoMapping<SettingType> mapping)
{
mapping.Table("SettingTypes");
mapping.Id(m => m.SettingTypeId).GeneratedBy.Identity();
mapping.Map(m => m.Name).Not.Nullable().Length(100);
mapping.Map(m => m.Active).Not.Nullable().Default("0");
}
}
public class SettingMap : IAutoMappingOverride<Setting>
{
public void Override(AutoMapping<Setting> mapping)
{
mapping.Table("Settings");
mapping.CompositeId()
.KeyReference(m => m.MyUser)
.KeyReference(m => m.SettingType);
mapping.Map(m => m.Created).Not.Nullable().Default("CURRENT_TIMESTAMP");
mapping.Map(m => m.Updated).Nullable();
}
}
public class MyUserMappingOverride : IAutoMappingOverride<MyUser>
{
public void Override(AutoMapping<MyUser> mapping)
{
mapping.Table("MyUsers");
mapping.Id(m => m.MyUserId).GeneratedBy.Identity();
mapping.Map(m => m.Email).Not.Nullable().Length(200);
mapping.HasMany(m => m.Settings).KeyColumn("MyUserId").Cascade.DeleteOrphan()
.Access.ReadOnlyPropertyThroughCamelCaseField(Prefix.Underscore);
}
}
All using:
FluentNHibernate v1.3.0.733
NHibernate v3.3.1.4000
UPDATE: After a few suggestions I've tried to change the mapping for MyUser entity.
First to this:
mapping.HasMany(m => m.Settings)
.KeyColumn("MyUserId")
.Inverse()
.Cascade.DeleteOrphan()
.Access.ReadOnlyPropertyThroughCamelCaseField(Prefix.Underscore);
This gives the error: Given key was not present in the dictionary
So tried to add second key column:
mapping.HasMany(m => m.Settings)
.KeyColumn("MyUserId")
.KeyColumn("SettingTypeId")
.Inverse()
.Cascade.DeleteOrphan()
.Access.ReadOnlyPropertyThroughCamelCaseField(Prefix.Underscore);
But this then causes odd behaviour when loading the Settings collection from the DB for a given MyUserId. Looking at the nh profiler I see a second SELECT ... FROM Settings but setting the SettingTypeId same as value for MyUserId.
Still totally baffled. Has cost me too much time so going to revert to adding a primary key id field to the Settings entity. Maybe you just can't do what I'm trying using NHibernate. In pure SQL this is simple.
You should use the Inverse mapping
mapping.HasMany(m => m.Settings)
.KeyColumn("MyUserId")
.Inverse()
.Cascade.DeleteOrphan()
.Access.ReadOnlyPropertyThroughCamelCaseField(Prefix.Underscore);
This will allow NHibernate to ask the setting itself to be deleted. Otherwise, NHibernate firstly tries to delete the relation, and would try to delete the entity.
See: 6.4. One-To-Many Associations
Very Important Note: If the column of a
association is declared NOT NULL, NHibernate may cause constraint
violations when it creates or updates the association. To prevent this
problem, you must use a bidirectional association with the many valued
end (the set or bag) marked as inverse="true". See the discussion of
bidirectional associations later in this chapter.

Multiple relations between two tables

I have two classes I want to store in EF Code First. A Building has a maintainer, and a list of people working there. The mainainer doesn't have to work in the building.
At my first attempt I just had
public class Person
{
public virtual Guid Id { get; set; }
public virtual string Name { get; set; }
}
and
public class Building
{
public virtual Guid Id { get; set; }
public virtual List<Person> WorksHere { get; set; }
public virtual Person MaintainedBy { get; set; }
public virtual String Address { get; set; }
}
This gives me two tables with the base properties, and a Building_Id on People, and a MaintainedBy_Id on Buildings.
When I run a test program
using (TestContext tc = new TestContext())
{
Person m1 = tc.persons.Create();
m1.Name = "maintainB1";
m1.Id = Guid.NewGuid();
Person m2 = tc.persons.Create();
m2.Name = "maintainB2";
m2.Id = Guid.NewGuid();
Building b1 = tc.buildings.Create();
b1.Address = "building1";
b1.Id = Guid.NewGuid();
tc.buildings.Add(b1);
Building b2 = tc.buildings.Create();
b2.Address = "building1";
b2.Id = Guid.NewGuid();
tc.buildings.Add(b2);
b1.MaintainedBy = m1;
b2.MaintainedBy = m2;
if (b1.WorksHere == null) b1.WorksHere = new List<Person>();
if (b2.WorksHere == null) b2.WorksHere = new List<Person>();
b1.WorksHere.AddRange(new List<String>() { "e11", "e12", "e13" }.Select(s =>
{
Person p = new Person();
p.Id = Guid.NewGuid();
p.Name = s;
return p;
}));
b2.WorksHere.AddRange(new List<String>() { "e21", "e22", "e23" }.Select(s =>
{
Person p = new Person();
p.Id = Guid.NewGuid();
p.Name = s;
return p;
}));
b1.WorksHere.Add(m2);
b2.WorksHere.Add(m1);
tc.SaveChanges();
}
}
I get an exception: "An error occurred while saving entities that do not expose foreign key properties for their relationships. The EntityEntries property will return null because a single entity cannot be identified as the source of the exception. Handling of exceptions while saving can be made easier by exposing foreign key properties in your entity types. See the InnerException for details."
with innerexception:
"Unable to determine a valid ordering for dependent operations. Dependencies may exist due to foreign key constraints, model requirements, or store-generated values."
I would prefer not to expose any more properties on my Poco's, because, well, they are Poco's, and I have no use for those properties. If I have to to satisfy Code First model generation, than that'll have to do, but if at all possible, I'd like to have that away from my Poco's in a mapping class.
How do I fix this?
So, this issue arises because you have a circular relationship between your entities, which causes EF to give up when trying to resolve all the inserts in a single call to SaveChanges, and raise the exception you are seeing.
To understand why it can't handle this situation, lets think about what happens in the database when trying to save the entities.
Using your code, you can make it run without errors by commenting out the last line before SaveChanges is called, but then person m1 won't be working in building b2, so this is not what you want.
b1.WorksHere.Add(m2);
//b2.WorksHere.Add(m1); <-- When this is removed it works..
tc.SaveChanges();
However, EF is able to run this by creating the following inserts in the database:
Insert person m1. Leave the FK to the buildings table as null, because m1 works nowhere.
Insert building 'b1'. Use id of ´m1´ as the FK, because m1 maintains b1.
Insert person m2. Use id of b1 as the FK, because b1 is where m2 works.
Insert building b2. Use id of m2 as the FK, because m2 maintains b2.
Now it's pretty easy to see why it doesn't work when you include the line that makes m1 work in b2.
In the first insert above, EF isn't able to leave the FK as null, because you are telling it that it needs to point to a building, but that building has not been inserted yet, so it can't create the FK pointing back to it.
That is always a problem in EF when entities have circular dependencies. When both depend on each other, the inserts can't be created in a single commit.
The solution to your problem is simply to make two calls to SaveChanges. If you call it right before making m1 work in b2, and then again after that, you will get the right kind of behavior.
b1.WorksHere.Add(m2);
tc.SaveChanges(); <-- Create inserts. FK in m1 is null because he works nowhere yet.
b2.WorksHere.Add(m1);
tc.SaveChanges(); <-- Updates FK in m1 to point to b2.
Future support in Entity Framework
It seems like this issue will be resolved in a future version of EF.
It is reasonable to expect from a ORM that it should be able to handle inserting a parent, inserting a child and then updating the parent with the newly created child id.
You can read more about it and vote for the feature to be implemented on Microsoft Connect.
There is also some info on the EF CodePlex site.
Try mapping the relationship. In your context which is derived from DbContext, override the OnModelCreating method:
protected override void OnModelCreating(DbModelBuilder mb)
{
mb.Entity<Building>()
.HasMany(m => m.LivesHere)
.WithRequired()
.Map(n => n.MapKey("Home_Id"));
}
I would also try using [Key] to denote the the primary keys in the Entities, but I'm not exactly sure if you have to do that.
Like so:
public class Building
{
[Key]
public virtual Guid Id { get; set; }
public virtual List<Person> WorksHere { get; set; }
public virtual Person MaintainedBy { get; set; }
public virtual String Address { get; set; }
}
I can't try this out right now, but I hope it helps.

Fluent NHibernate M2M Mapping ([One] to [Many - Many] to [One])

i have searched the "whole" internetz for this question, and its a damn hard one to search for as its rather complicated. Try searching for "Fluent NHibernate Many to Many with a bridge table with extra columns" etc...
Okay, to make it easier to explain ill define some tables i can refer to.
Table: User, Table: Function, Table: User_Has_Function.
One User can have many Functions, and a Function can have many Users, this is linked in the bridge table User_Has_Function. The bridge table has extra columns which is only relevant to the relationship.
Well anyways iv found that FNH doesn't have any automatic solution to this, basically you have to use a one to many relation from User to User_Has_Function and many to one from User_Has_Function to Function, hence "[One] to [Many - Many] to [One]".
I have solved it like in this link http://sessionfactory.blogspot.com/2010/12/many-to-many-relationships-with.html just with FNH class mapping instead of xml obviously.
But im not satisfied with the solution, do i really have to do all this manually work to make this function properly? Also as it is now it inserts duplicates in the bridge table.
In my head i'm doing something wrong, cause i cant imagine there is no support for this. Just use SaveAndUpdate(), no duplicates are inserted and when i remove an entity the relation is removed as well, if no relations are left remove the entity itself etc.
Okay here are my entities and mappings, I am VERY new to Fluent NHibernate so don't yell to much if i have done something very wrong. :)
Entities:
public class XUser
{
public virtual int Id { get; set; }
...
public virtual IList<XUserHasXFunction> XUserHasXFunctions { get; set; }
public XUser()
{
XUserHasXFunctions = new List<XUserHasXFunction>();
}
public virtual void AddXFunction(XFunction xFunction, int isActive)
{
var xUserHasXFunction = new XUserHasXFunction()
{
XUser = this,
XFunction = xFunction,
DeployedDate = DateTime.Now
};
XUserHasXFunctions.Add(xUserHasXFunction);
xFunction.XUserHasXFunctions.Add(xUserHasXFunction);
}
public virtual void RemoveXFunction(XFunction xFunction)
{
var xUserHasXFunction = XUserHasXFunctions.Single(x => x.XFunction == xFunction);
XUserHasXFunctions.Remove(xUserHasXFunction);
xFunction.XUserHasXFunctions.Remove(xUserHasXFunction);
}
}
public class XFunction
{
public virtual int Id { get; set; }
...
public virtual IList<XUserHasXFunction> XUserHasXFunctions { get; set; }
public XFunction()
{
XUserHasXFunctions = new List<XUserHasXFunction>();
}
public virtual void AddXUser(XUser xUser, int isActive)
{
var xUserHasXFunction = new XUserHasXFunction()
{
XUser = xUser,
XFunction = this,
DeployedDate = DateTime.Now
};
XUserHasXFunctions.Add(xUserHasXFunction);
xUser.XUserHasXFunctions.Add(xUserHasXFunction);
}
public virtual void RemoveXUser(XUser xUser)
{
var xUserHasXFunction = XUserHasXFunctions.Single(x => x.XUser == xUser);
XUserHasXFunctions.Remove(xUserHasXFunction);
xUser.XUserHasXFunctions.Remove(xUserHasXFunction);
}
}
public class XUserHasXFunction
{
public virtual int Id { get; set; }
public virtual XUser XUser { get; set; }
public virtual XFunction XFunction { get; set; }
public virtual DateTime DeployedDate { get; set; }
}
Mappings:
public class XUserMap : ClassMap<XUser>
{
public XUserMap()
{
Id(x => x.Id, "ID").GeneratedBy.Sequence("SEQ").Column("ID");
Table("XUSER");
...
HasMany(x => x.XUserHasXFunctions).Cascade.All();
}
}
public class XFunctionMap : ClassMap<XFunction>
{
public XFunctionMap()
{
Id(x => x.Id, "ID").GeneratedBy.Sequence("SEQ").Column("ID");
Table("XFUNCTION");
...
HasMany(x => x.XUserHasXFunctions).Cascade.All();
}
}
public class XUserHasXFunctionMap : ClassMap<XUserHasXFunction>
{
public XUserHasXFunctionMap()
{
Id(x => x.Id, "ID").GeneratedBy.Sequence("SEQ").Column("ID");
Table("USER_HAS_FUNCTION");
Map(x => x.DeployedDate, "DEPLOYED_DATE");
References(x => x.XUser).ForeignKey("XUSER_ID").Cascade.SaveUpdate();
References(x => x.XFunction).ForeignKey("XFUNCTION_ID").Cascade.SaveUpdate();
}
}
I don't understand the "do i really have to do all this manual work" part. What "all this manual work"? There is nothing special there. The mapping is simple and the c# code doesn't have to do anything with persistency, it's plain old OO design.
If you get duplicated rows, there is something wrong with your mapping. It might be because of a inverse collection which had not been mapped as inverse.
If you don't need to navigate from Function to User, it's very easy. Either map the relation as entity, as described in the blog, or even easier, map it as a composite element.
(Sorry, I don't know Fluent)
<bag name="Functions" table="User_Has_Function">
<key column="UserId" />
<composite-element>
<many-to-one class="Function"/>
</composite-element>
</bag>
Edit:
From the comments:
The manual work I am talking about is the manual getting and checking
to remove and add relations from a user or function.
Are you talking about the required Add and Remove methods, which maintain the consistency of the relations? This is plain OO design. If you hadn't NHibernate, you would have to write it exactly the same (given the same class model).
delete a user from a function make it cascade all the way to user and
so forth...
No. Delete-cascading happens when an object is deleted. When you delete a user, you should cascade the user_has_function. From there, you may or may not cascade the functions. The same in the other direction. There is also the concept of "cascade-all-delete-orphans". It means that additionally to regular cascading, an object is deleted automatically when it is removed from the collection. This is not cascading. It is a kind of very basic garbage collection. If you want to make use of this in your case, you should not apply it to both the user->user_has_function collection and the function->user_has_function collection, because it would try to delete the object twice.
Don't forget to map both collections inverse. If you don't, you may get duplicated entries.
Make sure that the three mappings (the user->user_has_function collection, the function->user_has_function and the user_has_function class mapping) are using the same table name and foreign key names.
You don't need to mess around with composite keys.
I ended up doing something similar a while ago with user, group, user_group and ended up having to use a hacky method of having both objects exist on both sides and also manually choose between save or update.
I don't think there is a NICE way to do what you want, and I agree it is something that from a database point of view is fairly logical to do, but from a modelling point of view is a pain.
As I also assume you are having to use a composite key for your user_has_function table to make sure that you can have multiple functions for multiple users. Which I think most people try to avoid and end up using surrogate keys or some other approach.
I know this isn't an answer, but I never found a real answer to the same question when I posted it.
Here is a similar question I posted a while back:
Nhibernate composite key question
I ended up using an ISet instead of having the relations in ILists. ISet does not allow duplicates, but IList does. To use ISet you have to override the Equals and GetHashCode methods for the object stored in the ISet.
I cascade from XUser and XFunction and not the other way around, ended up that every record in all 3 tables were deleted when i deleted one entity because of cascading.
Here is how i solved it.
Entities:
public class XUser
{
public virtual int Id { get; set; }
...
public virtual ISet<XUserHasXFunction> XUserHasXFunctions { get; set; }
public XUser()
{
XUserHasXFunctions = new HashedSet<XUserHasXFunction>();
}
public virtual void AddXFunction(XFunction xFunction, int isActive)
{
var xUserHasXFunction = new XUserHasXFunction()
{
XUser = this,
XFunction = xFunction,
IsActive = isActive,
DeployedDate = DateTime.Now
};
if (XUserHasXFunctions.Contains(xUserHasXFunction) && xFunction.XUserHasXFunctions.Contains(xUserHasXFunction))
{
return;
}
XUserHasXFunctions.Add(xUserHasXFunction);
xFunction.XUserHasXFunctions.Add(xUserHasXFunction);
}
public virtual void RemoveXFunction(XFunction xFunction)
{
var xUserHasXFunction = XUserHasXFunctions.Single(x => x.XFunction == xFunction);
XUserHasXFunctions.Remove(xUserHasXFunction);
xFunction.XUserHasXFunctions.Remove(xUserHasXFunction);
}
}
public class XFunction
{
public virtual int Id { get; set; }
...
public virtual ISet<XUserHasXFunction> XUserHasXFunctions { get; set; }
public XFunction()
{
XUserHasXFunctions = new HashedSet<XUserHasXFunction>();
}
public virtual void AddXUser(XUser xUser, int isActive)
{
var xUserHasXFunction = new XUserHasXFunction()
{
XUser = xUser,
XFunction = this,
IsActive = isActive,
DeployedDate = DateTime.Now
};
if (XUserHasXFunctions.Contains(xUserHasXFunction) && xUser.XUserHasXFunctions.Contains(xUserHasXFunction))
{
return;
}
XUserHasXFunctions.Add(xUserHasXFunction);
xUser.XUserHasXFunctions.Add(xUserHasXFunction);
}
public virtual void RemoveXUser(XUser xUser)
{
var xUserHasXFunction = XUserHasXFunctions.Single(x => x.XUser == xUser);
XUserHasXFunctions.Remove(xUserHasXFunction);
xUser.XUserHasXFunctions.Remove(xUserHasXFunction);
}
}
public class XUserHasXFunction
{
public virtual int Id { get; set; }
...
public virtual DateTime DeployedDate { get; set; }
public override bool Equals(object obj)
{
if (obj == null)
return false;
var t = obj as XUserHasXFunction;
if (t == null)
return false;
return XUser == t.XUser && XFunction == t.XFunction;
}
public override int GetHashCode()
{
return (XUser.Id + "|" + XFunction.Id).GetHashCode();
}
}
Mappings:
public class XUserMap : ClassMap<XUser>
{
public XUserMap()
{
Id(x => x.Id, "ID").GeneratedBy.Sequence("SEQ").Column("ID");
Table("XUSER");
...
HasMany(x => x.XUserHasXFunctions).KeyColumn("XUSER_ID").Cascade.All();
}
}
public class XFunctionMap : ClassMap<XFunction>
{
public XFunctionMap()
{
Id(x => x.Id, "ID").GeneratedBy.Sequence("SEQ").Column("ID");
Table("XFUNCTION");
...
HasMany(x => x.XUserHasXFunctions)KeyColumn("XFUNCTION_ID").Cascade.All();
}
}
public class XUserHasXFunctionMap : ClassMap<XUserHasXFunction>
{
public XUserHasXFunctionMap()
{
Id(x => x.Id, "ID").GeneratedBy.Sequence("SEQ").Column("ID");
Table("XUSER_HAS_XFUNCTION");
...
Map(x => x.DeployedDate, "DEPLOYED_DATE");
References(x => x.XUser).Column("XUSER_ID");
References(x => x.XFunction).Column("XFUNCTION_ID");
}
}
Usage:
To add relations.
xFunction.AddXUser(xUser, isActive); //visa versa if you like to add a function to a user...
dao.Store(xFunction); //to actually add the relation in the db
now to remove relation
xFunction.RemoveXUser(xUser); //Realtion is removed but neither of the objects xFunction or xUser
dao.Store(xFunction); //...same
to remove a user and its relations.
dao.delete(xUser); //but the xFunction object it was connected to is not removed
//if you want the xFunction object to be removed you have to do that manually.

One-to-one Mapping issue with NHibernate/Fluent: Foreign Key not updateing

Summary: Parent and Child class. One to one relationship between the Parent and Child. Parent has a FK property which references the primary key of the Child. Code as follows:
public class NHTestParent
{
public virtual Guid NHTestParentId { get; set; }
public virtual Guid ChildId
{
get
{
return ChildRef.NHTestChildId;
}
set { }
}
public virtual string ParentName { get; set; }
protected NHTestChild _childRef;
public virtual NHTestChild ChildRef
{
get
{
if (_childRef == null)
_childRef = new NHTestChild();
return _childRef;
}
set
{
_childRef = value;
}
}
}
public class NHTestChild
{
public virtual Guid NHTestChildId { get; set; }
public virtual string ChildName { get; set; }
}
With the following Fluent mappings:
Parent Mapping
Id(x => x.NHTestParentId);
Map(x => x.ParentName);
Map(x => x.ChildId);
References(x => x.ChildRef, "ChildId").Cascade.All();
Child Mapping:
Id(x => x.NHTestChildId);
Map(x => x.ChildName);
If I do something like (pseudo code) ...
HTestParent parent = new NHTestParent();
parent.ParentName = "Parent 1";
parent.ChildRef.ChildName = "Child 1";
nhibernateSession.SaveOrUpdate(aParent);
Commit;
... I get an error: "Invalid index 3 for this SqlParameterCollection with Count=3"
If I change the parent 'References' line as follows (i.e. provide the name of the child property I'm pointing at):
References(x => x.ChildRef, "ChildId").PropertyRef("NHTestChildId").Cascade.All();
I get the error: "Unable to resolve property: NHTestChildId"
So, I tried the 'HasOne()' reference setting, as follows:
HasOne<NHTestChild>(x => x.ChildRef).ForeignKey("ChildId").Cascade.All().Fetch.Join();
In this arrangement the save works (and data in db is as wanted), but loading fails to find the child entity. Inspecting the SQL Nhibernate produces shows me that NHibernate is assuming the Primary key of the parent is the link to the child (i.e. load join condition is "parent.NHTestParentId = child.NHTestChildId). The 'ForeignKey' I specified appears to be ignored - if fact I can set any value (even a non-existance field) and no error occurs - the join just always fails and no child is returned.
I've tried a number of slight variations on the above. It seems like it should be a simple thing to achieve. Any ideas?
you are mapping the same column twice, and that is not allowed. Remove the following from the parent class
Map(x => x.ChildId);
see also
IndexOutOfRangeException Deep in the bowels of NHibernate

Categories

Resources