-->
These old forums are deprecated now and set to read-only. We are waiting for you on our new forums!
More modern, Discourse-based and with GitHub/Google/Twitter authentication built-in.

All times are UTC - 5 hours [ DST ]



Forum locked This topic is locked, you cannot edit posts or make further replies.  [ 8 posts ] 
Author Message
 Post subject: Cascade all-delete-orphan and null collection
PostPosted: Sat Oct 04, 2008 9:05 am 
Newbie

Joined: Sun Sep 14, 2008 6:40 pm
Posts: 6
Hi,

I've got a problem with Cascade behaviour all-delete-orphan.
In one session I load the container object (SomeContainer) with its Files and File data. Then I close the session and let the user do some changes. Let's say the user wants to delete a file. Ok - i remove it from the container:
Code:
//method in SomeContainer
public void RemoveFile(File file)
        {
            _files.Remove(file);
            if (file.Id != 0)
            {
                file.Deleted = true;
                removedFiles.Add(file);
                FirePropertyChanged("Files");
            }
           
        }

and during the update:
Code:
//method in SomeContainer
protected override void OnUpdate()
        {
            //save files flaged as deleted
            foreach (File file in removedFiles)
            {
                file.Save();
            }
        }

But then the NHibernate throws an exception saying that
"You may not dereference an collection with cascade="all-delete-orphan""
It is because I manually initialize the collection in the File class:
private IList<FileData> _fileDatas = new List<FileData>();
I've read some posts about it and I know that I shouldn't change the _fileDatas reference cause NH wants to track the changes to delete possible orphaned entities and when I change the ref it is unable to.
Seems like a good reason to me.
But what should I do when I want to create a new file? If I remove the private IList<FileData> _fileDatas = new List<FileData>(); in the File class, then by saying
File f = new File(); f.Data = someBinaryData; I would cause a NullRefEx.


So how can I create a new entity with properly initialized collection that has a cascade all-delete-orphan?
I want to create both entities (File and FileData) first in memory and later choose whether to persist or discard (based on user action).


NHibernate version: 1.2

Mapping documents:

Since I use ActiveRecord I'm posting my classes decorated with attributes
Code:
    [ActiveRecord(SelectBeforeUpdate = true)]
    public partial class SomeContainer
        : MainActiveRecordBase<SomeContainer>,
        INotifyPropertyChanged
    {
        //...
        private IList<File> _files = new List<File>();
        [HasMany(Cascade = ManyRelationCascadeEnum.All, Inverse = true, Where="Deleted = 0",
                 Access = PropertyAccess.NosetterCamelcaseUnderscore,
                 RelationType = RelationType.Bag)]
        public IEnumerable<File> Files
        {
            get
            {
                return _files;
            }
        }

    //...
    }

[ActiveRecord(SelectBeforeUpdate=true, Where="Deleted = 0")]
public class File : MainActiveRecordBase<File>
    {
//...
        private IList<FileData> _fileDatas = new List<FileData>();
        [HasManyAttribute(Cascade = ManyRelationCascadeEnum.AllDeleteOrphan,
                Access = PropertyAccess.NosetterCamelcaseUnderscore,
                Lazy = true)]
        private IList<FileData> FileDatas
        {
            get
            {
                return _fileDatas;
            }
        }

         public byte[] Data
        {
            get
            {
                if (_fileDatas.Count == 0)
                {
                    return null;
                }
                else
                {
                    return _fileDatas[0].Data;
                }
            }
            set
            {
                if (_fileDatas.Count == 0)
                {
                    FileData fileData = new FileData();
                    fileData.File = this;
                    _fileDatas = new List<FileData>();
                    _fileDatas.Add(fileData);
                }
                _fileDatas[0].Data = value;
            }
        }
}

    [ActiveRecord()]
    public class FileData : MainActiveRecordBase<FileData>
    {
        private byte[] _data;
        [Property(Column = "`Data`", ColumnType = "BinaryBlob", Length = 2147483647)]
        public byte[] Data
        {
            get
            {
                return _data;
            }
            set
            {
                _data = value;
            }
        }

        private File _file;
        [BelongsTo("FileId")]
        public File File
        {
            get
            {
                return _file;
            }
            set
            {
                _file = value;
            }
        }

    }


Name and version of the database you are using:

Oracle 10g XE[/code]


Top
 Profile  
 
 Post subject:
PostPosted: Mon Oct 06, 2008 8:35 am 
Expert
Expert

Joined: Thu Dec 14, 2006 5:57 am
Posts: 1185
Location: Zurich, Switzerland
Your problem probably results from your OnUpdate implementation:

Code:
protected override void OnUpdate()
        {
            //save files flaged as deleted
            foreach (File file in removedFiles)
            {
                file.Save();
            }
        }


Why are you doing this ? If you specify cascade "all-delete-orphan" hibernate takes care of that. Removing it from the the bag and setting the container reference to null on the file object (if it's a bidirectional association) should be enough.

_________________
--Wolfgang


Top
 Profile  
 
 Post subject:
PostPosted: Mon Oct 06, 2008 9:23 am 
Newbie

Joined: Sun Sep 14, 2008 6:40 pm
Posts: 6
Here's the explanation of why I am doing that OnUpdate stuff.
I have a WinForms app with shirt-living sessions. I simply load the data, close the session and work on detached entities. In my "main" object I've got a list of files. I want let the user remove some files. The removed files have to be instantly removed from the collection - otherwise the user would still see them ie. in Grid. I remove them manually because instead of physically removing them from DB I just want to mark them as Deleted. Of course I do the saving when the user confirms that he wants to save the changes.

So that's why I can't let NH handle removing the files. I simply don't want to delete them in this particular scenario.
But in case the file gets deleted [somewhere in the app] I want to assure that no orphaned FileDatas are around. That can happen when someone in File class writes _fileDatas.Remove(data). That would be illegal from the logical point of view but I want to protect from such mistake. But in case it happens - fine - let that FileData GET removed, so that I won't have in DB [and after reloading also in app] many FileDatas connected to one File. So I want NH to track the changes and when a programmer removes FileData (bad thing, but I can recover in File.Data getter/setter) the FileData really gets deleted from the DB.

I know it's kinda weird but it's because NH doesn't curently support lazy loaded properties...


Top
 Profile  
 
 Post subject:
PostPosted: Mon Oct 06, 2008 10:00 am 
Expert
Expert

Joined: Thu Dec 14, 2006 5:57 am
Posts: 1185
Location: Zurich, Switzerland
How do you save the container and what does file.Save() actually do ? Can you post the code fragments ?

I suppose you get the exception because the file is not in the bag anymore thus hibernate tries to delete it. But in the same run you try to save it (again).

_________________
--Wolfgang


Top
 Profile  
 
 Post subject:
PostPosted: Mon Oct 06, 2008 10:06 am 
Newbie

Joined: Sun Sep 14, 2008 6:40 pm
Posts: 6
That's how I save the container:
I call contrainer.Save() which in turn fires OnUpdate. Notice that I use ActiveRecord.

Code:
protected override void OnUpdate()
{
     base.OnUpdate();
     
      //save flaged as deleted files
      foreach (File file in removedFiles)
      {
          file.Save();
      }
}


But first, when the user want's to delete a File he calls:
Code:
public void RemoveFile(File file)
{
       file.Deleted = true;
       _files.Remove(file);
       if (file.Id != 0)
       {
           removedFiles.Add(file);
       }
       FirePropertyChanged("Files");
}       


Top
 Profile  
 
 Post subject:
PostPosted: Mon Oct 06, 2008 10:20 am 
Expert
Expert

Joined: Thu Dec 14, 2006 5:57 am
Posts: 1185
Location: Zurich, Switzerland
When you save the container, hibernate will delete the file since you have specified "all-delete-orphan". If you want to keep the file and just mark it as deleted you probably need a different approach.

I suggest something like that:

1) Remove the public accessor to the internal file list
2) Don't remove the file from the internal file list, just mark it as deleted.
3) add a public accessor that returns a list of all files that are not marked as deleted.

_________________
--Wolfgang


Top
 Profile  
 
 Post subject:
PostPosted: Mon Oct 06, 2008 11:40 am 
Newbie

Joined: Sun Sep 14, 2008 6:40 pm
Posts: 6
I took a bit different aproach but based on what you said:
I changed the NH access to the Container.Files property to field and in property's getter I return a filtred list. I also removed the OnUpdate and changed the Container.RemoveFile method.
Here's how it looks now:

Code:
        private IList<File> _notDeletedFiles = new List<File>();
        private IList<File> _files = new List<File>();
        [HasMany(Cascade = ManyRelationCascadeEnum.All, Inverse = true, Where="Deleted = 0",
                 Access = PropertyAccess.FieldLowercaseUnderscore,
                 RelationType = RelationType.Bag)]
        public IEnumerable<File> Files
        {
            get
            {
                _notDeletedFiles.Clear();
                foreach (File f in _files)
                {
                    if (!f.Deleted)
                    {
                        _notDeletedFiles.Add(f);
                    }
                }
                return _notDeletedFiles;
            }
        }

        public void RemoveFile(File file)
        {
            file.Deleted = true;
            if (file.Id != 0)
            {
                _files.Remove(file);
            }
            FirePropertyChanged("Files");
        }         


I could finally set AllDeleteOrphan on my FileDatas collection in File class:

Code:
private bool inited = false;
        private IList<FileData> _fileDatas;
        [HasManyAttribute(Cascade = ManyRelationCascadeEnum.AllDeleteOrphan,
            Access = PropertyAccess.NosetterCamelcaseUnderscore,
            Inverse=true,
            Lazy=true)]
        private IList<FileData> FileDatas
        {
            get
            {
                return _fileDatas;
            }
        }


and just fi you're curious how I handled "detached" lazy collections:

Code:
public byte[] Data
        {
            get
            {
                if (!inited)
                {
                    NHibernate.ISession tmpSession =
                        ActiveRecordMediator.GetSessionFactoryHolder().CreateSession(typeof(FileData));
                    try
                    {
                        tmpSession.Lock(this, NHibernate.LockMode.None);
                        NHibernate.NHibernateUtil.Initialize(_fileDatas);
                    }
                    catch (Exception)
                    {

                    }
                    finally
                    {
                        ActiveRecordMediator.GetSessionFactoryHolder().ReleaseSession(tmpSession);
                    }
                }
                if (_fileDatas == null || _fileDatas.Count == 0)
                {
                    return null;
                }
                else
                {
                    return _fileDatas[0].Data;
                }
            }
           
            set
            {
                if (_fileDatas == null)
                {
                    _fileDatas = new List<FileData>();
                }
                if (_fileDatas.Count == 0)
                {
                    FileData fileData = new FileData();
                    fileData.File = this;
                    _fileDatas = new List<FileData>();
                    _fileDatas.Add(fileData);
                }
                _fileDatas[0].Data = value;
            }
        }


I know - kinda weird, but don't know how to handle detached lazy colls in a better way. Maybe that's the best one ;)


It works now.
Thank you for your help!


Top
 Profile  
 
 Post subject:
PostPosted: Tue Oct 07, 2008 3:05 am 
Expert
Expert

Joined: Thu Dec 14, 2006 5:57 am
Posts: 1185
Location: Zurich, Switzerland
You can use ReadOnlyCollection<File> instead of IEnumerator<File> to make sure that nobody changes this list directly. The rest looks good.

_________________
--Wolfgang


Top
 Profile  
 
Display posts from previous:  Sort by  
Forum locked This topic is locked, you cannot edit posts or make further replies.  [ 8 posts ] 

All times are UTC - 5 hours [ DST ]


You cannot post new topics in this forum
You cannot reply to topics in this forum
You cannot edit your posts in this forum
You cannot delete your posts in this forum

Search for:
© Copyright 2014, Red Hat Inc. All rights reserved. JBoss and Hibernate are registered trademarks and servicemarks of Red Hat, Inc.