Problems with updating records through Many-To-Many mappings - fluent-nhibernate

I have a problem where I have a many-to-many mappings in my table structure creating headaches when trying to edit a simple record.
Example layout of where I am having problems:
Facilities Many-to-One Locations
Facilities One-to-Many Users
Users Many-to-Many Locations
Users One-to-Many PreviousPasswords
If I make a change to a Facilities record (Change the name field) I get the following error upon save:
collection [Users.PreviousPasswords] was not processed by flush()
Mapping looks like:
public FacilitiesMap()
{
Table("Facilities");
Id(x => x.ID);
Map(x => x.Name);
HasMany(x => x.Users).KeyColumn("FacilitiesID").Cascade.AllDeleteOrphan().Inverse();
HasMany(x => x.Locations).KeyColumn("FacilitiesID").Cascade.AllDeleteOrphan().Inverse();
}
public UsersMap()
{
Table("Users");
Id(x => x.ID);
Map(x => x.FirstName);
Map(x => x.LastName);
References(x => x.Facilities, "FacilitiesID").ForeignKey("ID");
HasMany(x => x.PreviousPasswords).KeyColumn("UsersID").Cascade.AllDeleteOrphan().Inverse();
HasManyToMany<Locations>(x => x.Locations)
.Schema("Members")
.Table("UsersToLocations")
.ParentKeyColumn("UsersID")
.ChildKeyColumn("LocationsID")
.LazyLoad()
.Cascade.SaveUpdate().Inverse();
}
public LocationsMap()
{
Table("Locations");
Id(x => x.ID);
Map(x => x.Name);
References(x => x.Facilities, "FacilitiesID").ForeignKey("ID");
HasMany(x => x.Patients).KeyColumn("LocationsID").Cascade.AllDeleteOrphan().Inverse();
HasManyToMany<Users>(x => x.Users)
.Schema("Members")
.Table("UsersToLocations")
.ParentKeyColumn("LocationsID")
.ChildKeyColumn("UsersID")
.Cascade.SaveUpdate();
}
public PreviousPasswordsMap()
{
Table("PreviousPasswords");
Id(x => x.ID);
Map(x => x.Password);
Map(x => x.DateTime);
References(x => x.Users, "UsersID").ForeignKey("ID");
}
The only way I can do a successful update to the Facilities record is if I use the following function to get the record before changing and saving it:
public Facilities GetFacility(int id)
{
return FluentSessionManager.GetSession()
.CreateCriteria<Facilities>()
.Add(Expression.Eq("ID", id))
.SetFetchMode("Users", FetchMode.Eager)
.SetFetchMode("Locations", FetchMode.Eager)
.UniqueResult<Facilities>();
}
The problem with this method is that where there are 10,000 users it takes a long time to process this query. Or even worse, if we have 100 location as well, then it takes around 2 minutes to get the one Facilities record to edit.
I am sure there is some kind of issue in the Mapping. Not sure how to fix or even where to start. Any help with this would be greatly appreciated.
Thanks in advance.

Do you really need all the users for the facility? When you only add users you can use
HasMany(x => x.Users).ExtraLazyLoad();
and to improve the query when really all subcollections are needed
public Facilities GetFacility(int id)
{
var session = FluentSessionManager.GetSession();
// ignore the result, we only want to cache the results in the session
session.CreateCriteria<Facilities>()
.Add(Expression.Eq("ID", id))
.SetFetchMode("Users", FetchMode.Eager)
.Future<Facilities>();
return session.CreateCriteria<Facilities>()
.Add(Expression.Eq("ID", id))
.SetFetchMode("Locations", FetchMode.Eager)
.FutureValue<Facilities>().Value;
}

Related

Why NHibernate UPDATE reference entity?

I have a test mothod like below:
public void Add_Update_Delete_a_Registration() {
ISessionFactory sessionFactory = SessionFactory.GetSessionFactory(connString);
using (ISession session = sessionFactory.OpenSession()) {
Course course = new CourseRepository(session).GetById(12019);
Registration entity = new Registration();
entity.Course = course; //Assign the Course to register
//assign other entity members
//...
RegistrationRepository repository = new RegistrationRepository(session);
repository.Add(entity);
}
The Registration entity was inserted correctly.
The problem is, NHibernate also made an UPDATE database call to update the Course entity which is not changed at all in the test method. What could be the possible reasons?
The mappings:
public class CourseMap : ClassMap<Course>{
public CourseMap() {
Id(x => x.Id).GeneratedBy.HiLo("100");
Map(x => x.WeekDay)
.Not.Nullable()
.CustomType<int>(); //WeekDay is type of DayOfWeek enums
References(x => x.Room)
.Not.Nullable();
Map(x => x.StartTime)
.Not.Nullable();
Map(x => x.EndTime)
.Not.Nullable();
Map(x => x.CreatedTime)
.Not.Nullable();
Map(x => x.UpdatedTime);
Map(x => x.CreatedBy)
.Not.Nullable();
Map(x => x.UpdatedBy);
Version(x => x.Version).Column("RCB_Version")
.CustomSqlType("timestamp")
.Generated.Always()
.Not.Nullable();
}
public class RegistrationMap : ClassMap<Registration>{
public RegistrationMap() {
Id(x => x.Id)
.GeneratedBy.HiLo("100");
Map(x => x.OwnerWindowsAccount)
.Not.Nullable()
.Length(50);
References(x => x.Course)
.Not.Nullable();
Map(x => x.TrainingDate)
.Not.Nullable();
Map(x => x.RegistreeName)
.Not.Nullable()
.Length(50);
Map(x => x.RegistreeWindowsAccount)
.Nullable()
.Length(50);
Map(x => x.CreatedTime)
.Not.Nullable();
Map(x => x.UpdatedTime);
Map(x => x.CreatedBy)
.Not.Nullable();
Map(x => x.UpdatedBy);
Version(x => x.Version)
.CustomSqlType("timestamp")
.Generated.Always()
.Not.Nullable();
}
}
Much appreciated!
Leo
You have version column specified. Which means that any property change (even collection) trigger version update.
In order to prevent a certain property/collection to change version, optimistic-lock="false" property should be set in xml mapping.
Though not sure how it'd be in fluent syntax. Probably .OptimisticLock.False() or something.
There are some various issues that can cause this. I often get it when I have mapped my enums wrong or is using the inverse="true|false" the wrong way.

NHibernate Audit Interceptor - current and previous values match when auditing a collection

I'm auditing certain values using a NHibernate Audit Inteceptor - I have inherited from the EmptyInteceptor and overridden the OnFlushDirty
public override bool OnFlushDirty(object entity, object id, object[] currentState, object[] previousState, string[] propertyNames, IType[] types)
{
For the most part the currentState and previousState objects are what I'd expect - but I've mapped a collection and dosnt work - the previous and current both show the same collection values.
Can anyone assist please?
Heres the mapping I'm using ...
public SalesChannelMap()
{
Id(x => x.ID).GeneratedBy.Custom<StringTableHiLoGenerator>(a => a.AddParam(NHibernateConstants.MaxLo, Nexus3General.HiLoGeneratorMaxLoSize.ToString()));
Version(x => x.Version);
Map(x => x.Name).Unique();
Map(x => x.Exclusive);
Map(x => x.Active);
Map(x => x.Visible);
Map(x => x.VehicleType);
Map(x => x.PriceAdjustment);
Map(x => x.Deleted);
Map(x => x.FactoryFlag);
Map(x => x.Initials);
HasMany(x => x.VehicleIDs).AsBag().Element("VehicleID", m => m.Type<string>()).Table("SalesChannelVehicleLinker");
}
Turns out there's other events to override concerned specifically with collections.

Fluent Nhibernate Mapping Problem

I'm fairly new to nhibernate and fluent nhibernate, I have an issue with associating a collection to a query. In the database I have a well and have 4 associated AFEs. The problem I'm having is that the collection of AFEs is not populating correctly. No matter what I do, I get 4 AFEs in the collection but they are all the same object. Is there anything obvious that I am doing wrong.
Thanks
PS. I don't have change access to the database that I'm hitting so I can't change the db to make this a true FK.
Parent
public WellHeaderMap()
{
Table("Well");
Id(x => x.PropertyNumber, "WELL_NUMBER");
Map(x => x.PropertyID, "PROPERTY_ID");
Map(x => x.Name, "WELL_NAME");
//AFEs is a IList<AFE>
HasMany(x => x.AFEs).Inverse().KeyColumn("Property_ID").PropertyRef("PropertyID").Fetch.Join();
}
Collection
public AFEMap()
{
Table("AFE");
Id(x => x.PropertyID, "PROPERTY_ID");
Map(x => x.AFETypeID, "AFE_TYPE_CODE");
Map(x => x.AFENumber, "AFE_NUMBER");
Map(x => x.IsDeleted, "DELETED_IND");
}
Query
var wellSearchCriteria = _session.CreateCriteria<WellHeader>()
.CreateAlias("AFEs", "afe")
.Add(Restrictions.Eq("PropertyNumber", id.ToString()))
//.Add(Expression.Eq("afe.AFETypeID", "01"))
//.Add(Expression.Eq("afe.IsDeleted", "N"));
I think you might have your WellHeader Id wrong, currently you have:
Id(x => x.PropertyNumber, "WELL_NUMBER");
Map(x => x.PropertyID, "PROPERTY_ID");
Probably should be:
Id(x => x.PropertyID, "PROPERTY_ID");
Map(x => x.PropertyNumber, "WELL_NUMBER");
The PropertyNumber and PropertyId were switched. However without seeing your schema, its hard to tell.

Fluent NHibernate HasManyToMany() Mapping

I am having a problem in Fluent NHibernate example utilizing the Many-to-Many relationships. I tried to find out examples on a similar case, and I found tons, but I'm still having the same problem.
When running the test project, the following exception is thrown:
NHibernate.PropertyAccessException: Exception occurred getter of project.Entities.User.UserName ---> System.Reflection.TargetException: Object does not
match target type.
This is an image of the tables:
and the code
public UsersMap()
{
this.Table("Users");
Id(x => x.UserName).Column("Username").GeneratedBy.Assigned();
Map(x => x.FirstName);
Map(x => x.LastName);
Map(x => x.Password);
Map(x =>x.EMail);
Map(x => x.Title);
Map(x => x.Division);
HasManyToMany<User>(x => x.Roles)
.Table("UserInRoles").ParentKeyColumn("Username")
.ChildKeyColumn("Usernamepk")
.Cascade.SaveUpdate().LazyLoad();
}
public RolesMap()
{
this.Table("Roles");
Id(x => x.ID).GeneratedBy.Assigned().Column("ID");
Map(x => x.RoleName).Length(50);
HasManyToMany<User>(x => x.Users)
.Table("UserInRoles").ParentKeyColumn("ID")
.ChildKeyColumn("RoleIdpk").Cascade.SaveUpdate().LazyLoad();
}
here is the code, most examples on the web and the Fluent Nhibernate mappings page are written in the same way, so any ideas ?
Regarding to code I am using in my project I would define your manyTomany relations this way:
public UsersMap()
{
...
HasManyToMany(x => x.Roles)
.WithTableName("UserInRoles")
.WithParentKeyColumn("Usernamepk")
.WithChildKeyColumn("RoleIdpk");
}
public RolesMap()
{
...
HasManyToMany(x => x.Users)
.WithTableName("UserInRoles")
.WithParentKeyColumn("RoleIdpk")
.WithChildKeyColumn("Usernamepk");
}
Such a definitions works for me.
Check this first then decorate with LazyLoading and some other properties.

Fluent nhibernate table-per-hierarchy mapping

I have a simple scenario where I have an entity Action (this is a workflow style application) that has a DueDate calculated property.
Now id like to introduce a SlidingAction, whose only difference (at this stage) is to override the DueDate calculation, as such has none of its own mapping.
Im having difficulty mapping this scenario since Fluent Nhibernate seems to be forcing me to map 'something' on the subclass.
Could someone shed some light?
Cheers,
Byron
public class ActionMap : ClassMap<Action>
{
public ActionMap()
{
WithTable("Actions");
Id(x => x.ID);
Map(x => x.Description);
Map(x => x.TimeLine);
Map(x => x.Template);
Map(x => x.StageOrder);
Map(x => x.CorrespondenceType).CustomTypeIs(typeof (ActionCorrespondenceTypeEnumType));
References(x => x.Matter).FetchType.Join();
HasMany(x => x.FileNotes).Cascade.SaveUpdate();
DiscriminateSubClassesOnColumn("Type")
.SubClass<SlidingAction>(/*its forcing me to map something here*/);
}
}
Just put an empty lambda in, c => {}.
.SubClass<SlidingAction>(c => {});