Scott
Scott

Reputation: 11

Lazy Loading child collection with ToFuture

I'm currently using c# with Nhibernate 3.2 hitting a SqlServer database, and I am trying to work with multiquery's and Futures to load a child collection.

Anyways I can get it to work using Linq to Nhibernate, but when viewing the sql is sent to the database it looks as if it is loading all of the parent objects in addition to the child objects for the child collection fetch (Like it is eager loading). I was curious if it was possible to change this behavior to only pull the needed child object columns.

Here is an example of code that illustrates this issue.

  public class Parent : Entity
   {
      public virtual string Name { get; set; }
      public virtual IList<Child> Children { get; set; }
   }


public class Child : Entity
   {
      public virtual int Age { get; set; }
      public virtual string Name { get; set; }
      public virtual Parent Parent { get; set; }
   }


public class ChildClassMap : ClassMap<Child>
   {
      public ChildClassMap()
      {
         Id(x => x.Id,"Id");
         Map(x => x.Age);
         Map(x => x.Name);
         this.References(x => x.Parent).Column("ParentId").ForeignKey("Id");
      }
   }


 public class ParentClassMap : ClassMap<Parent>
   {
      public ParentClassMap()
      {
         Id(x => x.Id, "Id");
         Map(x => x.Name);
         this.HasMany(x => x.Children).KeyColumn("ParentId");
      }
   }



  public class FamilyRepository : NHibernateRepository<Parent>
   {
      public Parent GetParent(int id)
      {
         using (var session = this.Session.OpenSession())
         {
            var parent = session.Query<Parent>()
               .Where(p => p.Id == id);

            parent.FetchMany(x => x.Children)
               .ToFuture();

            return parent.ToFuture().SingleOrDefault();
         }
      }
   }

Test Case

   [TestClass]
   public class FamilyTests
   {
      [TestMethod]
      public void Should_Get_Parent_And_Children()
      {
         // arrange
         var repo = new FamilyRepository();

         // act
         var parent = repo.GetParent(1);

         // assert
         Assert.AreNotEqual(null, parent);
         Assert.AreEqual("TheOldOne", parent.Name);
         Assert.AreEqual(3, parent.Children.Count);
         Assert.AreEqual(4, parent.Children[1].Age);
         Assert.AreEqual("TheMiddleOne", parent.Children[1].Name);

      }
   }

Sql:

CREATE TABLE [dbo].[Parent](
   [Id] [int] IDENTITY(1,1) NOT NULL,
   [Name] [varchar](50) NOT NULL,
CONSTRAINT [PK_Parent] PRIMARY KEY CLUSTERED 
(
   [Id] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY]
GO

CREATE TABLE [dbo].[Child](
   [Id] [int] IDENTITY(1,1) NOT NULL,
   [ParentId] [int] NOT NULL,
   [Age] [int] NOT NULL,
   [Name] [varchar](50) NOT NULL,
CONSTRAINT [PK_Child] PRIMARY KEY CLUSTERED 
(
   [Id] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY]

GO

SET ANSI_PADDING OFF
GO

ALTER TABLE [dbo].[Child]  WITH CHECK ADD  CONSTRAINT [FK_Child_Parent] FOREIGN KEY([ParentId])
REFERENCES [dbo].[Parent] ([Id])
GO

ALTER TABLE [dbo].[Child] CHECK CONSTRAINT [FK_Child_Parent]
GO


Set Identity_Insert [dbo].[Parent] on 
insert into [dbo].[Parent]
(Id, Name)
values (1, 'TheOldOne');

insert into [dbo].[Parent]
(Id, Name)
values (2, 'TheOtherOne');
Set Identity_Insert [dbo].[Parent] off
GO

Set Identity_Insert [dbo].[Child] on
insert into [dbo].[Child]
(Id, ParentId, Age, Name)
values(1,1,3,'TheYoungOne')


insert into [dbo].[Child]
(Id, ParentId, Age, Name)
values(2,1,4,'TheMiddleOne')


insert into [dbo].[Child]
(Id, ParentId, Age, Name)
values(3,1,7,'TheFirstOne')
Set Identity_Insert [dbo].[Child] off

The output from the sql profiler is:

exec sp_executesql N'
    select parent0_.Id as Id3_0_, children1_.Id as Id2_1_, parent0_.Name as Name3_0_, children1_.Age as Age2_1_, children1_.Name as Name2_1_, children1_.ParentId as ParentId2_1_, children1_.ParentId as ParentId0__, children1_.Id as Id0__ 
    from [Parent] parent0_ left outer join [Child] children1_ on parent0_.Id=children1_.ParentId where parent0_.Id=@p0;
    select parent0_.Id as Id3_, parent0_.Name as Name3_ from [Parent] parent0_ where parent0_.Id=@p1;
',N'@p0 bigint,@p1 bigint',@p0=1,@p1=1

Does anyone have any suggestions?

thanks for your time

Upvotes: 1

Views: 1447

Answers (1)

Firo
Firo

Reputation: 30803

just shorten the code to

public Parent GetParentWithChildrenInitialised(int id)
{
    using (var session = SessionFactory.OpenSession())
    {
        return session.Query<Parent>()
           .Where(p => p.Id == id)
           .FetchMany(x => x.Children)
           .SingleOrDefault();
    }
}

I personaly would get rid of the repository because it adds needless abstractions and makes it harder to tune performance, ISession is already like a respository.

A better alternative is to use session.Get(parentId); because this uses the lvl1/session cache or the query above if the children are needed.

Also use the sessionfactory to create sessions because it is threadsafe, sessions are not.

Upvotes: 1

Related Questions