Nhibernate Updating - Solutions for updating children on an entity? - nhibernate

Looking for some advice on how to update a collection on an entity. In a web app - we have a multiselect listbox allowing a user to assign and remove child entities from a list of available entities. The user would select the relevant children to associate with the parent. i.e. Product with multiple Categories it could belong to. Once the user is satisfied, they submit and we update the entities.
What is the preferred way to update(delete removed children, add new children) to the collection taking performance into account. I would not want to run several sql statements to fetch each child and add it to the parent.
Cheers
Mappings Attached:
public class ParentMap : EntityMapping<Parent>
{
public ParentMap()
{
Map(x => x.Name);
HasMany(x => x.Children)
.Cascade.AllDeleteOrphan()
.Access.LowerCaseField(Prefix.Underscore);
}
}
public class ChildMap : EntityMapping<Child>
{
public ChildMap()
{
References(x => x.Parent);
}
}
public abstract class EntityMapping<TEntity> : ClassMap<TEntity> where TEntity : EntityBase
{
protected EntityMapping()
{
Id(x => x.Id, "Id")
.UnsavedValue("00000000-0000-0000-0000-000000000000")
.GeneratedBy.GuidComb();
OptimisticLock.Version();
Version(entity => entity.Version);
}
}

Establish a cascade relation between parent and child entities and force it to act on all operations like update, delete, etc. You must define cascade behavior in you HBM mapping files. For more info: http://nhibernate.info/doc/nh/en/index.html#mapping-declaration-mapping

Related

Deletion of one-to-zero or one child record not working

I'm trying to map a couple of tables where the primary key in Bar is a foreign key to Foo i.e. a 1..0:1 relationship.
My mappings look like this:
class FooMapping : ClassMap<Foo>
{
public FooMapping()
{
Table("Foo");
Id(x => x.Id).Column("ID");
HasOne(x => x.Bar).Cascade.All();
}
}
class BarMapping : ClassMap<Bar>
{
public BarMapping()
{
Table("Bar");
Id(x => x.FooId).GeneratedBy.Foreign("Foo");
HasOne(x => x.Foo).Constrained();
}
}
The problem is that when I try to remove an instance of Bar by setting Foo.Bar = null and Bar.Foo = null, the record does not get deleted from the database.
What am I missing?
So basicly the problem is that you're using Cascade.All() in the mapping of Foo.
Instead of that you should be using Cascade.AllDeleteOrphan().
Cascade.All
when an object is save/update/delete, check the associations and save/update/delete all the objects found.
Cascade.AllDeleteOrphan
when an object is save/update/delete, check the associations and save/update/delete all the objects found. In additional to that, when an object is removed from the association and not associated with another object (orphaned), also delete it.
There is other dirty solution by making this relation with using HasMany and Cascade.AllDeleteOrphan.
class FooMapping : ClassMap<Foo>
{
public FooMapping()
{
Table("Foo");
Id(x => x.Id).Column("ID");
HasMany(x => x.Bar).KeyColumn("FooId").Cascade.AllDeleteOrphan();
}
}

Fluent Nhibernate - make References from children side only

I have Survey that HasMany Question that HasMany Option.
I want to create a reference from Option to Survey.
The problem is that Every time I want to create a new Survey with all of its children, I need to loop on all survey.Questions and loop on all survey.Questions.Options and set o each Option the Survey (or else Nhibernate will create a new Survey for each Option)
** I dont want to have IList<Option> on Survey if I do that it will solve the problem but it would be heavy and unusable **
public class SurveyMap : ClassMap<Survey>
{
public SurveyMap()
{
Id(x => x.Id).GeneratedBy.Identity();
HasMany(x => x.Questions).KeyColumn("SurveyId").Inverse().AsBag().Not.LazyLoad().Cascade.SaveUpdate();
}
}
public class QuestionMap : ClassMap<Question>
{
public QuestionMap()
{
Id(x => x.Id).GeneratedBy.Identity();
HasMany(x => x.Options).KeyColumn("QuestionId").Inverse().AsBag().Cascade.SaveUpdate();
}
}
public class OptionMap : ClassMap<Option>
{
public OptionMap()
{
Id(x => x.Id).GeneratedBy.Identity();
References(x => x.Survey).Column("SurveyId").Cascade.All();
}
}
It might be better to separate your reporting concerns from your applications business logic concerns. Use the mappings that you already have for your applications business logic, and provide a different interface into the data for your reporting requirements. You could use either HQL, SQL or new objects and maps for this.

Fluent nHibernate Mapping issue

I have a DB with the following tables
Users
Roles
UserInRoles (Bridge table for Users and Roles for many to many relationship
Modules
ModulePermission (this include the PK of Roles and modules and a bool field to identify the role has permission to access the module or not
now i m confuse about the Mapping of these table in fluent nHibernate, i did the mapping as define in Fluent nHibernate Wiki site but getting this error:
(XmlDocument)(3,6): XML validation
error: The element 'composite-id' in
namespace 'urn:nhibernate-mapping-2.2'
has incomplete content. List of
possible elements expected: 'meta,
key-property, key-many-to-one' in
namespace
'urn:nhibernate-mapping-2.2'.
Mapping:
UserMap:
public class UserMap : ClassMap<User>
{
public UserMap()
{
Table("tblUsers");
LazyLoad();
Id(user => user.UserID).GeneratedBy.GuidComb();
Map(user => user.UserName).Not.Nullable();
Map(user => user.Password).Not.Nullable();
Map(user => user.IsActive).Nullable();
HasManyToMany(user => user.RolesOfUser).Cascade.All().Inverse().Table("UserInRoles");
}
}
RoleMap:
public class RoleMap : ClassMap<Role>
{
public RoleMap()
{
Table("tblRoles");
Id(role => role.RoleID).GeneratedBy.Identity();
Map(role => role.RoleName).Not.Nullable();
Map(role => role.IsActive).Not.Nullable();
Map(role => role.Description).Not.Nullable();
HasManyToMany(role => role.Users).Cascade.All().Table("UserInRoles");
}
}
Does anyone know what is the issue?
Thanks
HasManyToMany mapping configuration worked for me when I defined Parent and Child Key Column. You can do it like this:
HasManyToMany(user => user.RolesOfUser)
.Table("UserInRoles")
.ParentKeyColumn("UserID")
.ChildKeyColumn("RoleID");

Can I force nHibernate to persist a new entity with the same ID as an already-loaded entity?

My project has a Ticket entity with an OwnedBy property. I'm using nHibernate to persist the tickets to a database.
The canonical source for potential ticket owners is Active Directory. Since I don't want to have to query Active Directory every time I load tickets, I also persist Ticket.OwnedBy to the database and load it from there when fetching tickets.
When a ticket's owner is reassigned, I get the new Owner from Active Directory and assign it to Ticket.OwnedBy, then call Session.SaveOrUpdate(ticket). When I commit the transaction, NHibernate throws a NonUniqueObjectException because an Owner with the same ID is already associated with the session.
Class definitions
class Ticket {
public int Id { get; set; }
public Owner OwnedBy { get; set; }
/* other properties, etc */
}
class Owner {
public Guid Guid { get; set; }
public string Name { get; set; }
public string Email { get; set; }
/* other properties, etc */
}
Fluent nHibernate Mappings
class TicketMap : ClassMap<Ticket> {
public TicketMap() {
Id(x => x.Id);
References(x => x.OwnedBy)
.Cascade.SaveUpdate()
.Not.Nullable();
/* other properties, etc */
}
}
class OwnerMap : ClassMap<Owner> {
public OwnerMap() {
Id(x => x.Guid)
.GeneratedBy.Assigned()
Map(x => x.Name);
Map(x => x.Email);
/* other properties, etc */
}
}
Sample code
// unitOfWork.Session is an instance of NHibernate.ISession
Ticket ticket = unitOfWork.Session.Get<Ticket>(1);
Owner newOwner = activeDirectoryRepo.FindByGuid(/* guid of new owner, from user */);
ticket.OwnedBy = newOwner;
unitOfWork.Session.SaveOrUpdate(ticket);
unitOfWork.Commit(); // Throws NonUniqueObjectException
I want nHibernate to overwrite the properties of the existing Owner with the properties of the unattached one. (The Name or Email in the object I fetched from AD may be different, and AD is supposed to be the canonical source.) I've tried calling Session.SaveOrUpdateCopy(ticket.OwnedBy) and Session.Merge(ticket.OwnedBy) before the SaveOrUpdate(ticket), but the exception is still being thrown. I've also read this related question about NonUniqueObjectException, but calling Session.Lock() didn't work either.
I have two questions:
Is there an easy way to bend nHibernate to my will?
I may have made an architectural misstep in trying to treat the owners I fetch from AD as the same type as the owners I store in the DB. How can I improve this design so I won't need to bend nHibernate to my will?
Merge works, the most likely issue is you did not call it properly. Merge will update the existing object with the new object properties but Merge does not attach the new object. So you have to use the existing one. If you use the new object after a merge you still get the same error.
The following code should fix the problem:
//Merge the new Owner
unitOfWork.Session.Merge(newOwner);
//Get a valid Owner by retrieving from the session
Owner owner = session.Get<Owner>(newOwner.Id);
// Set the ticket to this owner instance instead of the new one
ticket.OwnedBy = owner;
unitOfWork.Session.Update(ticket);
unitOfWork.Commit();
The Owner retrieved from the session by Get will have the newOwner properties but also be valid for the session.
In order to persist the detached instance of an existing Owner-entity, it should be enough to call merge on the Owner instance without the call to SaveOrUpdate. It will then either insert the entity or update the existing one.
If merge does not work, then something is wrong. Post more code in that case and post your mapping.
BTW: Do you persist the Tickets, too? If so, the mapping seems rather odd. You should have a unique ID on Ticket and Map OwnedBy as a Reference, probably as an inverse mapping with a cascade on it.
Update:
You should map it from both sides. Map the owner-side as a HasMany to Tickets with your Cascade and as Inverse Mapping. Map the Ticket side as Cascade.None() and as a Reference.
public TicketMap() {
Id(x => x.Id);
References(x => x.OwnedBy)
.Cascade.None()
.Not.Nullable();
/* other properties, etc */
}
class OwnerMap : ClassMap<Owner> {
public OwnerMap() {
Id(x => x.Guid)
.GeneratedBy.Assigned()
Map(x => x.Name);
Map(x => x.Email);
HasMany<Ticket>(x => x.Tickets).KeyColumn("TicketId").Cascade.AllDeleteOrphan().LazyLoad().Inverse().NotFound.Ignore();
/* other properties, etc */
}
That should work nicely.

NHibernate explicit fluent column mapping

I have a set of fluent object mappings that looks like this:
public class UserMap : ClassMap<User>
{
public UserMap()
{
Map(x => x.Id);
Map(x => x.Status);
}
}
public class SpecialUserMap : SubClassMap<SpecialUser>
{
public SpecialUserMap()
{
Map(x => x.Property);
}
}
public class DirectoryMap : ClassMap<Directory>
{
public DirectoryMap
{
Map(x => x.Id);
HasMany(x => x.SpecialUsers).Where("Status = 0");
}
}
User is a join table, which SpecialUser joins against to get things like status. However, when I try to reference a SpecialUser in Directory's SpecialUsers collection, I get an error of "Undefined column 'Status'", as in the generated SQL, NHibernate tries to grab the Status column from the SpecialUser table, and not the User table.
Is there a way to explicitly tell NHibernate which table to get the Status column in the DirectoryMapping?
The Status property of a User / SpecialUser needs to map onto a single column in the database. You can't have it coming sometimes from User and sometimes from SpecialUser.
As a workaround, you could add a SpecialUserStatus property to SpecialUser, and then you could query on that easily.
That mappings looks right for table-per-subclass mapping, assuming that SpecialUser extends User. My guess is that it's a bug.