-->
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.  [ 4 posts ] 
Author Message
 Post subject: Is this fundamentally wrong????
PostPosted: Wed Mar 21, 2007 3:48 pm 
Beginner
Beginner

Joined: Tue Sep 19, 2006 11:26 am
Posts: 33
What I am doing that I am worried is the wrong thing to do is read an object from the database clear out any lists on that object and then re-populate them - potentially with the same data.

I am using NHibernate 1.2.0 in the persistence layer of a multi tier system. The data from the database is being passed upto a front end via a web service.

The top level object I have is Contact which has a number of one to many relationships hanging of it, such as ContactAddress and ContactLanguage. The mapping for Contact looks something like this:
Code:
<?xml version="1.0" encoding="utf-8" ?>
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2">

  <class name="Services.BusinessEntities.Contact,Services.BusinessEntities" table="Contact">

      <id name="Id" column="id" type="Int64" unsaved-value="0">
         <generator class="identity"/>
      </id>
      <bag name="ContactAddressList" inverse="true" lazy="true" cascade="all-delete-orphan">
         <key column="contactId" />
         <one-to-many class="Services.BusinessEntities.ContactAddress,Services.BusinessEntities" />
      </bag>
      <bag name="ContactLanguageList" inverse="true" lazy="true" cascade="all-delete-orphan">
         <key column="contactId" />
         <one-to-many class="Services.BusinessEntities.ContactLanguage,Services.BusinessEntities" />
      </bag>
      <property column="forename" type="String" name="Forename" not-null="true" length="50" />
      <property column="surname" type="String" name="Surname" not-null="true" length="50" />
  </class>
</hibernate-mapping>

When I receive a contact from the front-end for writing back to the database, I need to translate the "front-end" contact into a "NHibernate" contact. To do this I re-read the contact from the database, I then use the "front-end" contact to set the new values in the "NHibernate" Contact. For the properties such as forename and surname, this is a straight forward task:
Code:
      public static Contact Translate(ContactDataType from)
      {
          Contact to = contactService.GetContact(from.Id);

          to.Forename = from.Forename;
          to.Surname = from.Surname;

          // etc

          return to;
      }

The lists however is where I am woried that what I am doing is fundamentally wrong.

Taking the ContactAddressList as an example, the first thing I do is clear out the list on the "NHibernate" contact. I will then loop through the list of addresses on the front-end contact and for each one, attempt to read it from the database. If the address exists, I add it back into the address list on the "NHibernate" contact, if it doesn't I create a new "NHibernate" address and add that into the list. Finally I update any of the properties on the "NHibernate" contact address with what came back from the front-end. Any addresses that were deleted by the front-end, won't be added into the list and since I have cascade set to all-delete-orphan, they will be deleted, the new ones will be added and any others will be updated:
Code:
      public static Contact Translate(ContactDataType from)
      {

          // etc

          // Clear out the addresses - any addresses deleted by the front-end
          // won't be added back in and will therefore be deleted when the contact
          // is saved
          to.ContactAddressList.Clear();

          // Convert all of our contact addresses into "NHibernate" addresses
          foreach (BusinessEntities.ContactAddress contactAddress in TranslateContactAddressList(from.Addresses, to))
          {
              // Add the updated and new addresses back into the list for saving to the database
              to.ContactAddressList.Add(contactAddress);
          }

          // etc

          return to;
      }

      private static List<BusinessEntities.ContactAddress> TranslateContactAddressList(List<DataTypes.ContactAddress> from,
                                                       BusinessEntities.Contact parent)
      {
          List<BusinessEntities.ContactAddress> to = new List<BusinessEntities.ContactAddress>();

          foreach (DataTypes.ContactAddress address in from)
          {
            // Business logic layer will catch any exceptions and return null if the address
            // doesn't exist.
            BusinessEntities.ContactAddress addressBE = contactService.GetContactAddress(from.Id);

            // If to is null we are adding a new address otherwise we update the existing one
            if(to == null)
            {
                addressBE = new BusinessEntities.ContactAddress();
            }

            addressBE.Contact = parent;
            addressBE.number = from.number;
            addressBE.street = from.street;

            // etc

            // Add the address back into the list
            to.Add(addressBE);
          }

          return to;
      }


I would really appreciate it if someone could confirm one way or another whether what I am doing is OK or not.


Top
 Profile  
 
 Post subject:
PostPosted: Thu Mar 22, 2007 6:02 am 
Beginner
Beginner

Joined: Tue Sep 19, 2006 11:26 am
Posts: 33
It has been suggested to me that the reason I haven't got any responses to my question is that I take a bit too long to get to my point :o). Please find the point at the begining of my post now:

Quote:
What I am doing that I am worried is the wrong thing to do is read an object from the database clear out any lists on that object and then re-populate them - potentially with the same data.


A quick yes or no answer would be really appreciated.

Thanks.


Top
 Profile  
 
 Post subject:
PostPosted: Fri Mar 23, 2007 7:01 am 
Beginner
Beginner

Joined: Tue Sep 19, 2006 11:26 am
Posts: 33
I am going to take it upon myself to say that yes this wrong!

The reason I asked the question in the first place was because although this approach seemed to work fine for me for some time, I recently added another one to many relationship to the contact and I started getting an ObjectDeletedException when I was processing this new relationship. Specifically, after I had cleared out the list, when I tried reading (what I considered to be) an existing item, the exception was thrown.

I spent time trying to debug the NHibernate code in addition to my own in the hope that if nothing else I could see when and where NHibernate was trying to delete my objects. I never did track it down but I finally decided to add a flush just before I did anything with the NHibernate session (whether that be a read a save or whatever). This resulted in ObjectDeletedExceptions being thrown whenever I tried reading an object that was previously in a list that I had cleared out. I could at this point try and argue that this is a bug - I won't I'll just post my soluition instead.

Basically what I do now is loop through the list that I received from the client and attempt to read each corresponding object from the database. If one is found then I update it with the data received from the client. When I do my read, NHibernate will return me a reference to the same object that is in the list that I was previously clearing out (I know this isn't strictly true due to lazy loading, but is essentially what is happening) - therefore I am effectively updating both lists at this point. If I fail to read an item from the database I create a new object instead and add that to my list. Once I have processed the list received from the client I am left with two lists. The one NHibernate created which I was previously clearing out and the one I just created. The first one won't have any new items in it, so I need to get these from the second list and add them to the first. Any items that are in the first list but don't appear in the second will need to be removed from the first list. The code I use to do this is as follows:
Code:
        public static void UpdateInt64PrimaryKeyBEList<T>(IList oldList, IList<T> newList)
        {
            DataService.RemoveDeletions<Int64, T>(oldList, newList);

            foreach (T entity in newList)
            {
                if (DataService.GetId<Int64, T>(entity) == 0)
                {
                    oldList.Add(entity);
                }
            }
        }


The static DataService methods are as follows:
Code:
        public static T GetId<T, U>(Uentity)
        {
            IClassMetadata entityMeta =
                NHibernateSessionManager.Instance.SessionFactory.GetClassMetadata(typeof(U));

            return (T)entityMeta.GetIdentifier(entity);
        }

        public static void RemoveDeletions<T, U>(IList origList, IList<U> newList)
        {
            Dictionary<T, U> newItems = new Dictionary<T, U>();

            foreach (U item in newList)
            {
                T id = GetId<T>(item);

                // Need to test for duplicates since new items will all have the same id (0 for ints probably)
                if (!newItems.ContainsKey(id))
                {
                    newItems.Add(id, item);
                }
            }

            IList<Int32> idxOfitemsToDelete = new List<Int32>();
            for (int i = 0; i < origList.Count; i++)
            {
                if (!newItems.ContainsKey(GetId<T, U>((U)origList[i])))
                {
                    idxOfitemsToDelete.Add(i);
                }
            }

            for (int i = 0; i < idxOfitemsToDelete.Count; i++)
            {
                // Need to subtract i as everything will shuffle down as we delete items
                origList.RemoveAt(idxOfitemsToDelete[i] - i);
            }
        }



I hope this means that somebody else won't spend two days of there life on the same issue :o)


Top
 Profile  
 
 Post subject:
PostPosted: Sat Mar 24, 2007 12:01 am 
Pro
Pro

Joined: Fri Nov 19, 2004 5:52 pm
Posts: 232
Location: Chicago, IL
I don't know if this is the issue that you ran into or not, but, one thing to watch out for is that when you do a query, I'm pretty sure by default it will flush any changes that you have made to objects that are in the session cache. You can change the flush mode to change how this works. I'm not saying that you should change the flush mode, just that it can be done and pointing out that the session gets flushed on a query.


Top
 Profile  
 
Display posts from previous:  Sort by  
Forum locked This topic is locked, you cannot edit posts or make further replies.  [ 4 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.