【问题标题】:Why is NHibernate ignoring FetchMode.Join?为什么 NHibernate 忽略 FetchMode.Join?
【发布时间】:2011-02-04 22:02:54
【问题描述】:

我有一个名为 Member 的实体。一个成员可以跟随许多其他成员(根据域),因此属于多对多关系。我在我的数据库中创建了一个关系表 (member_follows)。使用 Fluent NHibernate,我还专门创建了一个新实体“MemberFollow”来映射这种关系,如下所示:

public class MemberMap : MapBase<Member>
{
    public MemberMap() 
        : base()
    {
        Table("members");

        Map(x => x.Id      ).Column("id"      );
        Map(x => x.Fullname).Column("fullname");
}

public class MemberFollowMap : MapBase<MemberFollow>
{
    public MemberFollowMap()
        : base()
    {
        Table("members_follows");

        Map(x => x.Id).Column("id");

        References<Member>(x => x.Follower)
            .Column("follower_id")
            .Fetch.Join();

        References<Member>(x => x.Member)
            .Column("member_id");
            .Fetch.Join();
    }
}

由于 MemberFollow 映射的 FetchMode 设置为 Join,我希望此查询能够在一个查询中获取成员。但是,当我查看日志时,我看到 NHibernate 执行了一个简单的选择来查找每个被关注成员的 Id,并在访问时一一加载成员。

    public IList<Member> ListFollowings(Int32 FollwerId, Int32 Start, Int32 Size, String SortBy, SortOrder OrderBy)
    {
        DetachedCriteria Filter = DetachedCriteria.For<MemberFollow>();

        Filter.Add           (Expression.Eq("Follower.Id", FollwerId));
        Filter.AddOrder      (OrderBy == SortOrder.Asc ? Order.Asc(SortBy) : Order.Desc(SortBy));
        Filter.SetProjection (Projections.Property("Member"));
        Filter.SetFirstResult(Start);
        Filter.SetMaxResults (Size ); 

        return Find<Member>(Filter); 
    }

所以我的问题是:为什么 NHibernate 会忽略映射类设置的 FetchMode?​​p>

【问题讨论】:

    标签: nhibernate hibernate fluent-nhibernate nhibernate-mapping


    【解决方案1】:

    我认为你可能从错误的角度来理解它。在 NHibernate 中,将多对多关系显式映射为模型对象是非常不寻常的。请参阅下面的更改建议。

    给定域对象MyMember 及其覆盖的映射:

    public class MyMember : DomainObjectBase
    {
        public virtual string Name { get; set; }
        public virtual IList<MyMember> Follows { get; set; }
    }
    
    public class MemberOverride : IAutoMappingOverride<MyMember>
    {
        public void Override(AutoMapping mapping)
        {
            mapping.HasManyToMany<MyMember> (x => x.Follows)
                .Table("FollowMap")
                .ParentKeyColumn("FollowerID")
                .ChildKeyColumn("FollowedID")
                .Cascade.SaveUpdate(); ;
       }
    }

    以下测试通过:

    [Test]
    public void WhoFollowsWho()
    {
        var a = new MyMember {Name = "A"};
        var b = new MyMember {Name = "B"};
        var c = new MyMember {Name = "C"};
        var d = new MyMember {Name = "D"};
        var e = new MyMember {Name = "E"};
    
        a.Follows = new List<MyMember> { b, c, d, e };
        d.Follows = new List<MyMember> { a, c, e };
    
        using (var t = Session.BeginTransaction())
        {
            Session.Save(a);
            Session.Save(b);
            Session.Save(c);
            Session.Save(d);
            Session.Save(e);
    
            t.Commit();
        }
    
        using (var t = Session.BeginTransaction())
        {
            DetachedCriteria followersOfC = DetachedCriteria.For<MyMember>();
    
            followersOfC.CreateCriteria("Follows")
                 .Add(Expression.Eq("Id", c.Id))
                 .SetProjection(Projections.Property("Name"));
    
            var results = followersOfC.GetExecutableCriteria(Session).List();
    
            t.Commit();
            CollectionAssert.AreEquivalent(new[]{"A", "D"}, results);
        }
    
        using (var t = Session.BeginTransaction())
        {
            DetachedCriteria followedByA = DetachedCriteria.For<MyMember>();
    
            followedByA.CreateAlias("Follows", "f")
                 .Add(Expression.Eq("Id", a.Id))
                 .SetProjection(Projections.Property("f.Name"));
    
            var results = followedByA.GetExecutableCriteria(Session).List();
    
            t.Commit();
            CollectionAssert.AreEquivalent(new[]{"B", "C", "D", "E"}, results);
        }
    }

    生成的 SQL 和预期一样依赖于内部连接:

    
    NHibernate: 
    SELECT this_.Name as y0_ FROM "MyMember" this_ 
    inner join FollowMap follows3_ on this_.Id=follows3_.FollowerID 
    inner join "MyMember" mymember1_ on follows3_.FollowedID=mymember1_.Id 
    WHERE mymember1_.Id = @p0
    
    NHibernate: 
    SELECT f1_.Name as y0_ FROM "MyMember" this_ 
    inner join FollowMap follows3_ on this_.Id=follows3_.FollowerID 
    inner join "MyMember" f1_ on follows3_.FollowedID=f1_.Id 
    WHERE this_.Id = @p0

    注意:如果不是只检索每个 MyMember 的“名称”属性,而是检索 MyMember 的完整实例,则 SQL 语句将保持相同的形状。只有额外的投影被添加到 SELECT 子句。但是,您必须修复测试以使其再次通过;-)

    注意 2: 如果您愿意处理拥有自己属性的多对多关系,则此 post 来自 Kyle Baley 和此 one 来自Nhibernate 博客可能会在此主题上提供一些帮助。

    注意 3:我已经尝试过了 :-)

    给定域对象 MySecondMemberMyFollowMap 及其覆盖的映射:

    public class MySecondMember : DomainObjectBase
    {
        public virtual string Name { get; set; }
        public virtual IList<MyFollowMap> Follows { get; set; }
    }
    
    
    public class MyFollowMap : DomainObjectBase
    {
        public virtual MySecondMember Who { get; set; }
        public virtual DateTime StartedToFollowOn { get; set; }
    }
    
    public class MemberSecondOverride : IAutoMappingOverride<MySecondMember>
    {
        public void Override(AutoMapping mapping)
        {
            mapping.HasMany(x => x.Follows);
        }
    }

    以下测试通过:

    [Test]
    public void WhoFollowsWho2()
    {
    
        var a = new MySecondMember { Name = "A" };
        var b = new MySecondMember { Name = "B" };
        var c = new MySecondMember { Name = "C" };
        var d = new MySecondMember { Name = "D" };
        var e = new MySecondMember { Name = "E" };
    
        var bfm = new MyFollowMap { Who = b, StartedToFollowOn = DateTime.UtcNow };
        var cfm = new MyFollowMap { Who = c, StartedToFollowOn = DateTime.UtcNow };
        var dfm = new MyFollowMap { Who = d, StartedToFollowOn = DateTime.UtcNow };
        var efm = new MyFollowMap { Who = e, StartedToFollowOn = DateTime.UtcNow };
    
    
        a.Follows = new List { bfm, cfm, dfm, efm };
    
        var afm = new MyFollowMap { Who = a, StartedToFollowOn = DateTime.UtcNow };
        cfm = new MyFollowMap { Who = c, StartedToFollowOn = DateTime.UtcNow };
        efm = new MyFollowMap { Who = e, StartedToFollowOn = DateTime.UtcNow };
    
        d.Follows = new List { afm, cfm, efm };
    
    
        using (var t = Session.BeginTransaction())
        {
            Session.Save(a);
            Session.Save(b);
            Session.Save(c);
            Session.Save(d);
            Session.Save(e);
    
            t.Commit();
        }
    
        using (var t = Session.BeginTransaction())
        {
            DetachedCriteria followersOfC = DetachedCriteria.For<MySecondMember>();
    
            followersOfC.CreateAlias("Follows", "f")
                .CreateAlias("f.Who", "w")
                .Add(Expression.Eq("w.Id", c.Id))
                .SetProjection(Projections.Property("Name"));
    
            var results = followersOfC.GetExecutableCriteria(Session).List();
            t.Commit();
            CollectionAssert.AreEquivalent(new[] { "A", "D" }, results);
    
        }
    
        using (var t = Session.BeginTransaction())
        {
            DetachedCriteria followedByA = DetachedCriteria.For<MySecondMember>();
    
            followedByA
                .CreateAlias("Follows", "f")
                .CreateAlias("f.Who", "w")
                .Add(Expression.Eq("Id", a.Id))
                .SetProjection(Projections.Property("w.Name"));
    
            var results = followedByA.GetExecutableCriteria(Session).List();
            t.Commit();
            CollectionAssert.AreEquivalent(new[] { "B", "C", "D", "E" }, results);
        }
    }

    生成的 SQL 和预期一样依赖于内部连接:

    NHibernate: 
    SELECT this_.Name as y0_ FROM "MySecondMember" this_ 
    inner join "MyFollowMap" f1_ on this_.Id=f1_.MySecondMember_id 
    inner join "MySecondMember" w2_ on f1_.Who_id=w2_.Id 
    WHERE w2_.Id = @p0;
    
    NHibernate: 
    SELECT w2_.Name as y0_ FROM "MySecondMember" this_ 
    inner join "MyFollowMap" f1_ on this_.Id=f1_.MySecondMember_id 
    inner join "MySecondMember" w2_ on f1_.Who_id=w2_.Id 
    WHERE this_.Id = @p0
    

    【讨论】:

    • Em,你是对的,多对多关系的显式映射很少以这种方式完成,但它背后的智慧是当关系包含许多记录时。使用您的方法,我同意这是通常的方法,NHibernate 将完全控制查询的执行,正如您在上面显示的那样,只需加载每条记录。另一方面,使用我的模型,我可以针对单个记录并进行更智能的查询。此外,这种显式映射为我的其他报告查询提供了一个有意义的位置来调查这些关系。
    • @Am:为了尝试改进我的答案,您能否提供一些您的智能查询示例?
    猜你喜欢
    • 2016-08-16
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-04-23
    • 2021-11-06
    • 2019-03-22
    • 2017-07-07
    • 2012-01-30
    相关资源
    最近更新 更多