I have a many to many relationship:
Product has many Categories and Category has Many Products.
Say I have
Shopping Category
Food Category
Product A - Shopping Category, Food Category
Product B - Shopping Category
Now I delete Shopping Category. I want the Product A reference to be removed from Shopping Category and I want Product B to be removed completely.
I would end up with:
Product A - Food Category.
How do I do this in nhibernate (I am using fluent nhibernate).
I tried to use Cascade DeleteOrphan and AllDeleteOrphan but when I do that and delete Shopping both Product A and B get deleted.
public class CategoryMapping : ClassMap<Category>
{
public CategoryMapping()
{
Id(x => x.Id).GeneratedBy.GuidComb();
Map(x => x.Name).Not.Nullable().NvarcharWithMaxSize();
HasManyToMany(x => x.Products).Cascade.DeleteOrphan();
}
}
public class ProductMapping : ClassMap<Product>
{
public ProductMapping()
{
Id(x => x.Id).GeneratedBy.GuidComb();
Map(x => x.Name).Not.Nullable().NvarcharWithMaxSize();
HasManyToMany(x => x.Categories);
}
}
unitOfWork.BeginTransaction();
Category category =session.Load<Category>(id);
session.Delete(category);
unitOfWork.Commit();
I don't think this can be handled by mapping with existing data structure. I think you would need to write some manual code (*) or change data structure.
(*) Not 100% sure it works though...
unitOfWork.BeginTransaction();
Category category =session.Load<Category>(id);
var productsDel = category.Products.Where(p => p.Categories.Count == 1);
productsDel.ForEach(p => session.Delete(p));
session.Delete(category);
unitOfWork.Commit();
Other:
I'm also thinking about adding mapping for your cross-ref tables. Then you should be able to configure mapping so it will delete only records from that cross-ref table. You will need to verify if there are products without references and delete them periodically. (some periodic clean-up code, like running some stored procedure). I know this solutions smells bad :) There are still triggers and other SQL Server stuff... not good solutions anyway, but solutions.
If you just want to remove the association between the two use Cascade.SaveUpdate()
Then just remove the entity from the collection and commit the transaction if you are using transactions if not you will need to do a Session.Flush
Related
I have Customer and Profile classes, where one Customer can have many Profiles.
I am using following NHibernate override classes with them:
public void Override(AutoMapping<Customer> mapping)
{
mapping.Table("[Customer]");
mapping.Id(x => x.Id, "Id").GeneratedBy.Identity();
mapping.HasMany(x => x.Profiles).Cascade.All().Inverse();
mapping.Map(x => x.FirstName, "FirstName");
mapping.Map(x => x.LastName, "LastName");
mapping.Map(x => x.Email, "Email");
}
public void Override(AutoMapping<Profile> mapping)
{
mapping.Table("[Profile]");
mapping.Id(x => x.Id, "Id").GeneratedBy.Identity();
mapping.References(x => x.Customer, "Customer_Id").Cascade.None();
mapping.Map(x => x.FacebookProfileLink, "FacebookProfileLink");
mapping.Map(x => x.ContactPhone, "ContactPhone");
}
I am getting following error while inserting Profile object:
Cannot insert the value NULL into column 'Customer_Id', table 'dbo.Profile'; column does not allow nulls. INSERT fails.\r\nThe statement has been terminated.
My intence is to insert Customer object before Profile that needs a reference to Customer
object. That's why I'm using Inverse attribute. Unfortunately it doesn't work. Any help on this ? Thank you.
One thing that NHibernate does as a good ORM is to save everything in the relationships so you don't have to save things separately. I think your issue might but in what you are doing when you say 'insert Customer object before Profile that needs a reference to Customer'.
The reality is that you should create the customer, with the profiles needed to be associated to it, and then just save the customer. NHibernate will save all the entities in the right order to make sure the relationships are preserved. Try doing that, first create or retrieve the customer entity, then add/remove the profiles, and then save the customer. Profiles would be saved because of the cascade option you have specified in the mapping.
Hope that helps!
I found out solution that works for me.
I am saving Customer entity without reference to it's Profiles. Afterwards I'm saving Profile entity.
Works for me even better than solution i wanted to achieve before, as I can check on success of Customer insert and then decide what to do next (save Profile as well or do some validation error).
Thank you all for your answers.
I need some help.
I'm just starting out with NHibernate and I'm using Fluent for mappings. Everything seemed to work fine until today.
Here is the story:
I have two tables in my db: Store and WorkDay
The first table contains info about the store, and the WorkDay table contains info about the days of week and start/end time when the store is open.
Store contains a Guid StoreID PK column that is referenced in the WorkDay table.
So I have a mapping file for Store where I have a HasMany association with the WorkDay table, and a corresponding POCO for Store.
Now, when I fill in all the necessary data and try to persist it to database, I get an exception telling me that the insert into table WorkDay failed because the StoreID had null value and the table constraint doesn't allow nulls for that column (which is, of course, expected behavior).
I understand the reason for this exception, but I don't know how to solve it.
The reason why the insert fails is because the StoreID gets generated upon insert, but the [b]WorkDay[/b] collection gets saved first, in the time when the StoreID hasn't yet been generated!
So, how do I force NHibernate to generate this ID to pass it to dependent tables? Or is there another solution for this?
Thank you!
Here's the code for StoreMap
public class StoreMap : ClassMap<Store> {
public StoreMap() {
Id(x => x.StoreID)
.GeneratedBy.GuidComb();
Map(x => x.City);
Map(x => x.Description);
Map(x => x.Email);
Map(x => x.Fax);
Map(x => x.ImageData).CustomType("BinaryBlob");
Map(x => x.ImageMimeType);
Map(x => x.Name);
Map(x => x.Phone);
Map(x => x.Street);
Map(x => x.Zip);
HasMany(x => x.WorkDays)
.Inverse().KeyColumn("StoreID").ForeignKeyCascadeOnDelete()
.Cascade.All();
}
}
and this is for the WorkDayMap
public class WorkDayMap : ClassMap<WorkDay>{
public WorkDayMap() {
Id(x => x.WorkDayID)
.GeneratedBy.Identity();
Map(x => x.TimeOpen);
Map(x => x.TimeClose);
References(x => x.Store).Column("StoreID");
References(x => x.Day).Column("DayID");
}
}
NHibernate shouldn't insert the WorkDay first, so there must be an error in your code. Make sure you do all of the following:
Add all WorkDay objects to the WorkDays collection.
Set the Store property on all WorkDay objects to the parent object.
Call session.Save() for the Store but not for the WorkDay objects.
edit: You should also note that ForeignKeyCascadeOnDelete() won't change anything at runtime. This is just an attribute for the hbm2ddl tool. If you want NHibernate to delete removed entries, use Cascade.AllDeleteOrphan().
It probably inserts the Workday before the Store because the first has an identity generator. This forces NH to execute an INSERT statement to generate the ID. guid.comb is generated in memory, NH doesn't need the database.
NH tries to access the db at the latest possible point in time, to avoid unnecessary updates and to make use of batches. Workday is inserted when you call session.Save, Store is inserted when it flushes the session next time (eg. on commit).
You shouldn't use the identity generator (unless you are forced to use it). It is bad for performance anyway.
If it still doesn't work, you probably need to remove the not-null constraint on the foreign key. NH can't resolve it every time. There is some smart algorithm which is also able to cope with recursive references. But there are sometimes cases where it sets a foreign key to null and updates it later even if there would be a solution to avoid it.
Imagine that I have a Parent/Child relationship managed by NHibernate.
I'm receiving a Parent object from an MVC postback that edits its properties; I want to save just the parent to the database without having to load the children from the database.
At the time of the save, Parent has a Children property that is null (because it hasn't been loaded; there are, however, valid Children in the database for that parent).
When I save a modified Parent (ID=100), NHibernate is issuing a "SET Child.ParentId = NULL WHERE Child.ParentId = 100" statement. I don't want this to happen because there could be valid children. I shouldn't have to load them from the database before the save just to prevent them from being orphaned.
The fluent mappings look like this (true entity names genericized for this post):
public ParentMapping()
{
Table("Parent");
Id(x => x.Id).Column("Id").GeneratedBy.Identity();
Map(x => x.ParentProperty1).Column("ParentProperty1").Not.Nullable();
HasMany(x => x.Children).Cascade.None();
}
public ChildMapping()
{
Table("Children");
Id(x => x.Id).Column("Id").GeneratedBy.Identity();
Map(x => x.ChildProperty1).Column("ChildProperty1").Not.Nullable();
References(x => x.Parent).Column("Parent_Id").Not.Nullable().Fetch.Select();
}
To summarize, I want to save an updated Parent instance that was retrieved from an earlier ISession (and passed to a browser and back through MVC model minding); its Children property is null, but in reality it's got plenty of children in the database. I don't want NHibernate to issue any modifying statements to the Children table at all.
I've experimented with Cascade.None() and LazyLoad() in the hopes that this nudges NHibernate to behave differently, but no luck.
Any insight would be appreciated. Thanks!
Jeff
You must specify Inverse() on the has many to tell nhibernate not to worry about this side of the collection
I have a one-to-many relationship in data model from A to B. But in my domain API I do not expose "B"s on A (since we will never navigate from from A to B), but have a reference from B to A. Now I want to be able to delete all "B"s when A is deleted. Is it possible? Right now NH is trying first to set FK to null, which I don't want, and cannot since column is not nullable.
A = SupplierType
B = BaseProductCoInsurance
public BaseProductCoInsuranceMap()
{
Table("BaseProductCoInsurance");
Id(x => x.Id, "BaseProductCoInsuranceId");
Map(x => x.CoInsurancePercent).Column("CoInsrPrcnt");
References(x => x.BaseProduct, "BaseProductId");
References(x => x.PolicySupplierType, "PlcySupplierTypeID");
References(x => x.InsuredType, "InsuredTypeCode");
}
If you need to be able to cascade delete then you need to let NHibernate know about the relationship. That said you don't need to make the relationship accessible to others. You could of course have a private collection that only NH knows about.
If you make the relationship lazy loaded you shouldn't even see a performance hit from this.
Another option to consider is to just modify your delete method to also delete the other entity.
Hello I'm a newbie to NHibernate. I'd like to make one sql query to the database using joins to my three tables.
I have an Application with many Roles with many Users. I'm trying to get NHibernate to properly form the object graph starting with the Application object. For example, if I have 10 application records, I want 10 application objects and then those objects have their roles which have their users. What I'm getting however resembles a Cartesian product in which I have as many Application objects as total User records.
I've looked into this quite a bit and am not sure if it is possible to form the application hierarchy correctly. I can only get the flattened objects to come back. It seems "maybe" possible as in my research I've read about "grouped joins" and "hierarchical output" with an upcoming LINQ to NHibernate release. Again though I'm a newbie.
[Update Based on Frans comment in Ayende's post here I'm guessing what I want to do is not possible http://ayende.com/Blog/archive/2008/12/01/solving-the-select-n1-problem.aspx ]
Thanks for you time in advance.
Session.CreateSQLQuery(#"SELECT a.ID,
a.InternalName,
r.ID,
r.ApplicationID,
r.Name,
u.UserID,
u.RoleID
FROM dbo.[Application] a JOIN dbo.[Roles] r ON a.ID = r.ApplicationID
JOIN dbo.[UserRoleXRef] u ON u.RoleID = r.ID")
.AddEntity("app", typeof(RightsBasedSecurityApplication))
.AddJoin("role", "app.Roles")
.AddJoin("user", "role.RightsUsers")
.List<RightsBasedSecurityApplication>().AsQueryable();
I just discovered batching. This should be good enough, albeit using a join would be better.
return Session
.CreateCriteria<RightsBasedSecurityApplication>()
.SetFetchMode("Roles", FetchMode.Select)
.List<RightsBasedSecurityApplication>().AsQueryable();
public class RightsBasedSecurityApplicationMapping: ClassMap<RightsBasedSecurityApplication>
{
public RightsBasedSecurityApplicationMapping()
{
Table("Application");
Id(x => x.ID, "ID");//.Column("ID");
Map(x => x.InternalName);
HasMany(x => x.Roles).BatchSize(10);
public class RoleMapping : ClassMap<Role>
{
public RoleMapping()
{
Table("Roles");
Id(x => x.ID, "ID");
References(x => x.Application, "ApplicationID").Not.LazyLoad();
Map(x => x.Name);
HasMany(x => x.RightsUsers).Table("UserRoleXRef").BatchSize(100);