2012-01-28 14 views
19

EDIT He rehecho un proyecto completo para este único problema. Y así, rehice la pregunta.¿Cómo se puede evitar NHibernate N + 1 con la clave compuesta

Quiero ser capaz de evitar eficientemente las uniones N + 1 y cartesianas uniendo una entidad de 4 niveles con una clave compuesta en el tercer nivel.

Estoy buscando que esto se haga solo en unas pocas consultas, no se carguen con pereza, y no solo junten todas las tablas.

A - (muchos) -> B - (muchos) -> C - (compuesto, single) -> 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 

Este es el código utilizado Esto es una aplicación completamente funcional. Utilicé NuGet para instalar Sqlite x86, StructureMap, NHProf, Fluido 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; 
     }); 
    } 
} 
} 

Entidades en Praga:

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

Muestra lo que has probado también. – gdoron

+0

Agregué lo que actualmente estoy probando – BradLaney

+0

¿Qué necesita para devolver la consulta, un 'Listing' completamente cargado o un' ListingItem' completamente cargado? ¿'Listing' y' ListingItem' también tienen propiedades 'Id'? ¿Podría publicar el mapeo para 'ListingItemValue'? –

Respuesta

1

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

+1

Cambié la publicación por completo para ofrecerte una aplicación para correr completa. Buena suerte. – BradLaney

+0

@BradLaney ¿Cómo se personalizan las asignaciones con su ejemplo? Realmente probaría el ajuste ['batch-size'] (http://nhibernate.info/doc/nhibernate-reference/performance.html#performance-fetching-batch), pero nunca uso el auto-mapping fluido, solo hbm archivos. Además, ver § 5.6.1 y 7.4 del [mismo documento] (http://nhibernate.info/doc/nhibernate-reference/), composite-id debe mapearse como componente. Es crítico para una carga lenta y eficiente. Pero en su caso la entidad 'ListingItemValue' no debería existir (pura de muchos a muchos), a menos que su necesidad real agregue algunas propiedades adicionales sobre ella. –

0

Esto es lo que suele hacer:

En primer lugar, ¿está usted familiarizado con .Future() y .FutureValue()? Con ellos puede enviar varias consultas en una sola ida y vuelta. Es sólo dos consultas aquí, así que no es un gran problema, pero aún así ...

Lo que estoy tratando de hacer es:

  • Prefetch todo ListingItems y su Values y Fields al primer caché de nivel para que no activen la Carga diferida. Como puede ver, no utilizo una variable en la primera consulta, porque no necesito almacenar el resultado. Solo necesito que esta consulta se ejecute y "capte previamente" mis entidades.
  • pude evitar la parte Subquery, pero el Subquery ayuda a evitar el uso de un producto cartesiano entre Listings - Items-Values.
  • Dado que cada Value tiene un solo Field, no tendré ningún problema, en la segunda consulta, con un producto cartesiano.
  • Luego, solo obtenga el Listing, junto con su Items. La parte .Value; activa la 'ejecución' de ambas consultas en una única ida y vuelta a la base de datos.
  • El resultado debería ser el siguiente. A medida que avance por el gráfico de objetos, todos los objetos ya deberían estar en la memoria caché de primer nivel y no debería producirse 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(); 
} 

Debo decir aquí que no lo he probado, 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 veo por qué debería ser así.

Por favor pruébalo y avísame.

Cuestiones relacionadas