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 => {});
Related
I have a Product object which references an ordered list of Specification objects. When the Product gets updated, the associated list of Specifications are .Clear()'d and rebuilt (not new'd.)
The problem with Cascade.All() is that when the Product is updated, it creates 20 new Specification rows in the database, abandoning the 20 old ones. Cascase.AllDeleteOrphans() throws an error:
Additional information: A collection with cascade="all-delete-orphan" was no longer referenced by the owning entity instance: Invax.Core.Models.Product.Specifications
Although this is what I want to happen.. It's true, the collection of Specifications are no longer referenced by the Product -- so delete these, right? Well, I have tried Inverse() to allow the Specification objects themselves handle the relationship but it didn't work either. Here is my current mappings which throw the aforementioned error.
Product Mapping:
public class ProductMap : ClassMap<Product>
{
public ProductMap ()
{
Id(x => x.Id);
Map(x => x.CatalogId).Not.Nullable();
Map(x => x.Name).Not.Nullable();
Map(x => x.UrlSlug).Not.Nullable();
Map(x => x.ShortDescription).Not.Nullable();
Map(x => x.Description).Not.Nullable();
Map(x => x.ImageFileName);
HasMany(x => x.Specifications).Cascade.AllDeleteOrphan().KeyColumn("ProductId");
References(x => x.Category).Column("Category");
}
}
Specification Mapping:
class SpecificationMap : ClassMap<Specification>
{
public SpecificationMap()
{
Id(x => x.Id);
Map(x => x.Name);
Map(x => x.Value);
References(x => x.Product).Column("ProductReferenceId").Cascade.All();
}
}
I am open to any solution to this problem, including a more robust database design or data organization. I have to stick with Lists because the Specifications are ordered in a specific way.
Apparently too late for you, but if another one has this problem.
You should set the Cascade to Cascade.AllDeleteOrphan() because
Cascade.All() does not delete unreferenced child objects.
Ayende: Difference between cascades
The Inverse() solves the problem. Eg:
HasMany(x => x.Specifications).Cascade.AllDeleteOrphan().Inverse();
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;
}
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.
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.
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.