2

I have the following test support classes.

    public class FixtureBase
    {
        protected SessionSource SessionSource { get; set; }
        protected ISession Session { get; private set; }

        [TestFixtureSetUp]
        public void SetupFixture()
        {
            var cfg = Fluently.Configure().Database(SQLiteConfiguration.Standard.ShowSql().InMemory);
            SessionSource = new SessionSource(cfg.BuildConfiguration().Properties, new TestModel());
        }

        [SetUp]
        public void SetupContext()
        {
            Session = SessionSource.CreateSession();
            SessionSource.BuildSchema(Session);
        }

        [TearDown]
        public void TearDownContext()
        {
            Session.Close();
            Session.Dispose();
        }
    }

    public TestModel()
    {
        this.AddMappingsFromAssembly(typeof(StudentMap).Assembly);
    }

And a very simple test class that is supposed to test the linking class map in a many to many relationship.

[TestFixture]
public class StudentGuardianAssociationMap_Fixture : FixtureBase
{
    [Test]
    public void can_correctly_map_studentguardianassociation()
    {
        Guardian guardian = new Guardian
                                {
                                    FirstName = "GuardianFName",
                                    LastName = "GuardianLName",
                                    NameSuffix = "GuardianSuffix",
                                    MiddleName = "GuardianMiddleName"
                                };

        Student student = new Student
                              {
                                  FirstName = "StudentFName",
                                  LastName = "StudentLName",
                                  MiddleName = "StudentMiddleName",
                                  Address1 = "StudentAddress1",
                                  Address2 = "StudentAddress2",
                                  City = "StudentCity",
                                  State = "MO",
                                  PostalCode = "12345-2342"
                              };   

        new PersistenceSpecification<StudentGuardianAssociation>(Session)
            .CheckProperty(x => x.RelationShipToStudent, 1)
            .CheckReference(x => x.AssociatedStudent, student)
            .CheckReference(x => x.AssociatedGuardian, guardian)
            .VerifyTheMappings();
    }
}

When running this test I recieve the following exception.

System.ApplicationException : For property 'AssociatedStudent' expected 'Pats.DataTransfer.Student' of type 'Pats.DataTransfer.Student' but got '' of type 'Pats.DataTransfer.Student'

Some research shows that this sort of error commonly occurs because when you fail to override object equality in your DTOs, however, I am currently implementing a very rudimentary object equality based on id in my DTO base class.

    public class DataTranfserObject<TDto> where TDto : DataTranfserObject<TDto>
    {
        private int? _oldHashCode;

        public virtual int Id { get; set; }
        public virtual int Version { get; set; }

        public override int GetHashCode()
        {
            // Once we have a hash code we'll never change it
            if (_oldHashCode.HasValue)
            {
                return _oldHashCode.Value;
            }

            var thisIsTransient = Equals(Id, 0);

            // When this instance is transient, we use the base GetHashCode()
            // and remember it, so an instance can NEVER change its hash code.
            if (thisIsTransient)
            {
                _oldHashCode = base.GetHashCode();
                return _oldHashCode.Value;
            }
            return Id.GetHashCode();
        }

        public override bool Equals(object obj)
        {
            var other = obj as TDto;
            if (other == null)
            {
                return false;
            }

            // handle the case of comparing two NEW objects
            var otherIsTransient = Equals(other.Id, 0);
            var thisIsTransient = Equals(Id, 0);

            if (otherIsTransient && thisIsTransient)
            {
                return ReferenceEquals(other, this);
            }
            return other.Id.Equals(Id);
        }
    }

And my mapping table's object representation.

    public class StudentGuardianAssociation : DataTranfserObject<StudentGuardianAssociation>
    {
        public virtual int RelationShipToStudent { get; set; }

        public virtual Student AssociatedStudent { get; set; }
        public virtual Guardian AssociatedGuardian { get; set; }
    }

And lastly my map for good measure.

public StudentGuardianAssociationMap() 
    {
        LazyLoad();

    this.Table("StudentGuardians");

    this.Id(x => x.Id).GeneratedBy.HiLo("100").UnsavedValue(0);

    this.Version(x => x.Version).Column("Version");

    Map(x => x.RelationShipToStudent).Column("RelationshipToStudent").Not.Nullable();

    References(x => x.AssociatedGuardian).Not.Nullable();
    References(x => x.AssociatedStudent).Not.Nullable();
}

I'm still pretty new to nhibernate and the fluent api, but I have successfully gotten my student and guardian maps to pass their tests. Though I have not yet included the tests for their HasMany Associations portion.

Bottom line, what causes the

System.ApplicationException : For property 'AssociatedStudent' expected 'Pats.DataTransfer.Student' of type 'Pats.DataTransfer.Student' but got '' of type 'Pats.DataTransfer.Student'

exception to be thrown during my test, and what strategy should I take to correct it.

EDIT


This is the stack trace information returned by nunit.

at FluentNHibernate.Testing.Values.Property`2.CheckValue(Object target) in d:\Builds\FluentNH\src\FluentNHibernate\Testing\Values\Property.cs: line 91
at FluentNHibernate.Testing.PersistenceSpecification`1.c__DisplayClass2.b__1(Property`1 p) in d:\Builds\FluentNH\src\FluentNHibernate\Testing\PersistenceSpecification.cs: line 64
at System.Collections.Generic.List`1.ForEach(Action`1 action)
at FluentNHibernate.Testing.PersistenceSpecification`1.VerifyTheMappings(T first) in d:\Builds\FluentNH\src\FluentNHibernate\Testing\PersistenceSpecification.cs: line 64
at FluentNHibernate.Testing.PersistenceSpecification`1.VerifyTheMappings() in d:\Builds\FluentNH\src\FluentNHibernate\Testing\PersistenceSpecification.cs: line 40
at Pats.DataAccessTest.StudentGuardianAssociationMap_Fixture.can_correctly_map_studentguardianassociation() in StudentGuardianAssociationMap_Fixture.cs: line 37 

And the sql executed in case there are any insights there.

NHibernate: select next_hi from hibernate_unique_key
NHibernate: update hibernate_unique_key set next_hi = @p0 where next_hi = @p1;@p0 = 2, @p1 = 1
NHibernate: INSERT INTO Students (Version, FirstName, LastName, MiddleName, NameSuffix, Address1, Address2, City, State, PostalCode, Id) VALUES (@p0, @p1, @p2, @p3, @p4, @p5, @p6, @p7, @p8, @p9, @p10);@p0 = 1, @p1 = 'StudentFName', @p2 = 'StudentLName', @p3 = 'StudentMiddleName', @p4 = NULL, @p5 = 'StudentAddress1', @p6 = 'StudentAddress2', @p7 = 'StudentCity', @p8 = 'MO', @p9 = '12345-2342', @p10 = 1001
NHibernate: select next_hi from hibernate_unique_key
NHibernate: update hibernate_unique_key set next_hi = @p0 where next_hi = @p1;@p0 = 3, @p1 = 2
NHibernate: INSERT INTO Guardians (Version, FirstName, LastName, MiddleName, NameSuffix, Id) VALUES (@p0, @p1, @p2, @p3, @p4, @p5);@p0 = 1, @p1 = 'GuardianFName', @p2 = 'GuardianLName', @p3 = 'GuardianMiddleName', @p4 = 'GuardianSuffix', @p5 = 2002
NHibernate: select next_hi from hibernate_unique_key
NHibernate: update hibernate_unique_key set next_hi = @p0 where next_hi = @p1;@p0 = 4, @p1 = 3
NHibernate: INSERT INTO StudentGuardians (Version, RelationshipToStudent, Id) VALUES (@p0, @p1, @p2);@p0 = 1, @p1 = 1, @p2 = 3003
NHibernate: SELECT studentgua0_.Id as Id1_2_, studentgua0_.Version as Version1_2_, studentgua0_.RelationshipToStudent as Relation3_1_2_, student1_.Id as Id2_0_, student1_.Version as Version2_0_, student1_.FirstName as FirstName2_0_, student1_.LastName as LastName2_0_, student1_.MiddleName as MiddleName2_0_, student1_.NameSuffix as NameSuffix2_0_, student1_.Address1 as Address7_2_0_, student1_.Address2 as Address8_2_0_, student1_.City as City2_0_, student1_.State as State2_0_, student1_.PostalCode as PostalCode2_0_, guardian2_.Id as Id0_1_, guardian2_.Version as Version0_1_, guardian2_.FirstName as FirstName0_1_, guardian2_.LastName as LastName0_1_, guardian2_.MiddleName as MiddleName0_1_, guardian2_.NameSuffix as NameSuffix0_1_ FROM StudentGuardians studentgua0_ left outer join Students student1_ on studentgua0_.Id=student1_.Id left outer join Guardians guardian2_ on studentgua0_.Id=guardian2_.Id WHERE studentgua0_.Id=@p0;@p0 = 3003

Thanks for wading through my wall of text, I just wanted to be thorough about what I am trying to accomplish, and what I have done thus far.

Matthew Vines
  • 27,253
  • 7
  • 76
  • 97
  • Aren't you comparing the Id, which is an int, to Guid.Empty? That doesn't make much sense... – flq Dec 15 '10 at 22:34
  • @flq - Great catch. I forgot to update that class when I changed from Combs to HiLo. Unfortunately fixing the equality method did not resolve my exception. In fact, the exception is thrown before equality is even checked. As my break points in those methods are never hit. – Matthew Vines Dec 15 '10 at 22:44
  • I really want to figure this out. If anyone is willing to have a look at it all together I'll gladly send my project over, it only contains the classes shown here, and Guardian and Student files. – Matthew Vines Dec 16 '10 at 02:44

2 Answers2

2

Try using the overloaded PersistenceSpecification constructor that takes an IEqualityComparer to compare the child objects (AssociatedGuardian, AssociatedStudent). The persistence specification test compares two different references (original and retrieved), so they will have different hash codes with your implementation. As I understand it, comparisons made using an IEqualityComparer firsts compare the hash codes then check Equals if the hash code matches. My guess is that PersistenceSpecification is wrapping the Equals call in an IEqualityComparer implementation.

I use a utility class to make this easier:

public class PersistenceSpecificationEqualityComparer : IEqualityComparer
{
    private readonly Dictionary<Type, Delegate> _comparers = new Dictionary<Type, Delegate>();

    public void RegisterComparer<T>(Func<T, object> comparer)
    {
        _comparers.Add(typeof(T), comparer);
    }

    public bool Equals(object x, object y)
    {
        if (x == null || y == null)
        {
            return false;
        }
        var xType = x.GetType();
        var yType = y.GetType();
        // check subclass to handle proxies
        if (_comparers.ContainsKey(xType) && (xType == yType || yType.IsSubclassOf(xType)))
        {
            var comparer = _comparers[xType];
            var xValue = comparer.DynamicInvoke(new[] {x});
            var yValue = comparer.DynamicInvoke(new[] {y});
            return xValue.Equals(yValue);
        }
        return x.Equals(y);
    }

    public int GetHashCode(object obj)
    {
        throw new NotImplementedException();
    }
}

Usage:

var comparer = new PersistenceSpecificationEqualityComparer();
comparer.RegisterComparer((Guardian x) => x.Id);
comparer.RegisterComparer((Student x) => x.Id);
// etc.

new PersistenceSpecification<StudentGuardianAssociation>(Session, comparer)
    .CheckProperty(x => x.RelationShipToStudent, 1)
    .CheckReference(x => x.AssociatedStudent, student)
    .CheckReference(x => x.AssociatedGuardian, guardian)
    .VerifyTheMappings();

EDIT:

I think I see the problem: guardian and student are not persisted in the session and there's no cascade that would do it automatically. Saving those objects before running the PersistenceSpecification should fix it. You can see the code for the CheckValue method in this answer. In looking at your question again, the error message states "but got '' of type 'Pats.DataTransfer.Student'" indicating that the value is null.

Assuming that's the solution, I am curious if you still need the IEqualityComparer. Profiling using a tool such as NHProf would have caught this quickly because you would see the nulls in the insert.

Community
  • 1
  • 1
Jamie Ide
  • 48,427
  • 16
  • 81
  • 117
  • Thank you for your input. I added the custom comparer to my tests project and used the overload for PersistenceSpecification that utilized it. But unfortunately, it did not resolve the exception. If I place break points in GetHashCode, and Equals in my base class, they are never hit. So the exception is being thrown prior to the equality check. I think it is when the Student object is being added to database, but it's hard to say, as CheckReference does so much, adding the object to the database, and retrieving it again, and then comparing the instances. – Matthew Vines Dec 16 '10 at 02:31
  • I added the stack trace information to my question. Unfortunately, it doesn't give me any insights into where to look for this. But I have only been experimenting with nhibernate for a few days now. – Matthew Vines Dec 16 '10 at 02:38
  • Thanks for taking a second look Jamie. I tried what you suggested, but still to no avail. However, I do think I may be on to the source of the issue. I am on a 64 bit machine, and have had a lot of trouble getting System.Data.Sqlite.dll to work correctly for me. In fact the only solution I have that has worked, is to build my test project in explicit x86 mode. I don't toy with that setting too often, but as I look more closely at my Equals method breakpoints, they are never hit because the debugger believes that the file has been modified. Have you had any success with 64bit sqlite? How? – Matthew Vines Dec 16 '10 at 14:41
  • I finally got it working, see my answer below. I wish I could gift some rep to you for your time and effort in resolving this, I learned quite a bit researching based on your recommendations. Thanks again. – Matthew Vines Dec 16 '10 at 16:33
1

I did finally resolve this, it turns out that the code is not the issue in my case, but instead, I had used a 32 bit version of the System.Data.Sqlite.dll and compiled my test assembly as x86 to get it to work correctly, and my other assemblies as AnyCPU, which on my system meant 64 bit. For some reason this worked for some things, but began to manifest itself in the CheckValue call, which was throwing me off the scent a bit.

I was able to locate a 64 bit dll for Sqlite at http://sqlite.phxsoftware.com/ that works beautifully.

And now life is good with no custom IEqualityComparer, or explicit persisting of the objects before hand with the session.

Matthew Vines
  • 27,253
  • 7
  • 76
  • 97
  • Sorry I took you on such a detour but I'm glad you resolved it. Sqlite is handing for testing but it does have its quirks. – Jamie Ide Dec 17 '10 at 00:59