为什么 NHibernate 忽略 FetchMode.Join?

发布于 2024-10-15 01:40:10 字数 1596 浏览 1 评论 0 原文

我有一个名为“会员”的实体。一个成员可以关注许多其他成员(根据域),因此属于多对多关系。我在数据库中创建了一个关系表(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?

I have an entity called Member. A Member can follow many other Members (according to the domain), thus pertaining a many-to-many relationship. I've created a relationship table (member_follows) in my database. Using Fluent NHibernate I've also dedicated a new entity "MemberFollow" to map this relationship as seen below:

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

Since the FetchMode for MemberFollow mapping is set to Join, I was expecting this query to fetch the members in one query. However when I look at the logs, I see that NHibernate performs a simple select to find the Ids of each followed member and upon access, loads members one by one.

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

So my question is: Why is NHibernate ignoring the FetchMode set by the mapping class?

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

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

发布评论

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

评论(1

冰之心 2024-10-22 01:40:10

我认为你可能从错误的角度来看待它。在 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 的“Name”属性,您检索 MyMember 的完整实例,SQL 语句保持相同的形状。仅将额外的投影添加到 SELECT 子句中。但是,您必须修复测试才能再次通过;-)

注释 2: 如果您愿意处理拥有自己属性的多对多关系,此帖子来自凯尔·贝利和这个 可能会就此主题提供一些帮助。

注 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

I think you may take it from the wrong angle. In NHibernate, it is quite unusual to explicitly map a many-to-many relationship as a model object. See below a proposal for changes.

Given the domain object MyMember and its overriden mapping:

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

The following test pass:

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

And the produced SQL relies, as expected, on inner joins:


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

Note: If, instead of only retrieving the "Name" property of each MyMember, you retrieve the full instances of MyMember, the SQL statement keeps the same shape. Only additional projections are added to the SELECT clause. However, you'll have to fix the test to make it pass again ;-)

Note 2: Provided you're willing to deal with a many-to-many relationship which holds properties of its own, this post from Kyle Baley and this one from the Nhibernate blog may provide some help on this subject.

Note 3: I've given it a try :-)

Given the domain objects MySecondMember and MyFollowMap and their overriden mapping:

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

The following test pass:

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

And the produced SQL relies, as expected, on inner joins:

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