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

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.

Related

Fluent NHibernate: do not generate any mapping for class during SchemaExport

I've got an application talking to two databases, and it runs fine, and my tests pass, except when it tries to export the current DB schema, it outputs everything for both DBs in one block, and when it tries to validate the object model / DB schema line up, it tries to look in one database for everything, when one class (which is external data, hence the foreign DB) should not be mapped. I provided a mapping override for the class so that NH can correctly load/use data from the foreign DB (it's read-only), but now when I export my schema, it tries to make that table.
I tried IgnoreBase(typeof(Unit)) but this has no effect (whereas IgnoreBase(typeof(Entity)) does work correctly). I have decorated the custom repository methods with [SessionFactory(DataGlobals.FOREIGN_DB_FACTORY_KEY)] where that simply defines a constant string to use as a key for the SessionFactory, but I'm sure if there's something I need to decorate the class (Unit) with or pass a different parameter to SchemaExport ...
public void CanGenerateDatabaseSchema(){
var session = NHibernateSession.GetDefaultSessionFactory().OpenSession();
using (TextWriter stringWriter = new StreamWriter("../../../../db/schema/UnitTestGeneratedSchema.sql"))
{
new SchemaExport(configuration).Execute(true, false, false, session.Connection, stringWriter);
}
}
public void CanConfirmDatabaseMatchesMappings()
{
var allClassMetadata = NHibernateSession.GetDefaultSessionFactory().GetAllClassMetadata();
foreach (var entry in allClassMetadata)
{
NHibernateSession.Current.CreateCriteria(entry.Value.GetMappedClass(EntityMode.Poco))
.SetMaxResults(0).List();
}
}
In Fluent NHibernate you can use SchemaAction.None to disable the generation of the schema for a particular table.
Example:
public class BankInfoMap : ClassMap<BankInfo>
{
public BankInfoMap()
{
Schema(“viplookups.dbo”);
Table(“bnkroute”);
SchemaAction.None();
Id(x => x.Id).Column(“bnkrouteid”);
Map(x => x.AbaNumber).Column(“crouting”);
Map(x => x.Name).Column(“ccompname”);
Map(x => x.City).Column(“ccity”);
Map(x => x.State).Column(“cstate”);
Map(x => x.PhoneNumber).Column(“cphone1″);
}
}
Taken from: http://lostechies.com/rodpaddock/2010/06/29/using-fluent-nhibernate-with-legacy-databases/

NHibernate: Exception with collection mapped as cascade all-delete-orphan

I receive the following exception intermittently:
A collection with cascade="all-delete-orphan" was no longer referenced by the owning entity instance: Domain.Foo.Bars
The majority of the results on Google for this exception indicate that the problem occurs when you dereference a collection, instead of calling Clear() on the existing collection then adding new entities. However, this is not my problem.
Here is all the relevant code:
public class Foo
{
public int Id { get; set; }
private Iesi.Collections.Generic.ISet<Bar> _bars = new HashedSet<Bar>();
public virtual ICollection<Bar> Bars
{
get { return _bars; }
}
}
public class Bar
{
public int Id { get; set; }
public DateTime Expiry { get; set; }
}
public class FooDbMap : ClassMap<Foo>
{
public FooDbMap
{
Id(x => x.Id);
HasMany(x => x.Bars)
.Access.CamelCaseField(Prefix.Underscore)
.KeyColumn("FooId")
.LazyLoad()
.Where("Expiry > (select getdate())")
.AsSet()
.Cascade.AllDeleteOrphan();
}
}
You will see with this code that it is impossible to dereference the Bars collection, i.e. by doing this:
foo.Bars = new List<Boo>();
What could be causing the error?
you should never mess with collection references created by NHibernate. First of all, NH creates proxy objects for lazy loading - replace that proxy with a List<> and NH has no way to either lazy load the contents OR detect wether any children have been removed. Secondly, NH watches mapped collections for changes (new entities, deletes etc.). Just replacing a collection with a new one that NH does know nothing about is not a good idea. NH will still have a reference to the managed collection, especially if you have NH monitor this collection and cascade all changes to contained children.
I would simply clear the collection to remove all entities instead of replacing the entire collection.

Nhibernate Updating - Solutions for updating children on an entity?

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

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.

Nhibernate Fluent domain Object with Id(x => x.id).GeneratedBy.Assigned not saveable

I am using for some legacy db the corresponding domainclasses with mappings.
Now the Ids of the entities are calculated by some stored Procedure in the DB which gives back the Id for the new row.(Its legacy, I cant change this)
Now I create the new entity , set the Id and Call Save. But nothing happens. no exeption. Even NH Profiler does not say a bit. its as the Save call does nothing.
I expect that NH thinks that the record is already in the db because its got an Id already.
But I am using Id(x => x.id).GeneratedBy.Assigned() and intetionally the Session.Save(object) method.
I am confused. I saw so many samples there it worked.
does any body have any ideas about it?
public class Appendix
{
public virtual int id { get; set; }
public virtual AppendixHierarchy AppendixHierachy { get; set; }
public virtual byte[] appendix { get; set; }
}
public class AppendixMap : ClassMap<Appendix>
{
public AppendixMap ()
{
WithTable("appendix");
Id(x => x.id).GeneratedBy.Assigned();
References(x => x.AppendixHierachy).ColumnName("appendixHierarchyId");
Map(x => x.appendix);
}
}
Stupid question, but the reason of this problem in many cases: did you commit the session? NH caches the changes (when calling Save, nothing happens yet) until it is forced to flush it or until you commit the session. When you create your own ADO connection, you need also to call Flush before committing.