为什么NHibernate会忽略FetchMode.Join?

时间:2011-02-01 13:29:17

标签: nhibernate hibernate fluent-nhibernate nhibernate-mapping

我有一个名为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?

1 个答案:

答案 0 :(得分:0)

我想你可能从错误的角度来看待它。在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的“Name”属性,则SQL语句将保持相同的形状。只有其他投影才会添加到SELECT子句中。但是,您必须修复测试以使其再次通过; - )

注2:如果您愿意处理包含自己属性的多对多关系,则来自post的这个Kyle Baley和此{来自Nhibernate博客的{3}}可能会就此主题提供一些帮助。

注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