Skip to content

Exception when using BinaryFormatter to deserialize entities with initialized proxies in associations #2673

Closed
@dimako

Description

@dimako

I've created a little test project which runs on .NET Framework 4.8 and using NHibernate 5.3.5 which throws:

System.ArgumentNullException: 'Value cannot be null.
   at System.Collections.Generic.HashSet`1..ctor(IEnumerable`1 collection, IEqualityComparer`1 comparer)
   at NHibernate.Proxy.NHibernateProxyFactoryInfo.CreateProxyFactory()
   at NHibernate.Proxy.NHibernateProxyObjectReference.GetRealObject(StreamingContext context)
   at System.Runtime.Serialization.ObjectManager.ResolveObjectReference(ObjectHolder holder)
   at System.Runtime.Serialization.ObjectManager.DoFixups()
   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)

The database design is as follows:

create table [Role]
(
    RoleID int,
    [Name] nvarchar(50),
    PRIMARY KEY (RoleID)
)

create table [Resource]
(
    ResourceID int,
    [Name] nvarchar(50),
    RoleID int,
    ManagerID int,
    PRIMARY KEY (ResourceID),
    FOREIGN KEY (RoleID) REFERENCES [Role](RoleID),
    FOREIGN KEY (ManagerID) REFERENCES [Resource](ResourceID)
)

insert [Role] (RoleID, [Name])
values (1, 'role1')
insert [Role] (RoleID, [Name])
values (2, 'role2')

insert [Resource] (ResourceID, [Name], RoleID, ManagerID)
values (1, 'res1', 1, null)

insert [Resource] (ResourceID, [Name], RoleID, ManagerID)
values (2, 'res2', 2, 1)

There are two classes:
using System;

namespace NHibernate.Bug
{
    [Serializable]
    public class Role
    {
        public virtual string Name { get; set; }
        public virtual int Key { get; set; }
    }
}

using System;

namespace NHibernate.Bug
{
    [Serializable]
    public class Resource
    {
        public virtual int Key { get; set; }
        public virtual Resource Manager { get; set; }        
        public virtual string Name { get; set; }
        public virtual Role ResourceRole { get; set; }
    }
}

and their mappings are:

<?xml version="1.0"?>
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2" assembly="NHibernate.Bug"
    namespace="NHibernate.Bug">

  <class name="Role" table="Role">
    <id name="Key">
      <column name="RoleID" sql-type="int" not-null="true"/>
      <generator class="assigned" />
    </id>
    <property name="Name"/>
  </class>

</hibernate-mapping>

<?xml version="1.0"?>
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2" assembly="NHibernate.Bug"
    namespace="NHibernate.Bug">

  <class name="Resource" table="Resource" dynamic-insert="true">
    <id name="Key" column="ResourceID" >
      <generator class="assigned" />
    </id>
    <property name="Name"/>
    <many-to-one name="Manager" class="Resource" column="ManagerID"/>
   
    <many-to-one name="ResourceRole" class="Role" column="RoleID"/>
  </class>

</hibernate-mapping>

The code that is failing is:

var session = CreateSessionFactory().OpenSession();

            // if we don't run this line the test passes
            var resource1 = session.CreateCriteria<Resource>("r").Add(Restrictions.Eq("r.Key", 2)).UniqueResult<Resource>();

            var resource2 = session.QueryOver<Resource>()
                .Where(res => res.Key == 2)
                .Fetch(SelectMode.Fetch, res => res.Manager)
                .SingleOrDefault();

            var serialised = Serialize(resource2);
            var deserialised = Deserialize(serialised);

For some reason it only fails if we execute the criteria query before the queryover one.
Please see below the full fixture code:

using Microsoft.VisualStudio.TestTools.UnitTesting;
using NHibernate.Cfg;
using NHibernate.Criterion;
using System.IO;
using System.Runtime.Serialization.Formatters.Binary;

namespace NHibernate.Bug
{
    [TestClass]
    public class ProxyFixture
    {
        [TestMethod]
        public void ItShouldSerializeAndDeserialize()
        {
            var session = CreateSessionFactory().OpenSession();

            // if we don't run this line the test passes
            var resource1 = session.CreateCriteria<Resource>("r").Add(Restrictions.Eq("r.Key", 2)).UniqueResult<Resource>();

            var resource2 = session.QueryOver<Resource>()
                .Where(res => res.Key == 2)
                .Fetch(SelectMode.Fetch, res => res.Manager)
                .SingleOrDefault();

            var serialised = Serialize(resource2);
            var deserialised = Deserialize(serialised);
        }

        private ISessionFactory CreateSessionFactory()
        {
            var config = new Configuration();
            config.SetProperty(Environment.ConnectionString, "Server=xxx;Database=xxx;User Id=xxx;Password=xxx;");
            config.SetProperty(Environment.ConnectionDriver, "NHibernate.Driver.SqlClientDriver");
            config.SetProperty(Environment.Dialect, "NHibernate.Dialect.MsSql2012Dialect, NHibernate");
            config.SetProperty(Environment.ShowSql, "false");
            config.SetProperty(Environment.DefaultFlushMode, "Commit");
            config.SetProperty(Environment.FormatSql, "false");
            config.SetProperty(Environment.BatchSize, "50");
            config.AddAssembly(GetType().Assembly);              

            return config.BuildSessionFactory();
        }

        protected byte[] Serialize(object value)
        {                     
            var formatter = new BinaryFormatter();
            using (var stream = new MemoryStream())
            {
                formatter.Serialize(stream, value);
                var result = new byte[stream.Length];
                stream.Position = 0;
                stream.Read(result, 0, (int)stream.Length);
                stream.Close();
                return result;
            }
        }

        protected object Deserialize(byte[] state)
        {
            using (MemoryStream stream = new MemoryStream())
            {
                stream.Write(state, 0, state.Length);
                stream.Position = 0;
                var formatter = new BinaryFormatter();
                return formatter.Deserialize(stream);
            }
        }
    }
}

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions