2012-01-28 63 views
19

編輯我爲這一個問題改寫了整個項目。因此,我重新提出了這個問題。如何避免使用複合鍵的NHibernate N + 1

我希望能夠有效地避免N + 1和笛卡爾連接在第三級上將複合鍵與4級深度實體連接在一起。

我正在尋找這個只在幾個查詢中完成,而不是延遲加載,而不是隻是將所有表連接在一起。

A - (多) - >乙 - (多) - 「ç - (複合材料,單) - > 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) { } 
    } 
} 
} 
+0

顯示您的嘗試以及。 – gdoron 2012-01-28 20:56:11

+0

添加了我目前正在測試的 – BradLaney 2012-01-30 18:00:33

+0

你需要什麼查詢才能返回 - 一個完全加載的'Listing'或一個完全加載的'ListingItem'? 'Listing'和'ListingItem'是否也有'Id'屬性?你可以發佈'ListingItemValue'的映射嗎? – 2012-02-01 19:05:25

回答

1

您能否提供您的地圖的詳細信息。減少查詢數量的一種方法(不是一種,但很少)是在映射中使用批量大小功能。這將使代理人的路數少於N + 1。但真的應該有一個解決方案來獲取所有使用期貨或類似的數據,所以請提供映射。

+1

我完全改變了帖子,爲您提供了一個完整的正在運行的應用程序。祝你好運。 – BradLaney 2012-02-07 01:19:35

+0

@BradLaney你如何定製你的例子的映射?我真的會嘗試['batch-size'](http://nhibernate.info/doc/nhibernate-reference/performance.html#performance-fetching-batch)設置,但我從不使用流暢的自動映射,只有hbm文件。此外,請參閱[同一文檔](http://nhibernate.info/doc/nhibernate-reference/)的§5.6.1和7.4,composite-id應映射爲組件。這對於高效的延遲加載至關重要。但在你的情況'ListingItemValue'實體不應該存在(純多對多),除非你的實際需要添加一些額外的屬性。 – 2016-03-25 07:15:23

0

這是我平時做:

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

我必須在這裏說我沒有測試過,所以可能我錯過了一些東西。其次,我沒有考慮你提到的組合鍵。我不知道這是否會導致任何問題,但我不明白爲什麼它應該。

請試試看,並讓我知道。