编辑我为这一个问题重新制作了整个项目。因此,我重新提出了这个问题。
我希望能够有效地避免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) { }
}
}
}
答案 0 :(得分:1)
您能否提供您的地图的详细信息。减少查询数量(不是一个,但很少)的一种方法是在映射中使用批量大小的功能。这将使代理人的往返次数减少,而不是N + 1。但实际上应该有一个使用期货或类似方法获取所有数据的解决方案,所以请提供映射。
答案 1 :(得分:0)
这就是我通常做的事情:
首先,您熟悉.Future()
和.FutureValue()
吗?有了这些,您可以在一次往返中发送多个查询。这里只有两个问题,所以这不是什么大不了的事,但还是......
我想做的是:
ListingItems
及其Values
和Fields
预取到第一级缓存,以便它们不会触发延迟加载。如您所见,我不在第一个查询中使用变量,因为我不需要存储结果。我只需要运行这个查询并“预取”我的实体。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();
}
我必须在这里说我没有测试过,所以我可能错过了一些东西。其次,我没有考虑你提到的复合键。我不知道这是否会引起任何问题,但我不明白为什么会这样。
请尝试一下,让我知道。