Подтвердить что ты не робот

Как вы можете избежать NHibernate N + 1 с помощью составного ключа

EDIT Я переделаю весь проект для этой одной проблемы. И таким образом, я переделал вопрос.

Я хочу иметь возможность эффективно избегать 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) { }
    }
}
}
4b9b3361

Ответ 1

Можете ли вы предоставить подробную информацию о своем картографировании. Один из способов уменьшить количество запросов (не до одного, а до очень немногих) заключается в использовании функции пакетного размера в вашем сопоставлении. Это заполнило бы прокси-серверы на пути меньшего количества переходов, чем N + 1. Но на самом деле должно быть решение для получения всех данных с использованием фьючерсов или подобных, поэтому, пожалуйста, предоставьте сопоставление.

Ответ 2

Это то, что я обычно делаю:

В первую очередь, знакомы ли вы с .Future() и .FutureValue()? С ними вы можете отправить несколько запросов в одном обратном направлении. Здесь всего два вопроса, поэтому он не большой сделки, но все же...

Я пытаюсь сделать следующее:

  • Предварительно выберите все ListingItems и их Values и Fields в кеш первого уровня, чтобы они не запускали Lazy Loading. Как вы можете видеть, я не использую переменную в первом запросе, потому что мне не нужно сохранять результат. Мне просто нужно, чтобы этот запрос запускался и "предвыборку" моих объектов.
  • Я мог бы избежать части 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();
}

Я должен сказать здесь, что я не тестировал это, поэтому, возможно, я что-то упустил. Во-вторых, я не принял во внимание сложный ключ, который вы упомянули. Я не знаю, вызовет ли это какие-либо проблемы, но я не понимаю, почему это должно быть.

Пожалуйста, попробуйте и дайте мне знать.