单元测试流畅的 NHibernate。需要帮助理解测试期间发生的异常

发布于 2024-10-08 00:20:01 字数 8567 浏览 2 评论 0原文

我有以下测试支持课程。

    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);
    }

一个非常简单的测试类,应该测试多对多关系中的链接类映射。

[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();
    }
}

运行此测试时,我收到以下异常。

System.ApplicationException:对于 预期属性“AssociatedStudent” 类型为“Pats.DataTransfer.Student” 'Pats.DataTransfer.Student' 但得到 '' 类型为“Pats.DataTransfer.Student”

这种错误通常会发生,因为当您无法覆盖 DTO 中的对象相等性时,我目前正在基于 DTO 基类中的 id 实现非常基本的对象相等性。

    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);
        }
    }

以及我的映射表的对象表示。

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

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

最后是我的地图。

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();
}

我对 nhibernate 和 Fluent api 还很陌生,但我已经成功让我的学生和监护人地图通过了他们的测试。尽管我还没有包含 HasMany Associations 部分的测试。

归根结底,是什么原因导致

System.ApplicationException:对于 预期属性“AssociatedStudent” 类型为“Pats.DataTransfer.Student” 'Pats.DataTransfer.Student' 但得到 '' 类型为“Pats.DataTransfer.Student”

在我的测试期间抛出

异常,以及我应该采取什么策略来纠正它。编辑


这是 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 

以及执行的 SQL,以防有任何见解。

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

感谢您翻阅我的文字墙,我只是想彻底了解我正在努力实现的目标以及迄今为止我所做的事情。

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.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

冷月断魂刀 2024-10-15 00:20:01

尝试使用重载的 PersistenceSpecification 构造函数,该构造函数采用 IEqualityComparer 来比较子对象(AssociatedGuardian、AssociatedStudent)。持久性规范测试会比较两个不同的引用(原始引用和检索到的引用),因此它们将具有与您的实现不同的哈希代码。据我了解,使用 IEqualityComparer 进行的比较首先比较哈希代码,然后检查等于是否哈希代码匹配。我的猜测是 PersistenceSpecification 将 Equals 调用包装在 IEqualityComparer 实现中。

我使用实用程序类来使这更容易:

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();
    }
}

用法:

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();

编辑:

我想我看到了问题:监护人和学生不会保留在会话中,并且没有级联可以自动执行此操作。在运行 PersistenceSpecification 之前保存这些对象应该可以修复它。您可以在此答案中查看 CheckValue 方法的代码。再次查看您的问题时,错误消息显示“but got '' of type 'Pats.DataTransfer.Student'”,表明该值为空。

假设这就是解决方案,我很好奇您是否仍然需要 IEqualityComparer。使用 NHProf 等工具进行分析会很快发现这一点,因为您会在插入中看到空值。

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.

放血 2024-10-15 00:20:01

我终于解决了这个问题,事实证明代码不是我的问题,而是我使用了 32 位版本的 System.Data.Sqlite.dll 并将我的测试程序集编译为 x86 以使其能够工作正常,我的其他程序集为 AnyCPU,这在我的系统上意味着 64 位。出于某种原因,这对某些事情有效,但开始在 CheckValue 调用中显现出来,这让我有点迷失了方向。

我能够在以下位置找到 Sqlite 的 64 位 dll
http://sqlite.phxsoftware.com/
效果很好。

现在,生活很美好,没有自定义 IEqualityComparer,也没有在会话之前显式保留对象。

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.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文