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();
Related
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;
}
having a little trouble with a mapping for the following table setup currently:
Shop
[1] [1]
/ \
[n] [n]
Category-[m]---[n]-Article
The behaviour should be the following :
1 - when deleting a shop, all Articles and Categories Should be deleted
2 - when deleting a Category, related Articles should be unassigned but not deleted
3 - when deleting an Article, related Categories should be unassigned but not deleted
Here's the current mapping:
public class ShopMap: ClassMap<Shop>
{
public ShopMap()
{
this.Table("shop");
Id(x => x.Id).Column("id").GeneratedBy.Native();
Map(x => x.Name).Column("name");
HasMany(x => x.Categories).Cascade.AllDeleteOrphan;
HasMany(x => x.Articles).Cascade.AllDeleteOrphan;
}
}
public class CategoryMap: ClassMap<Category>
{
public CategoryMap()
{
this.Table("category");
Id(x => x.Id).Column("id").GeneratedBy.Native();
Map(x => x.Name).Column("name");
References(x => x.Shop);
HasManyToMany(x => x.Articles).Cascade.AllDeleteOrphan()
.Table("article_category")
.ChildKeyColumn("article_id")
.ParentKeyColumn("category_id")
.Inverse();
}
}
public class ArticleMap: ClassMap<Article>
{
public ArticleMap()
{
this.Table("article");
Id(x => x.Id).Column("id").GeneratedBy.Native();
Map(x => x.Name).Column("name");
References(x => x.Shop);
HasManyToMany(x => x.Categories).Cascade.All()
.Table("article_category")
.ParentKeyColumn("article_id")
.ChildKeyColumn("category_id");
}
}
When deleting a Category (Session.Delete()), NH tries to delete the related Articles as well. Changing the Cascade-Mode to SaveUpdate will fix this, but will leave the entries in the link table *article_category*. Summing up : Cascade.SaveUpdate is too lazy, Cascade.All is too eager.
I tried everything that came to my mind in the mappings, but couldn't find a correct way to map this (rather simple schema).
Any ideas on how to (fluently) map this are greatly appreciated!
Thanks in advance
Sebi
The entries are left in the link table because Category.Articles is defined as the inverse side of the relationship. You need to remove the Category from Article.Categories before deleting it in order for the link record to be removed.
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.
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 => {});