una tablas tabla secundarias relacionar primarias primaria misma llaves llave foráneas foraneas foranea datos compuesta como clave nhibernate queryover select-n-plus-1

nhibernate - tablas - llaves primarias y secundarias en base de datos



¿Cómo se puede evitar NHibernate N+1 con clave compuesta? (2)

EDITAR Me rehice un proyecto completo para este problema. Y así, rehecho la pregunta.

Quiero poder evitar eficazmente N + 1 y Cartesian se une uniendo una entidad profunda de 4 niveles con una clave compuesta en el tercer nivel.

Estoy buscando que esto se haga solo en unas pocas consultas, no con carga lenta, y no solo unir todas las tablas.

A - (muchos) -> B - (muchos) -> C - (compuesto, sencillo) -> D

Algo como:

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

Aquí está el código utilizado. Esta es una aplicación completamente funcional. Utilicé NuGet para instalar 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; }); } } }

Listado de Entidades:

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

Programa (consola):

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


¿Puede por favor proporcionar detalles de su asignación. Un método para reducir el número de consultas (no a una, sino a muy pocas) sería utilizar la función de tamaño de lote en su asignación. Eso llenaría los proxies en menos viajes de ida y vuelta que N + 1. Pero realmente debería haber una solución para obtener todos los datos utilizando futuros o similares, así que proporcione un mapeo.


Esto es lo que suelo hacer:

En primer lugar, ¿está familiarizado con .Future() y .FutureValue() ? Con esos puedes enviar varias consultas en un solo viaje de ida y vuelta. Aquí solo hay dos consultas, así que no es un gran problema, pero aún así ...

Lo que estoy tratando de hacer es:

  • ListingItems todos los ListingItems y sus Values y Fields al caché de primer nivel para que no activen la Carga diferida. Como puede ver, no uso una variable en la primera consulta, porque no necesito almacenar el resultado. Solo necesito que esta consulta se ejecute y ''prefetch'' mis entidades.
  • Podría evitar la parte de Subquery , pero la Subquery me ayuda a evitar un producto cartesiano entre Listings - Items - Values .
  • Como cada Value tiene un solo Field , no tendré ningún problema, en la segunda consulta, con un producto cartesiano.
  • Luego, simplemente obtenga el Listing , junto con sus Items . El .Value; parte con desencadenar la ''ejecución'' de ambas consultas en un solo viaje de ida y vuelta a la base de datos.
  • El resultado debe ser este. Mientras viajo a través del gráfico de objetos, todos los objetos deberían estar ya en el caché de primer nivel y no debería ocurrir una carga lenta.

.

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

Tengo que decir aquí que no he probado eso, así que posiblemente me esté perdiendo algo. En segundo lugar, no tomé en cuenta la clave compuesta que mencionas. No sé si eso causará algún problema, pero no puedo ver por qué debería hacerlo.

Por favor, pruébalo y házmelo saber.