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

发布于 2024-12-29 13:43:39 字数 7403 浏览 4 评论 0原文

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

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

EDIT I remade an entire project for this one problem. And thus, I remade the question.

I want to be able to efficiently avoid N+1 and Cartesian joins joining together a 4 level deep entity with a composite key on the third level.

I am looking for this to be done in only a few queries, not lazy loaded, and not just join all the tables together.

A -(many)-> B -(many)-> C -(composite, single)-> D

Something like:

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

Here is the code used
This is a fully functional app.
I used NuGet to install 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;
        });
    }
}
}

Listing Entities:

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; }
}
}

Program (console):

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

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

坠似风落 2025-01-05 13:43:39

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

Can you please provide details of your mapping. One method to reduce the number of queries (not to one, but to very few) would be to use the batch-size feature in your mapping. That would populate the proxies on way fewer roundtrips than N+1. But really there should be a solution to fetch all data using futures or similar, so please provide mapping.

驱逐舰岛风号 2025-01-05 13:43:39

这是我通常做的事情:

首先,你熟悉 .Future().FutureValue() 吗?通过这些,您可以在一次往返中发送多个查询。这里只有两个查询,所以没什么大不了的,但仍然......

我想做的是:

  • 预取所有 ListingItems 及其 ValuesFields 到一级缓存,这样就不会触发延迟加载。正如您所看到的,我在第一个查询中没有使用变量,因为我不需要存储结果。我只需要运行此查询并“预取”我的实体。
  • 我可以避免 Subquery 部分,但是 Subquery 帮助我避免 Listings - Items - < 之间的笛卡尔积代码>值。
  • 由于每个 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();
}

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

请尝试一下并告诉我。

This is what I usually do:

First of all, are you familiar with .Future() and .FutureValue()? With those you can send several queries in a single roundtrip. It's only two queries here, so it's not big of a deal, but still...

What I am trying to do is:

  • Prefetch all ListingItems and their Values and Fields to the first level cache so that they don't trigger Lazy Loading. As you can see I don't use a variable in the first query, because I don't need to store the result. I just need for this query to run and 'prefetch' my entities.
  • I could avoid the Subquery part, but the Subquery helps me avoiding a cartesian product between Listings - Items - Values.
  • Since each Value has a single Field, I won't have a problem, in the second query, with a cartesian product.
  • Then, just get the Listing, along with its Items. The .Value; part with trigger the 'execution' of both queries in a single roundtrip to the database.
  • The result should be this. As I travel through the object graph, all objects should be already in first level cache and no lazy loading should happen.

.

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();
}

I have to say here that I haven't tested that, so possibly I am missing something. Secondly, I didn't take in account the composite key you mention. I don't know if that will causes any issues, but I can't see why it should.

Please try it out and let me know.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文