19

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

我希望能够有效地避免 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>();
    }
}
}

应用程序注册表

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) { }
    }
}
}
4

2 回答 2

1

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

于 2012-02-03T20:05:43.340 回答
0

这是我通常做的:

首先,你熟悉.Future()and.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();
}

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

请尝试一下并告诉我。

于 2014-03-07T17:52:26.703 回答