views:

262

answers:

6

Hi,

I'm storing some blobs in my database, so I have a Document table and a DocumentContent table. Document contains a filename, description etc and has a DocumentContent property.

I have a Silverlight client, so I don't want to load up and send the DocumentContent to the client unless I explicity ask for it, but I'm having trouble doing this.

I've read the blog post by Davy Brion. I have tried placing lazy=false in my config and removing the virtual access modifier but have had no luck with it as yet.

Every time I do a Session.Get(id), the DocumentContent is retrieved via an outer join. I only want this property to be populated when I explicity join onto this table and ask for it.

Any help is appreciated.

My NHibernate mapping is as follows:

<?xml version="1.0" encoding="utf-8" ?>

<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2"
                   assembly="Jrm.Model"
                   namespace="Jrm.Model">
  <class name="JrmDocument" lazy="false">
    <id name="JrmDocumentID">
      <generator class="native" />
    </id>

    <property name="FileName"/>
    <property name="Description"/>
    <many-to-one name="DocumentContent" class="JrmDocumentContent" unique="true" column="JrmDocumentContentID" lazy="false"/>
  </class>


  <class name="JrmDocumentContent" lazy="false">
    <id name="JrmDocumentContentID">
      <generator class="native" />
    </id>

    <property name="Content" type="BinaryBlob" lazy="false">
      <column name="FileBytes" sql-type="varbinary(max)"/>
    </property>

  </class>
</hibernate-mapping>

and my classes are:

[DataContract]   
    public class JrmDocument : ModelBase
    {
        private int jrmDocumentID;
        private JrmDocumentContent documentContent;
        private long maxFileSize;
        private string fileName;
        private string description;

        public JrmDocument()
        {
        }

        public JrmDocument(string fileName, long maxFileSize)
        {
            DocumentContent = new JrmDocumentContent(File.ReadAllBytes(fileName));
            FileName = new FileInfo(fileName).Name;
        }

        [DataMember]
        public virtual int JrmDocumentID
        {
            get { return jrmDocumentID; }
            set
            {
                jrmDocumentID = value;
                OnPropertyChanged("JrmDocumentID");
            }
        }               

        [DataMember]
        public JrmDocumentContent DocumentContent
        {
            get { return documentContent; }
            set
            {
                documentContent = value;
                OnPropertyChanged("DocumentContent");
            }
        }        

        [DataMember]
        public virtual long MaxFileSize
        {
            get { return maxFileSize; }
            set
            {
                maxFileSize = value;
                OnPropertyChanged("MaxFileSize");
            }
        }


        [DataMember]
        public virtual string FileName
        {
            get { return fileName; }
            set
            {
                fileName = value;
                OnPropertyChanged("FileName");
            }
        }

        [DataMember]
        public virtual string Description
        {
            get { return description; }
            set
            {
                description = value;
                OnPropertyChanged("Description");
            }
        }
    }



    [DataContract]
    public class JrmDocumentContent : ModelBase
    {
        private int jrmDocumentContentID;
        private byte[] content;

        public JrmDocumentContent()
        {
        }
        public JrmDocumentContent(byte[] bytes)
        {
            Content = bytes;
        }

        [DataMember]
        public int JrmDocumentContentID
        {
            get { return jrmDocumentContentID; }
            set
            {
                jrmDocumentContentID = value;
                OnPropertyChanged("JrmDocumentContentID");
            }
        }

        [DataMember]
        public byte[] Content
        {
            get { return content; }
            set
            {
                content = value;
                OnPropertyChanged("Content");
            }
        }
    }
+2  A: 

If you want to defer loading, then set lazy="true" in your map.

I've tried lazy="true" and lazy="false". Neither give the desired effect. I don't want the DocumentContent to be loaded - lazily or otherwise unless I explicitly do it via a select criteria.I've used LLBL Gen previously and in the adapter model the relationships are not loaded unless explicitly requested via a Prefetch. I want to do something similar in nHibernate
Ciaran
A: 

You should use DTOs for your service and leave all with lazy = true instead of serializing you domain model. It's a big performance gain.

Andrea Balducci
I don't want to change my app over to DTO's now. A lot of my classes are quite lightweight and in most cases are working perfectly
Ciaran
Then use DTOs for the one's that aren't working perfectly.
James Gregory
A: 

DTO? No… Why?

My issue was that in my DataAccess Layer I have Entities loaded using Criteria API, lazy=true, and those are returned in the Service Layer, which serialize (WCF) the entity , thus accessing the properties, which trigger the proxy crap.

I just want the serializer to access the property as it is , without re-querying the DB as we are now in the service layer and not in the DAL. I don’t want to make changes at the serializer level.

In my opinion, using DTO in this case is a too bulky solution…

  • create a new DTO class for each specific need leads to a huge number of class at the end unmanageable

  • filling DTO from Entities require either a deep copy / recursive reflection / (serialization/deserialization) operation which is very consuming

In my case I absolutely want to return the Entities but i need a way :

  • either to remove the proxy crap from the entity (I found this Hibernate proxy cleaner that I need to convert for NHibernate in C#.

  • either to disable the proxy so that IF the NHibernate session is closed, the proxy does NOT try to retreive any data (I think this is missing from nhib.)

If anyone has ideas….

Mathieu Clerte
You may want to look at a DataContractSurrogate. I can check if something is a proxy and overwrite the property and set it to a new default instance. var proxy = obj as INHibernateProxy; if (proxy != null) { var initializer = proxy.HibernateLazyInitializer; if (initializer.IsUninitialized) { return Activator.CreateInstance(obj.GetType().BaseType); } else { return initializer.GetImplementation(); } }
Ciaran
+1  A: 

First rule of thumb:

  • do not distribute your domain objects

http://martinfowler.com/bliki/FirstLaw.html

dario-g
Following every law blindly isn't always possible.
Ciaran
A: 

I had a problem with where my relationship was being managed (inverse=true) and also with my session instantiation as it was being injected in. Using that in conjunction with a custom DataContractSurrogate that prevented some of the lazy loading issues now works.

Thanks

Ciaran
A: 

The scenario you have described is exactly what lazy loading is designed for.

You want to display a list containing summary information and when required load the heavier stuff. This is lazy loading. It is "lazy" because it avoids doing extra work until it absolutely needs to.

What you want is for the JrmDocumentContent to be loaded lazily and you're well on th way to that. To get this you have to remove the lazy=false. lazy=true is the default in nhibernate but you may put lazy=true to be sure. You'll have to restore the virtuals though.

Loading blobs lazily like this or any property for that matter, I think is supported in nhibernate's latest release now by setting lazy=true on the property definition.

The method you have used by separating the heavy content into a separate class/table was the only way to lazily load a blob before. I don't know what version of nhibernate you're using but the strategy you have adopted is correct. You should embrace lazy loading. You also need to remove lazy=false on the Content property of the JrmDocumentContent class. Apart from that, I see no other reason why it shouln't work.

Jideo