简体   繁体   中英

NHibernate QueryOver with Nested Classes

I am using NHibernate's QueryOver to populate an IEnumerable of a class that has many nested classes. However, this is yielding a lot separate select statements in Sql Profiler. Using the example below, NHibernate selects results from the foo table, then "loops" through each row in the result set, selecting from bar, foobar, etc. Ideally, I would like for this to be one select statement using joins, but I'm not sure if that's possible.

I think the problem may lie with the Not.LazyLoad() methods in the mappings.

Given these classes and mappings:

public class Foo
{
    public virtual int FooId {get; set;}
    public virtual Bar Bar {get; set;}
    public virtual Baz Baz {get; set;}
}

public class Bar
{
    public virtual int BarId {get; set;}
    public virtual FooBar FooBar {get; set;}
    ...
}

public class FooMap : ClassMap<Foo>{
    public FooMap(){
        Schema("my_schema");
        Table("foo");
        LazyLoad();
        Id(x => x.FooId).GeneratedBy.Identity().Column("foo_id");
        References(x => x.Bar).Column("bar").Not.LazyLoad();
        References(x => x.Baz).Column("baz").Not.LazyLoad();
    }
}

public class BarMap : ClassMap<Bar>{
    public BarMap(){
        Schema("my_schema");
        Table("bar");
        LazyLoad();
        Id(x => x.FooId).GeneratedBy.Identity().Column("bar_id");
        References(x => x.FooBar).Column("foo_bar").Not.LazyLoad();
    }
}

I am using queryover in the following way to populate the IEnumerable:

IEnumerable<Foo> foos;

using (ISession session = NHibernateSessionFactoryManager.Factory.OpenSession())
{
    foos = session.QueryOver<foos>()
        .Where(f => f.Baz.BazId == bazId).List();
}

And here's my NHibernateSesssionFactory Manager class:

public class NHibernateSessionFactoryManager {
    private static readonly Object lockObject = new object();
    private static readonly ISessionFactory SessionFactory = BuildSessionFactory();

    private static ISessionFactory BuildSessionFactory(){
        lock (lockObject){//make sure only one session factory is created per start of the app
            return Fluently.Configure()
                .Database(MsSqlConfiguration.MsSql2008
                    .ConnectionString(c => c
                        .FromConnectionStringWithKey("foobardb")))
                .Mappings(m =>
                    m.FluentMappings.AddFromAssemblyOf<FooMap>())
                //.ExposeConfiguration(BuildSchema)
                .BuildSessionFactory();
        }
    }

    private static void BuildSchema(Configuration config) {
        // export the database schema
        new SchemaExport(config)
            .Create(true,false);
    }

    public static ISession OpenSession(){
        return Factory.OpenSession();
    }

    public static ISessionFactory Factory{
        get{
            return SessionFactory;
        }
    }
}

This can be achieved with Futures, find the full code below for your example,

public class FooBar
{
    public virtual int FooBarId { get; set; }
    public virtual string FooBarName { get; set; }
}

public class FooBarMap : ClassMap<FooBar>
{
    public FooBarMap()
    {
        Table("foorbar");
        LazyLoad();
        Id(x => x.FooBarId).GeneratedBy.Identity().Column("foobar_id");
        Map(x => x.FooBarName);
    }
}

public class Bar
{
    public virtual int BarId {get; set;}
    public virtual FooBar FooBar {get; set;}
    public virtual string BarName { get; set; }
}

public class BarMap : ClassMap<Bar>
{
    public BarMap()
    {
        Table("bar");
        LazyLoad();
        Id(x => x.BarId).GeneratedBy.Identity().Column("bar_id");
        Map(x => x.BarName);
        References(x => x.FooBar).Column("foo_bar").Not.LazyLoad();
    }
}

public class Baz
{
    public virtual int BazId {get; set;}
    public virtual string BazName { get; set; }
}

public class BazMap : ClassMap<Baz>
{
    public BazMap()
    {
        Table("baz");
        LazyLoad();
        Id(x => x.BazId).GeneratedBy.Identity().Column("baz_id");
        Map(x => x.BazName);
    }
}

public class Foo
{
    public virtual int FooId { get; set; }
    public virtual Bar Bar { get; set; }
    public virtual Baz Baz { get; set; }
    public virtual string FooName { get; set; }
}

public class FooMap : ClassMap<Foo>
{
    public FooMap()
    {
        Table("foo");
        LazyLoad();
        Id(x => x.FooId).GeneratedBy.Identity().Column("foo_id");
        Map(x => x.FooName);
        References(x => x.Bar).Column("bar").Not.LazyLoad();
        References(x => x.Baz).Column("baz").Not.LazyLoad();
    }
}

This test uses a future query,

[Test]
public void FutureTest()
{
    using (ISession session = _sessionFactory.OpenSession())
    {
        var tx = session.BeginTransaction();
        for (int i = 0; i < 5; i++)
        {
            var fb = new FooBar() {FooBarName = "FooBar_" + i};
            session.Save(fb);
            var bz = new Baz() { BazName = "Baz_" + i};
            session.Save(bz);
            var b = new Bar() { BarName = "Bar_" + i ,FooBar = fb};
            session.Save(b);
            var f = new Foo() { FooName = "Foo_" + i , Bar = b,Baz = bz};
            session.Save(f);
        }
        session.Flush();
        tx.Commit();
    }

    using (ISession session = _sessionFactory.OpenSession())
    {
        Foo fooAlias = null;
        Bar barAlias = null;
        Baz bazAlias = null;
        FooBar fooBarAlias = null;

        var tx = session.BeginTransaction();

        var fooList = session.QueryOver<Foo>(() => fooAlias)
            .JoinAlias(f => f.Bar, () => barAlias)
            .JoinAlias(f => f.Baz, () => bazAlias)
            .JoinAlias(f => f.Bar.FooBar, () => fooBarAlias)
            .Future<Foo>().Distinct().ToList();

        foreach (var foo in fooList)
        {
            Console.WriteLine(foo.FooName);
            Console.WriteLine(foo.Bar.BarName);
            Console.WriteLine(foo.Baz.BazName);
            Console.WriteLine(foo.Bar.FooBar.FooBarName);
        }
        session.Flush();
        tx.Commit();
    }
}

Resulting Single SQL is,

SELECT this_.foo_id as foo1_5_3_, this_.FooName as FooName5_3_, this_.bar as bar5_3_, this_.baz as baz5_3_, baralias1_.bar_id as bar1_1_0_, baralias1_.BarName as BarName1_0_, baralias1_.foo_bar as foo3_1_0_, foobaralia3_.foobar_id as foobar1_4_1_, foobaralia3_.FooBarName as FooBarName4_1_, bazalias2_.baz_id as baz1_2_2_, bazalias2_.BazName as BazName2_2_ FROM foo this_ inner join bar baralias1_ on this_.bar=baralias1_.bar_id inner join foorbar foobaralia3_ on baralias1_.foo_bar=foobaralia3_.foobar_id inner join baz bazalias2_ on this_.baz=bazalias2_.baz_id;

Output,

Foo_0
Bar_0
Baz_0
FooBar_0
Foo_1
Bar_1
Baz_1
FooBar_1
Foo_2
Bar_2
Baz_2
FooBar_2
Foo_3
Bar_3
Baz_3
FooBar_3
Foo_4
Bar_4
Baz_4
FooBar_4

If you what yo load by Join use QueryOver API to make the work:

foos = session.QueryOver<Foo>()
    .JoinAlias(x => x.Bar, () => barAlias)
    .JoinAlias(x => x.Bar, () => barAlias)
    .JoinAlias(() => barAlias.FooBar, () => fooBarAlias)
    .List();

If you what to load by default with your original query change your mapping to Fetch.Join() instead of Not.LazyLoad() .

Be aware of duplicated data, you can read about it here . Or using projections with distinct.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM