I am unable to get Eclipselink to delete a privately owned entity that is mapped as a one-to-many relationship. When I remove the privately owned entity from the one-to-many collection and set the reference to its parent to NULL and then save the parent, the privately owned entity is not removed from the database. However, it is removed from the object and during the remainder of the session, it appears that the privately owned entity was in fact removed.
Here's one mapping
#OneToMany(mappedBy="parent", cascade={CascadeType.MERGE, CascadeType.PERSIST, CascadeType.REFRESH}, fetch=FetchType.EAGER)
#PrivateOwned // should prevent orhpans, but doesn't.
List<PrivatelyOwnedEntity> privatelyOwnedEntities;
I have tried adding cascade all, and also added #CascadeOnDelete, and every combination thereof, which one would think would not have an impact, and it didn't.
otherside:
#ManyToOne
private Parent parent;
I have tried adding JoinColumn(nullable=false) and also removing the #ManyToOne annotation, and every combination of the presence/removal of these annotaitons. None of the changes had any impact.
Removal code:
public boolean removePrivatelyOwnedEntity(int i) {
PrivatelyOwnedEntity privatelyOwnedEntityToBeRemoved = this.privatelyOwnedEntities.get(i);
//privatelyOwnedEntityToBeRemoved.setParent(null);
return this.privatelyOwnedEntities.remove(privatelyOwnedEntityToBeRemoved );
}
Code that updates:
projectManager.saveProject(project);
//so this privatelyOwnedEntity is deep within this object.
// Its project.site.siteMap.parents.privatelyOwnedEntities.
// SiteMap is embedded in Site.
// On save it calls a dao, and the dao's abstract class handles the update
// as shown below using Springs JpaTemplate
Save code:
public T save(T object) {
return getJpaTemplate().merge(object);
}
Is the parent part of the object's id?
Try not setting the parent to null, you should not do this for private owned objects, just remove them from the collection.
Related
How can I make NHibernate to update any field ONLY on first saving and not to update it on session.Update(obj)?
EDIT: For example, I have an entity A, that has reference to entity B, like:
public class A
{
// ... some properties
public virtual B PropB {get; set;}
}
After retrieving the instance of class A I save all its properties instead of PropB into fields on web page (including id and version). After user modified some fields and click 'Save' (herewith I am sure, that he can not edit PropB), I can just restore this object from the web page and save it to the database but I can not restore the linked PropB. So, when I save A instance, it looses link. So, because of PropB can not be modified by any way after first saving, I need a solution to restrict its updating.
There's a mapping attribute that effectively makes a property insert-only: update="false".
However, there are two issues with your question:
session.Update does not update an entity, flushing the session does. You only need to call session.Update to attach entities that were not loaded by the session.
Why are you modifying a property you don't intend to update in the DB?
I would us DTO's in this case and update the entities within the transaction.
Pseudo-code:
public StoreB(ADto dto)
{
using (transaction)
{
A entity = session.Get<A>(dto.Id);
entity.PropB = dto.PropB;
transaction.Commit();
}
}
public StoreC(ADto dto)
{
using (transaction)
{
A entity = session.Get<A>(dto.Id);
entity.PropC = dto.PropC;
transaction.Commit();
}
}
I have a situation where I'm using a table per subclass for association. The mappings are fine until I try and delete a record from the subclass table. The delete cause's not only the subclass to be deleted, but also the parent. I can understand this feature may be by design, but is there anyway to just delete the subclass?
Here's my sample code.
public class ParentClassMap : ClassMap<Parent>
{
public ParentClassMap ()
{
Table("tblParent");
Id(x => x.ParentId).Column("ParentId").GeneratedBy.Identity()
... other properties
}
}
public class ChildClassMap : SubClassMap<Child>
{
public ChildClassMap()
{
Table("tblChild");
KeyColumn("ParentId");
... other properties
}
}
Now when I query for a record, everything seems fine
Child child = session.CreateCriteria<Parent>().Add(Restrictions.Eq("ParentId", 1)).UniqueResult<Parent>();
But when I delete the child, the sql executed includes updates to all tables that reference either Parent or Child, then the deletion of Child then Parent.
session.Delete(child);
I only want to delete the Child Object, is this possible?
No, it's not possible.
Think about it in OOP terms: if you have an object of the class Dog that inherits from Animal, does it makes sense to "delete the dog, but leave the animal"?
Which brings me to the next point: if you can delete "part" of an object, then you should not be using a subclass, but an association (probably one-to-one or many-to-one)
I'm working on a project with NHibernate that classes similar to the following:
public class Parent {
public IList Children {get;set;}
// ...
}
public class Child {
// ...
}
I've got the Children property set to cascade all / delete orphan. Since I'm using the aggregate pattern and instances of Child class will only ever be referenced in the context of a Parent, I don't have a ChildRepository to delete the children directly - only a ParentRepository. However, when I retrieve the Parent object and call Parent.Children.Clear(), the children from the database are never deleted. How can I achieve this?
Deleting child entities is this easy - just remove them from the collection and then save the parent entity. The collection should be mapped with all-delete-orphans.
parent.Children.Clear();
session.Save( parent );
// or
parent.Children.RemoveAt(0);
session.Save( parent );
You can do this without Save() calls as well, unless your FlushMode is Never.
session.BeginTransaction();
parent.Children.Clear();
session.Transaction.Commit();
Using #Chris's UnitOfWork abstractionm this could look like:
using (var uow = new UnitOfWork()) {
parent.Children.Clear();
}
I have two related objects: ProgramSession and ProgramTask, with a one-to-many relationship. A ProgramSession has many ProgramTasks. So the objects looks like this:
public class ProgramSession
{
public virtual IList<ProgramTask> ProgramTasks
{
get { return _programTasks; }
set { _programTasks = value; }
}
}
public class ProgramTask
{
public virtual ProgramSession ProgramSession
{
get { return _programSession; }
set { _programSession = value; }
}
}
And the mappings...
ProgramSession.hbm.xml
<bag name="ProgramTasks" lazy="false" cascade="all-delete-orphan" inverse="true" >
<key column="SessionUid"></key>
<one-to-many class="ProgramTask"></one-to-many>
</bag>
ProgramTask.hbm.xml
<many-to-one name="ProgramSession" column="SessionUid" class="ProgramSession" />
The problems begin when I try to change the ProgramSession of a ProgramTask.
If I remove the ProgramTask from the ProgramSession.ProgramTasks list property of the old session, and then add it to the same property on the new session, NHibernate tells me that the a deleted object will be resaved.
If I simply change the value of the ProgramTask.ProgramSession object, I have no problem saving. However, I get weird behavior if I do not save immediately because the ProgramSession.ProgramTasks properties (on both sessions) are not synchronized until after the NHibernate session is refreshed.
Changing the ProgramTask.ProgramSession object without directly modifying the lists as well, creates an invalid state. Take the following code as an example:
programTask.ProgramSession = newProgramSession;
Assert.That(newProgramSession.ProgramTasks.Contains(programTask)); // fails
Assert.That(!oldProgramSession.ProgramTasks.Contains(programTask)); // fails
This is more problematic in code that executed later on, that assumes the ProgramTasks collections are synchronized with the ProgramSession property. For example:
foreach(var programTask in programSession.ProgramTasks)
{
// whatever
}
One hack I used to work around this was to query the list. I can't use it everywhere, and it's clearly a bad solution, but it underscores the problem:
var tasksActuallyInSession =
programSession.ProgramTasks
.Where(task => task.ProgramSession == programSession)
.ToList();
Is there any way to handle this kind of situation? A best practice? Am I doing something wrong? Is the mapping incorrect? Is there some super-secret NHibernate flag I need to set?
Not sure if I understand everything what you are doing here. Some thoughts:
If you decide to move ProgramTasks around, then they get independent and should not be mapped using cascade="all-delete-orphan". If you do this, NH removes the ProgramTask from the database when you remove it from a ProgramSession.
Map it using cascade="none" and control the lifecycle of the object yourself. (this means: store it before a ProgramSession gets stored. Delete it when it is not used anymore.)
Not sure if this this is also a problem, but note that if you have a inverse reference, you code is responsible to make the references consistent. Of course, references get cleaned up after storing to the database and loading into an empty session, this is because there is only one foreign key in the database. But this is not the way it should be done. It is not responsibility of NH to manage your references. (Its only responsibility is to persist what you are doing in memory.) So you need to make it consistent in memory, and implement your business logic as if there wasn't NHibernate behind.
I have a situation where I need to load part of an object graph using custom SQL (for performance reasons). So I load up this collection using my custom SQL (ISession.CreateSQLQuery()) and then I assign that list to a property on another entity object.
The problem is that when I assign the list to the property, it makes that entity dirty along with all of the objects in the list, so when I go to save the object, it does hundreds of queries to save the entire list. Is there a way that I can specify that an object is NOT dirty (after I load it up myself)?
(Yeah, I know I could turn off cascade="save-update", but I really don't want to have to do that if I can avoid it.)
I think there is a functionality to evict an entity.
That means it is not connected to NHibernate anymore.
UPDATED after Jon's various comments:
If you want NHibernate to manage the object, ie detect if it is dirty, then keep it managed.
If not, Evict() it, it won't be managed. You can still save it manually and so on, it's just that it won't be done automatically for you.
I don't see any middle ground, between automatic and manual...
Note that you can still persist in various ways, like saving manually the parent entity, a Set of child entities and so on... Many things are still possible.
Expanding on KLEs answer, I would:
Evict() the parent entity
Load the child list
Attach the list of children to the parent entity
Merge() the whole thing back into nHibernate
At that point I believe that NHibernate will recognize everything as clean.
Can you just remove the property you use to store this manually fetched data from NHibernates tracking?
Assuming that you are not persisting the property that the list is assigned to, you can remove that property from the NHibernate mapping. I haven't tested this, but my expectation is that assigning to that property would not cause IsDirty() to return true.
EDIT: Ok, try try this. Load the object from an IStatelessSession, call your custom SQL and assign the property. Then Lock the object into a new ISession and continue working with it. I think the Lock will cascade to child objects if your cascade setting is all or all-delete-orphan. If Lock does not cascade then you will have to manually walk the object graph.
I don't remember where I got this from, but I have a class of Session extensions, one of which is this:
public static Object GetOriginalEntityProperty(this ISession session, Object entity, String propertyName)
{
ISessionImplementor sessionImpl = session.GetSessionImplementation();
IPersistenceContext persistenceContext = sessionImpl.PersistenceContext;
EntityEntry oldEntry = persistenceContext.GetEntry(entity);
if ((oldEntry == null) && (entity is INHibernateProxy))
{
INHibernateProxy proxy = entity as INHibernateProxy;
Object obj = sessionImpl.PersistenceContext.Unproxy(proxy);
oldEntry = sessionImpl.PersistenceContext.GetEntry(obj);
}
if (oldEntry == null) // I'm assuming this means the object is transient and that this is the way to treat that
return false;
String className = oldEntry.EntityName;
IEntityPersister persister = sessionImpl.Factory.GetEntityPersister(className);
Object[] oldState = oldEntry.LoadedState;
Object[] currentState = persister.GetPropertyValues(entity, sessionImpl.EntityMode);
Int32[] dirtyProps = persister.FindDirty(currentState, oldState, entity, sessionImpl);
Int32 index = Array.IndexOf(persister.PropertyNames, propertyName);
Boolean isDirty = (dirtyProps != null) ? (Array.IndexOf(dirtyProps, index) != -1) : false;
return ((isDirty == true) ? oldState[index] : currentState[index]);
}
If you get the original value using this method and assign it to the persistent property it will no-longer be dirty.
You could use an interceptor and then override the FindDirty method.