2012-01-28 29 views
19

EDIT Tôi làm lại toàn bộ dự án cho vấn đề này. Và do đó, tôi làm lại câu hỏi.Làm cách nào bạn có thể tránh NHibernate N + 1 bằng phím tổng hợp

Tôi muốn có thể có hiệu quả tránh tham gia N + 1 và Cartesian tham gia cùng một thực thể sâu 4 cấp với khóa tổng hợp ở cấp thứ ba.

Tôi đang tìm kiếm điều này được thực hiện chỉ trong một vài truy vấn, không được tải xuống lười biếng và không chỉ tham gia tất cả các bảng cùng nhau.

A - (nhiều) -> B - (nhiều) -> C - (composite, duy nhất) -> D

Cái gì như:

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 

Đây là đoạn mã sử dụng Đây là một ứng dụng đầy đủ chức năng. Tôi đã sử dụng NuGet để cài đặt 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; 
     }); 
    } 
} 
} 

Đối tượng Listing:

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

Chương trình (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) { } 
    } 
} 
} 
+0

Hiển thị những gì bạn đã thử. – gdoron

+0

Đã thêm những gì tôi hiện đang thử nghiệm – BradLaney

+0

Bạn cần truy vấn nào để trả về - một truy vấn được nạp đầy đủ 'Liệt kê', hoặc một' ListingItem' được nạp đầy đủ? Do 'Listing' và' ListingItem' cũng có thuộc tính 'Id'? Bạn có thể đăng ánh xạ cho 'ListingItemValue' không? –

Trả lời

1

Bạn có thể vui lòng cung cấp chi tiết về bản đồ của mình không. Một phương pháp để giảm số lượng truy vấn (không phải một, nhưng rất ít) sẽ sử dụng tính năng kích thước lô trong ánh xạ của bạn. Điều đó sẽ cư trú trên các proxy theo cách ít khứ hồi hơn N + 1. Nhưng thực sự nên có một giải pháp để lấy tất cả dữ liệu sử dụng tương lai hoặc tương tự, vì vậy vui lòng cung cấp ánh xạ.

+1

Tôi đã thay đổi hoàn toàn bài đăng để cung cấp cho bạn một ứng dụng đang chạy chính thức. Chúc may mắn. – BradLaney

+0

@BradLaney Bạn làm cách nào để tùy chỉnh ánh xạ với ví dụ của mình? Tôi thực sự sẽ thử cài đặt ['batch-size'] (http://nhibernate.info/doc/nhibernate-reference/performance.html#performance-fetching-batch), nhưng tôi không bao giờ sử dụng bản đồ tự động lưu loát, chỉ hbm các tập tin. Hơn nữa, xem § 5.6.1 và 7.4 của [cùng một tài liệu] (http://nhibernate.info/doc/nhibernate-reference/), tổng hợp-id nên được ánh xạ dưới dạng thành phần. Nó là rất quan trọng cho tải lười biếng hiệu quả. Nhưng trong trường hợp của bạn, thực thể 'ListingItemValue' sẽ không tồn tại (thuần túy từ nhiều đến nhiều), trừ khi nhu cầu thực tế của bạn thêm một số thuộc tính bổ sung vào nó. –

0

Đây là những gì tôi thường làm:

Trước hết, là bạn quen với .Future().FutureValue()? Với những người bạn có thể gửi một số truy vấn trong một vòng duy nhất. Đó là chỉ có hai thắc mắc ở đây, vì vậy nó không lớn của một thỏa thuận, nhưng vẫn ...

Những gì tôi đang cố gắng làm là:

  • Prefetch tất cả ListingItemsValues của họ và Fields vào bộ nhớ cache cấp độ đầu tiên để chúng không kích hoạt tải Lazy. Như bạn có thể thấy tôi không sử dụng một biến trong truy vấn đầu tiên, bởi vì tôi không cần lưu trữ kết quả. Tôi chỉ cần cho truy vấn này để chạy và 'tìm nạp trước' các thực thể của tôi.
  • Tôi có thể tránh phần Subquery, nhưng Subquery giúp tôi tránh sản phẩm Descartes giữa Listings - Items - Values.
  • Vì mỗi Value có một đơn Field, tôi sẽ không gặp vấn đề gì trong truy vấn thứ hai, với một sản phẩm mang tính Descartes.
  • Sau đó, chỉ cần lấy Listing, cùng với Items. Phần .Value; có kích hoạt 'thực thi' cả hai truy vấn trong một vòng lặp đơn lẻ tới cơ sở dữ liệu.
  • Kết quả phải là điều này. Khi tôi đi qua biểu đồ đối tượng, tất cả các đối tượng phải ở trong bộ nhớ cache cấp đầu tiên và không tải xuống lười biếng nào xảy ra.

.

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

Tôi phải nói ở đây là tôi chưa thử nghiệm điều đó, vì vậy có thể tôi đang thiếu thứ gì đó. Thứ hai, tôi không tính đến khóa tổng hợp mà bạn đề cập đến. Tôi không biết liệu điều đó có gây ra bất kỳ vấn đề nào không, nhưng tôi không thể hiểu tại sao nó lại xảy ra.

Hãy dùng thử và cho tôi biết.

Các vấn đề liên quan