Silent truncation of binary data

Description

Hi,
I have upgraded from NHibernate 2.1 to NHibernate 3.0 GA and I have hit a regression.
I have an entity with an Image property of type System.Drawing.Image and I have mapped it like
this:

<property name="Image" type="System.Drawing.Image, System.Drawing, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
<column name="Image" length="2147483647" not-null="false" />
</property>`

The Image column in the table is of type varbinary(MAX) in a MySQL Server 2008 Express database and I am using the MsSql2008 dialect.

While in NHibernate 2.1 this works perfectly fine in NHibernate 3.0 GA it breaks with the exception below when retrieving the entity back from the database:

If I serialize the Image myself with a BinaryFormatter the resulting byte array is more than 8000 bytes, but I see that the NHibernate code is deserializing only the first 8000 bytes. Maybe varbinary(MAX) is not handled correctly?

NHibernate.Type.SerializationException was unhandled by user code
Message=Could not deserialize a serializable property:
Source=NHibernate
StackTrace:
at NHibernate.Type.SerializableType.FromBytes(Byte[] bytes) in d:\CSharp\NH\nhibernate\src\NHibernate\Type\SerializableType.cs:line 150
at NHibernate.Type.SerializableType.Get(IDataReader rs, Int32 index) in d:\CSharp\NH\nhibernate\src\NHibernate\Type\SerializableType.cs:line 67
at NHibernate.Type.NullableType.NullSafeGet(IDataReader rs, String name) in d:\CSharp\NH\nhibernate\src\NHibernate\Type\NullableType.cs:line 253
at NHibernate.Type.NullableType.NullSafeGet(IDataReader rs, String[] names, ISessionImplementor session, Object owner) in d:\CSharp\NH\nhibernate\src\NHibernate\Type\NullableType.cs:line 195
at NHibernate.Type.AbstractType.Hydrate(IDataReader rs, String[] names, ISessionImplementor session, Object owner) in d:\CSharp\NH\nhibernate\src\NHibernate\Type\AbstractType.cs:line 131
at NHibernate.Persister.Entity.AbstractEntityPersister.Hydrate(IDataReader rs, Object id, Object obj, ILoadable rootLoadable, String[][] suffixedPropertyColumns, Boolean allProperties, ISessionImplementor session) in d:\CSharp\NH\nhibernate\src\NHibernate\Persister\Entity\AbstractEntityPersister.cs:line 2505
at NHibernate.Loader.Loader.LoadFromResultSet(IDataReader rs, Int32 i, Object obj, String instanceClass, EntityKey key, String rowIdAlias, LockMode lockMode, ILoadable rootPersister, ISessionImplementor session) in d:\CSharp\NH\nhibernate\src\NHibernate\Loader\Loader.cs:line 980
at NHibernate.Loader.Loader.InstanceNotYetLoaded(IDataReader dr, Int32 i, ILoadable persister, EntityKey key, LockMode lockMode, String rowIdAlias, EntityKey optionalObjectKey, Object optionalObject, IList hydratedObjects, ISessionImplementor session) in d:\CSharp\NH\nhibernate\src\NHibernate\Loader\Loader.cs:line 935
at NHibernate.Loader.Loader.GetRow(IDataReader rs, ILoadable[] persisters, EntityKey[] keys, Object optionalObject, EntityKey optionalObjectKey, LockMode[] lockModes, IList hydratedObjects, ISessionImplementor session) in d:\CSharp\NH\nhibernate\src\NHibernate\Loader\Loader.cs:line 867
at NHibernate.Loader.Loader.GetRowFromResultSet(IDataReader resultSet, ISessionImplementor session, QueryParameters queryParameters, LockMode[] lockModeArray, EntityKey optionalObjectKey, IList hydratedObjects, EntityKey[] keys, Boolean returnProxies) in d:\CSharp\NH\nhibernate\src\NHibernate\Loader\Loader.cs:line 322
at NHibernate.Loader.Loader.DoQuery(ISessionImplementor session, QueryParameters queryParameters, Boolean returnProxies) in d:\CSharp\NH\nhibernate\src\NHibernate\Loader\Loader.cs:line 453
at NHibernate.Loader.Loader.DoQueryAndInitializeNonLazyCollections(ISessionImplementor session, QueryParameters queryParameters, Boolean returnProxies) in d:\CSharp\NH\nhibernate\src\NHibernate\Loader\Loader.cs:line 236
at NHibernate.Loader.Loader.LoadEntity(ISessionImplementor session, Object id, IType identifierType, Object optionalObject, String optionalEntityName, Object optionalIdentifier, IEntityPersister persister) in d:\CSharp\NH\nhibernate\src\NHibernate\Loader\Loader.cs:line 1396
at NHibernate.Loader.Entity.AbstractEntityLoader.Load(ISessionImplementor session, Object id, Object optionalObject, Object optionalId) in d:\CSharp\NH\nhibernate\src\NHibernate\Loader\Entity\AbstractEntityLoader.cs:line 42
at NHibernate.Loader.Entity.AbstractEntityLoader.Load(Object id, Object optionalObject, ISessionImplementor session) in d:\CSharp\NH\nhibernate\src\NHibernate\Loader\Entity\AbstractEntityLoader.cs:line 37
at NHibernate.Persister.Entity.AbstractEntityPersister.Load(Object id, Object optionalObject, LockMode lockMode, ISessionImplementor session) in d:\CSharp\NH\nhibernate\src\NHibernate\Persister\Entity\AbstractEntityPersister.cs:line 3436
at NHibernate.Event.Default.DefaultLoadEventListener.LoadFromDatasource(LoadEvent event, IEntityPersister persister, EntityKey keyToLoad, LoadType options) in d:\CSharp\NH\nhibernate\src\NHibernate\Event\Default\DefaultLoadEventListener.cs:line 342
at NHibernate.Event.Default.DefaultLoadEventListener.DoLoad(LoadEvent event, IEntityPersister persister, EntityKey keyToLoad, LoadType options) in d:\CSharp\NH\nhibernate\src\NHibernate\Event\Default\DefaultLoadEventListener.cs:line 320
at NHibernate.Event.Default.DefaultLoadEventListener.Load(LoadEvent event, IEntityPersister persister, EntityKey keyToLoad, LoadType options) in d:\CSharp\NH\nhibernate\src\NHibernate\Event\Default\DefaultLoadEventListener.cs:line 104
at NHibernate.Event.Default.DefaultLoadEventListener.ProxyOrLoad(LoadEvent event, IEntityPersister persister, EntityKey keyToLoad, LoadType options) in d:\CSharp\NH\nhibernate\src\NHibernate\Event\Default\DefaultLoadEventListener.cs:line 160
at NHibernate.Event.Default.DefaultLoadEventListener.OnLoad(LoadEvent event, LoadType loadType) in d:\CSharp\NH\nhibernate\src\NHibernate\Event\Default\DefaultLoadEventListener.cs:line 87
at NHibernate.Impl.SessionImpl.FireLoad(LoadEvent event, LoadType loadType) in d:\CSharp\NH\nhibernate\src\NHibernate\Impl\SessionImpl.cs:line 2457
at NHibernate.Impl.SessionImpl.Get(String entityName, Object id) in d:\CSharp\NH\nhibernate\src\NHibernate\Impl\SessionImpl.cs:line 1336
at NHibernate.Impl.SessionImpl.Get(Type entityClass, Object id) in d:\CSharp\NH\nhibernate\src\NHibernate\Impl\SessionImpl.cs:line 1275
at NHibernate.Impl.SessionImpl.Get[T](Object id) in d:\CSharp\NH\nhibernate\src\NHibernate\Impl\SessionImpl.cs:line 1259
at HealthyLiving.DataAccess.Repositories.PolymorphicRepository`1.FindById[TEntity](Object id) in C:\Users\Ivan\Documents\Dropbox\projects\HealthyLiving\src\HealthyLiving.DataAccess\Repositories\PolymorphicRepository.cs:line 28
at HealthyLiving.DataAccess.Repositories.Repository`1.FindById(Object id) in C:\Users\Ivan\Documents\Dropbox\projects\HealthyLiving\src\HealthyLiving.DataAccess\Repositories\Repository.cs:line 28
at HealthyLiving.Web.Controllers.FoodController.Details(Int32 id) in C:\Users\Ivan\Documents\Dropbox\projects\HealthyLiving\src\HealthyLiving.Web\Controllers\FoodController.cs:line 24
at lambda_method(ExecutionScope , ControllerBase , Object[] )
at System.Web.Mvc.ActionMethodDispatcher.Execute(ControllerBase controller, Object[] parameters)
at System.Web.Mvc.ReflectedActionDescriptor.Execute(ControllerContext controllerContext, IDictionary`2 parameters)
at System.Web.Mvc.ControllerActionInvoker.InvokeActionMethod(ControllerContext controllerContext, ActionDescriptor actionDescriptor, IDictionary`2 parameters)
at System.Web.Mvc.ControllerActionInvoker.<>c_DisplayClassd.<InvokeActionMethodWithFilters>b_a()
at System.Web.Mvc.ControllerActionInvoker.InvokeActionMethodFilter(IActionFilter filter, ActionExecutingContext preContext, Func`1 continuation)
InnerException: System.Runtime.Serialization.SerializationException
Message=End of Stream encountered before parsing was completed.
Source=mscorlib
StackTrace:
at System.Runtime.Serialization.Formatters.Binary.__BinaryParser.Run()
at System.Runtime.Serialization.Formatters.Binary.ObjectReader.Deserialize(HeaderHandler handler, __BinaryParser serParser, Boolean fCheck, Boolean isCrossAppDomain, IMethodCallMessage methodCallMessage)
at System.Runtime.Serialization.Formatters.Binary.BinaryFormatter.Deserialize(Stream serializationStream, HeaderHandler handler, Boolean fCheck, Boolean isCrossAppDomain, IMethodCallMessage methodCallMessage)
at System.Runtime.Serialization.Formatters.Binary.BinaryFormatter.Deserialize(Stream serializationStream)
at NHibernate.Type.SerializableType.FromBytes(Byte[] bytes) in d:\CSharp\NH\nhibernate\src\NHibernate\Type\SerializableType.cs:line 146
InnerException:

Environment

None

Attachments

2

Activity

Show:

Alex Zaytsev 
August 2, 2012 at 8:54 AM

Closed after NH 3.3.1.GA release

Oskar Berggren 
June 3, 2012 at 1:58 PM

Fixed in 3cdbb5d4097de26fb667a93bab5a81e244b39648. Now NHibernate will throw if binary data won't fit into the DB parameter to prevent ADO.NET from truncating. If the driver doesn't set the parameter size, the behaviour is defined by the database engine in use.

Oskar Berggren 
June 3, 2012 at 12:30 PM

One could of course ask, if the dialect will use varbinary(max) for unspecified length, why will the driver use 8000? Sounds like a discrepancy.

Oskar Berggren 
June 3, 2012 at 12:16 PM

The test for NH2484 basically verifies that an image 97kB in size can be stored and retrieved in a property mapped with length=10000, which works because 10000>8000 forces the parameter size to 2G.

NH3121 creates the column as varbinary(max) because the dialect is setup to use varbinary if the length is specified as <= 8000, but varbinary(max) if the length is > 8000 or unspecified, which makes sense if unspecified is interpreted as "no defined limit" (though a string in the same situation would default to 255 characters).

What remains as an issue here then is that NHibernate on MS SQL Server will cause ADO.NET to silently truncate binary data to 8000 bytes (when writing) if the length in the mapping is unspecified or specified as <= 8000 characters. (If a larger value is used, the silent truncation shouldn't happen until the 2G border). This is what was covered for String types in NH-2528.

I believe NHibernate should be save by default, and throw exceptions instead of causing silent truncation (i.e. same solution as in Nh-2528).

Oskar Berggren 
June 3, 2012 at 11:22 AM

I just tried the attached tests (on current 3.3.x branch):
NH3121: columns are created as varbinary(max), exactly 8000 bytes are inserted (which means silent truncation), and those 8000 bytes are returned. The same for both columns.
NH3121_2: Exactly the same as the previous version.

Fixed

Details

Assignee

Reporter

Labels

Components

Fix versions

Affects versions

Priority

Who's Looking?

Open Who's Looking?
Created April 20, 2012 at 11:25 AM
Updated October 28, 2012 at 5:18 PM
Resolved June 6, 2012 at 1:57 PM
Who's Looking?