Почему NHibernate игнорирует FetchMode.Join? - PullRequest
0 голосов
/ 01 февраля 2011

У меня есть объект с именем 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();
    }
}

Поскольку FetchMode для сопоставления MemberFollow установлено на Join, я ожидал, что этот запрос получитучастники в одном запросе.Однако когда я просматриваю журналы, я вижу, что NHibernate выполняет простой выбор, чтобы найти идентификаторы каждого последующего участника и при доступе загружает участников по одному.

    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 голосов
/ 04 февраля 2011

Я думаю, вы можете принять это под неправильным углом.В 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: При условии, что вы готовы иметь дело с отношением "многие ко многим", которое имеет местособственные свойства, эта запись от Кайл Бэйли и эта одна из блога Nhibernate могут оказать некоторую помощь по этому вопросу.

Примечание 3: Я попробовал: -)

Учитывая объекты домена MySecondMember и MyFollowMap и их переопределенное отображение:

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
...