如何使用复合键避免使用NHibernate N + 1

时间:2012-01-28 01:08:05

标签: nhibernate queryover select-n-plus-1

编辑我为这一个问题重新制作了整个项目。因此,我重新提出了这个问题。

我希望能够有效地避免N + 1和笛卡尔联接在第三级将一个4级深度实体与复合键连接在一起。

我希望这只能在几个查询中完成,而不是延迟加载,而不只是将所有表连接在一起。

A - (很多) - > B - (很多) - > C - (复合,单一) - > d

类似的东西:

Select * From A Left Join B On A.Id = B.AId
Select * From B Left Join C On B.Id = C.BId Inner Join D On C.DId = D.Id

这是使用的代码 这是一个功能齐全的应用程序。 我使用NuGet安装Sqlite x86,StructureMap,NHProf,Fluent NH。

StructureMapServiceLocator:

namespace MyTest.NHibernateTest
{
using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.Practices.ServiceLocation;
using StructureMap;

public class StructureMapServiceLocator : ServiceLocatorImplBase
{
    private readonly IContainer _container;

    public StructureMapServiceLocator(IContainer container)
    {
        _container = container;
    }

    public IContainer Container { get { return _container; } }

    protected override object DoGetInstance(Type serviceType, string key)
    {
        return string.IsNullOrEmpty(key)
                   ? _container.GetInstance(serviceType)
                   : _container.GetInstance(serviceType, key);
    }

    protected override IEnumerable<object> DoGetAllInstances(Type serviceType)
    {
        return _container.GetAllInstances(serviceType).Cast<object>().AsEnumerable();
    }

    public override TService GetInstance<TService>()
    {
        return _container.GetInstance<TService>();
    }

    public override TService GetInstance<TService>(string key)
    {
        return _container.GetInstance<TService>(key);
    }

    public override IEnumerable<TService> GetAllInstances<TService>()
    {
        return _container.GetAllInstances<TService>();
    }
}
}

AppRegistry

namespace MyTest.NHibernateTest
{
using System;
using System.Collections.Generic;
using System.Linq;
using StructureMap.Configuration.DSL;
using FluentNHibernate.Cfg.Db;
using FluentNHibernate.Cfg;
using NHibernate;
using NHibernate.Tool.hbm2ddl;
using FluentNHibernate.Automapping;
using FluentNHibernate.Data;

public class AppRegistry : Registry
{
    public AppRegistry()
    {
        var dbConfiguration = SQLiteConfiguration.Standard
            .ConnectionString("Data Source=sqlite.db;Version=3;New=True;");
        dbConfiguration.ShowSql();

        var cfg = Fluently.Configure()
            .Database(dbConfiguration)
            .Mappings(m =>
            {
                m.AutoMappings.Add(AutoMap.AssemblyOf<Program>().Where(t =>
                {
                    return typeof(Entity).IsAssignableFrom(t);
                }));
            })
            .ExposeConfiguration(c =>
            {
                if (RebuildSchema.Value)
                    new SchemaExport(c).Create(false, true);
            });
        var sessionFactory = cfg.BuildSessionFactory();

        For<ISessionFactory>().Singleton().Use(sessionFactory);
        For<ISession>().HybridHttpOrThreadLocalScoped().Use(cx =>
        {
            var session = cx.GetInstance<ISessionFactory>().OpenSession();
            session.FlushMode = FlushMode.Commit;

            return session;
        });
    }
}
}

列出实体:

namespace MyTest.NHibernateTest.Entities
{
using System;
using System.Collections.Generic;
using System.Linq;
using FluentNHibernate.Data;

public class Listing : Entity
{
    public Listing()
    {
        Items = new List<ListingItem>();
    }
    public virtual IList<ListingItem> Items { get; set; }
}

public class ListingItem : Entity
{
    public ListingItem()
    {
        Values = new List<ListingItemValue>();
    }
    public virtual IList<ListingItemValue> Values { get; set; }
}

public class ListingItemValue : Entity
{
    public virtual ListingItem ListingItem { get; set; }
    public virtual ListingItemField ListingItemField { get; set; }
}

public class ListingItemField : Entity
{
    public virtual string Value { get; set; }
}
}

程序(控制台):

namespace MyTest.NHibernateTest
{
using System;
using System.Collections.Generic;
using System.Linq;
using StructureMap;
using HibernatingRhinos.Profiler.Appender.NHibernate;
using Microsoft.Practices.ServiceLocation;
using NHibernate;
using System.Threading;
using NHibernate.Transform;
using MyTest.NHibernateTest.Entities;

public static class RebuildSchema
{
    public static bool Value { get; set; }
}

class Program
{
    static void Main(string[] args)
    {
        RebuildSchema.Value = true;
        Setup();
        BuildData();
        Work();
        Console.ReadLine();
    }

    static void Setup()
    {
        NHibernateProfiler.Initialize();

        ObjectFactory.Initialize(x =>
        {
            x.Scan(s =>
            {
                s.TheCallingAssembly();
                s.LookForRegistries();
            });
        });

        ServiceLocator.SetLocatorProvider(() => new StructureMapServiceLocator(ObjectFactory.Container));
    }

    static void BuildData()
    {
        var s = ObjectFactory.GetInstance<NHibernate.ISession>();
        using (var t = s.BeginTransaction())
        {
            var listing = new Listing();
            s.Save(listing);

            var item = new ListingItem();
            listing.Items.Add(item);
            s.Save(item);

            var item2 = new ListingItem();
            listing.Items.Add(item2);
            s.Save(item2);

            var field = new ListingItemField();
            field.Value = "A";
            s.Save(field);

            var field2 = new ListingItemField();
            field2.Value = "B";
            s.Save(field2);

            var value = new ListingItemValue();
            value.ListingItem = item;
            value.ListingItemField = field;
            item.Values.Add(value);
            s.Save(value);

            var value2 = new ListingItemValue();
            value2.ListingItem = item;
            value2.ListingItemField = field2;
            item.Values.Add(value2);
            s.Save(value2);

            var value3 = new ListingItemValue();
            value3.ListingItem = item2;
            value3.ListingItemField = field;
            item2.Values.Add(value3);
            s.Save(value3);

            t.Commit();
        }
    }

    static void Work()
    {
        var s = ObjectFactory.GetInstance<ISession>();
        IList<Listing> foo;
        using (var t = s.BeginTransaction())
        {
            foo = s.QueryOver<Listing>()
                .Left.JoinQueryOver<ListingItem>(x => x.Items)
                .Left.JoinQueryOver<ListingItemValue>(x => x.Values)
                .Left.JoinQueryOver<ListingItemField>(x => x.ListingItemField)
                .TransformUsing(Transformers.DistinctRootEntity)
                .List();
            t.Commit();
        }

        try
        {
            Thread.Sleep(100);
            var x1 = foo[0];
            Thread.Sleep(100);
            var x2 = x1.Items[0];
            Thread.Sleep(100);
            var x3 = x2.Values[0];
            Thread.Sleep(100);
            var x4 = x2.Values[0].ListingItemField.Value;
        }
        catch (Exception) { }
    }
}
}

2 个答案:

答案 0 :(得分:1)

您能否提供您的地图的详细信息。减少查询数量(不是一个,但很少)的一种方法是在映射中使用批量大小的功能。这将使代理人的往返次数减少,而不是N + 1。但实际上应该有一个使用期货或类似方法获取所有数据的解决方案,所以请提供映射。

答案 1 :(得分:0)

这就是我通常做的事情:

首先,您熟悉.Future().FutureValue()吗?有了这些,您可以在一次往返中发送多个查询。这里只有两个问题,所以这不是什么大不了的事,但还是......

我想做的是:

  • 将所有ListingItems及其ValuesFields预取到第一级缓存,以便它们不会触发延迟加载。如您所见,我不在第一个查询中使用变量,因为我不需要存储结果。我只需要运行这个查询并“预取”我的实体。
  • 我可以避开Subquery部分,但Subquery可帮助我避免Listings - Items - Values之间的笛卡尔积。
  • 由于每个Value都有一个Field,因此在第二个查询中,我不会遇到使用笛卡尔积的问题。
  • 然后,只需获取Listing及其Items.Value;部分在单次往返数据库中触发两次查询的“执行”。
  • 结果应该是这个。当我浏览对象图时,所有对象都应该已经处于一级缓存中,并且不会发生延迟加载。

using (var t = s.BeginTransaction())
{
    ListingItem liAlias = null
    ListingItemValue livAlias = null;

    // 'Preload' all ListingItems with their Values and Fields
    s.QueryOver<ListingItem>()
        .JoinAlias(li => li.Values, () => livAlias, JoinType.LeftOuterJoin)
        .Fetch(_ => livAlias.ListingItemField).Eager
        .WithSubquery.WhereProperty(li => li.Id).In(
            QueryOver.Of<Listing>()
                .Where(l => l.Id == id)
                .JoinAlias(l => l.Items, () => liAlias, JoinType.LeftOuterJoin)
                .Select(_ => liAlias.Id)
        )
        .Future();

    // Get a single Listing w/ all its Items
    var listing = s.QueryOver<Listing>()
        .Fetch(l => l.Items).Eager
        .Where(l => l.Id == id)
        .FutureValue()
        .Value;

    t.Commit();
}

我必须在这里说我没有测试过,所以我可能错过了一些东西。其次,我没有考虑你提到的复合键。我不知道这是否会引起任何问题,但我不明白为什么会这样。

请尝试一下,让我知道。