我遇到N + 1问题而且我不确定如何解决它。
可以在此问题的底部找到完全可重复的样本。因此,如果您愿意,请创建数据库,设置NUnit测试和所有附带的类,并尝试在本地消除N + 1。这是我遇到的真正问题的匿名版本。 众所周知,这段代码对于帮助推出下一个登月航天飞机至关重要。如果被问到,我不会否认它。
总结问题:我正在尝试查询下面绘制的表结构。关于这个表结构唯一值得注意的是问题有选择,然后有子问题,然后有子选择。您只能假设2个级别的问题 - > choice-> question->选择。
SiteSurveyQuestion
|
+---Site
|
+---Survey
|
+---Question
|
+---Choice
+---Choice
+---Choice
|
+---Question
+---Question
+---Question
|
+---Choice
+---Choice
+---Choice
我已经尝试了所有我想知道的事情。
在映射中,我尝试了一堆引用字段.Not.LazyLoad()
,但没有取得真正的成功。
我还尝试通过添加.Fetch()
和.FetchMany()
以及.ThenFetchMany()
的多种组合来修改查询,甚至尝试运行多个.ToFuture()
查询。这些执行对SQL查询进行了实际更改,但不是我正在寻找的最终结果。
这个查询已经归结为,#34;让我列出本网站上此次调查的所有问题,包括所有子问题"。这是查询:
using (var session = sessionFactory.OpenSession())
{
var questionsForSurvey = session.Query<SiteSurveyQuestion>()
.Where(x => x.Site.Id == 1 && x.Survey.Id == 1)
.ToArray();
}
所以最后提出一个问题:如何解决这个N + 1问题?我会满意以下任何一个
我不想要一个HQL解决方案,因为我无法了解我的映射和/或查询错误 - 我觉得我错过了一些基本的东西,我甚至不知道在哪里看。
示例说明:
```
Site: Site1 Survey: SurveyAboutCats Q: Own A Cat?
o Yes
Q: How many cats did you feed yesterday?
o 1
o 2-5
o 6-10
o 11-20
o 20+
o 100+
Q: How much do you spend on cats annually?
o 0-100
o 100-500
o 500-2000
o 2000+
o No
Q: No cats? What is wrong with you?
o I am sorry
Site: Site1 Survey: SurveyAboutCats Q: Own A Dog?
o Yes
o No
完整样本:
/*
Nuget packages:
<package id="FluentNHibernate" version="1.3.0.733" targetFramework="net40" />
<package id="NHibernate" version="3.3.3.4001" targetFramework="net45" />
<package id="NUnit" version="2.6.2" targetFramework="net40" />
*/
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using FluentNHibernate.Cfg;
using FluentNHibernate.Cfg.Db;
using FluentNHibernate.Conventions.Helpers;
using FluentNHibernate.Mapping;
using NHibernate;
using NHibernate.Cfg;
using NHibernate.Linq;
using NUnit.Framework;
namespace StackOverflow.CryForHelp
{
[TestFixture]
public class NHibernateMappingTests
{
[Test]
public void ShouldMapEntitiesWithoutNPlusOneIssue()
{
//Arrange
var connectionString = "Data Source=(local);Initial Catalog=NinetyNineProblemsAndAnNPlusOne;Integrated Security=SSPI;";
Configuration applicationConfiguration = new Configuration();
applicationConfiguration.SetProperty("connection.provider", "NHibernate.Connection.DriverConnectionProvider");
applicationConfiguration.SetProperty("dialect", "NHibernate.Dialect.MsSql2008Dialect");
applicationConfiguration.SetProperty("connection.driver_class", "NHibernate.Driver.SqlClientDriver");
applicationConfiguration.SetProperty("default_schema", "dbo");
applicationConfiguration.SetProperty("format_sql", "format_sql");
applicationConfiguration.SetProperty("show_sql", "true");
applicationConfiguration.SetProperty("generate_statistics", "true");
applicationConfiguration.Configure();
Configuration fluentConfiguration = null;
ISessionFactory sessionFactory = Fluently.Configure(applicationConfiguration)
.Mappings(m =>
{
m.FluentMappings.Conventions.Setup(x => x.Add(AutoImport.Never()));
m.FluentMappings.AddFromAssembly(Assembly.GetAssembly(GetType()));
})
.ExposeConfiguration(c => fluentConfiguration = c)
.Database(MsSqlConfiguration.MsSql2008.ConnectionString(connectionString))
.BuildSessionFactory();
var mappings = fluentConfiguration.ClassMappings;
//Act + Assert that we please don't create N+1 queries
using (var session = sessionFactory.OpenSession())
{
var questionsForSurvey = session.Query<SiteSurveyQuestion>()
.Where(x => x.Site.Id == 1 && x.Survey.Id == 1)
.ToArray();
foreach (var question in questionsForSurvey)
{
Console.WriteLine("Site: {0} Survey: {1} Q: {2}", question.Site.Name, question.Survey.Name, question.Question.InternalName);
foreach (var choice in question.Question.Choices)
{
Console.WriteLine("\t> " + choice.InternalName);
foreach (var subQuestion in choice.Questions)
{
Console.WriteLine("\t\tQ: " + subQuestion.InternalName);
foreach (var subChoice in subQuestion.Choices)
Console.WriteLine("\t\t\t> " + subChoice.InternalName);
}
}
}
}
}
}
public class Site
{
public virtual int Id { get; set; }
public virtual string Name { get; set; }
}
public class Survey
{
public virtual int Id { get; set; }
public virtual string Name { get; set; }
}
public class SiteSurvey
{
public virtual Site Site { get; set; }
public virtual Survey Survey { get; set; }
public virtual string Status { get; set; }
public virtual string Name { get; set; }
public virtual bool Equals(SiteSurvey other)
{
if (ReferenceEquals(null, other)) return false;
if (ReferenceEquals(this, other)) return true;
return Site.Id == other.Site.Id && Survey.Id == other.Survey.Id;
}
public override bool Equals(object obj)
{
if (ReferenceEquals(null, obj)) return false;
if (ReferenceEquals(this, obj)) return true;
if (obj.GetType() != this.GetType()) return false;
return Equals((SiteSurvey) obj);
}
public override int GetHashCode()
{
unchecked
{
return (Survey.Id * 397) ^ Site.Id;
}
}
}
public class SiteSurveyQuestion
{
public virtual Site Site { get; set; }
public virtual Survey Survey { get; set; }
public virtual Question Question { get; set; }
public virtual bool IsActive { get; set; }
public virtual bool Equals(SiteSurveyQuestion other)
{
if (ReferenceEquals(null, other)) return false;
if (ReferenceEquals(this, other)) return true;
return Site.Id == other.Site.Id && Survey.Id == other.Survey.Id && Question.Id == other.Question.Id;
}
public override bool Equals(object obj)
{
if (ReferenceEquals(null, obj)) return false;
if (ReferenceEquals(this, obj)) return true;
if (obj.GetType() != this.GetType()) return false;
return Equals((SiteSurveyQuestion) obj);
}
public override int GetHashCode()
{
unchecked
{
return Question.Id ^ (((Survey.Id * 397) ^ Site.Id) * 397);
}
}
}
public class Question
{
public virtual int Id { get; set; }
public virtual string InternalName { get; set; }
public virtual bool IsActive { get; set; }
public virtual IEnumerable<Choice> Choices { get; set; }
}
public class Choice
{
public virtual int Id { get; set; }
public virtual string InternalName { get; set; }
public virtual bool IsActive { get; set; }
public virtual IEnumerable<Question> Questions { get; set; }
}
public class SurveyMap : ClassMap<Survey>
{
public SurveyMap()
{
Table("Surveys");
Id(x => x.Id, "SurveyId").GeneratedBy.Identity().UnsavedValue(0);
Map(x => x.Name).Not.Nullable();
}
}
public class SiteMap : ClassMap<Site>
{
public SiteMap()
{
Table("Sites");
Id(x => x.Id, "SiteId").GeneratedBy.Identity().UnsavedValue(0);
Map(x => x.Name, "Name").Not.Nullable();
}
}
public class SiteSurveyMap : ClassMap<SiteSurvey>
{
public SiteSurveyMap()
{
Table("SiteSurveys");
CompositeId()
.KeyReference(x => x.Site, "SiteId")
.KeyReference(x => x.Survey, "SurveyId");
Map(x => x.Status).Not.Nullable();
Map(x => x.Name).Not.Nullable();
}
}
public class SiteSurveyQuestionMap : ClassMap<SiteSurveyQuestion>
{
public SiteSurveyQuestionMap()
{
Table("SiteSurveyQuestions");
CompositeId()
.KeyReference(x => x.Site, "SiteId")
.KeyReference(x => x.Survey, "SurveyId")
.KeyReference(x => x.Question, "QuestionId");
Map(x => x.IsActive, "ActiveFlag").Not.Nullable();
}
}
public class QuestionMap : ClassMap<Question>
{
public QuestionMap()
{
Table("Questions");
Id(x => x.Id, "QuestionId").GeneratedBy.Identity().UnsavedValue(0);
Map(x => x.InternalName);
Map(x => x.IsActive, "ActiveFlag");
HasMany(x => x.Choices).KeyColumn("QuestionId").AsBag().Cascade.AllDeleteOrphan().Inverse().Not.LazyLoad();
}
}
public class ChoiceMap : ClassMap<Choice>
{
public ChoiceMap()
{
Table("Choices");
Id(x => x.Id, "ChoiceId").GeneratedBy.Identity().UnsavedValue(0);
Map(x => x.InternalName);
Map(x => x.IsActive, "ActiveFlag");
HasMany(x => x.Questions)
.KeyColumn("ChoiceId")
.AsBag()
.Cascade
.AllDeleteOrphan()
.Inverse();
}
}
}
/*
use [master]
GO
CREATE DATABASE [NinetyNineProblemsAndAnNPlusOne]
GO
USE [NinetyNineProblemsAndAnNPlusOne]
GO
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
SET ANSI_PADDING ON
GO
CREATE TABLE [dbo].[Sites](
[SiteId] [int] IDENTITY(1,1) NOT NULL,
[Name] [varchar](100) NOT NULL,
CONSTRAINT [XPKSites] PRIMARY KEY CLUSTERED
(
[SiteId] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY],
CONSTRAINT [XAK1Sites] UNIQUE NONCLUSTERED
(
[Name] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY]
GO
CREATE TABLE [dbo].[Surveys](
[SurveyId] [int] IDENTITY(1,1) NOT NULL,
[Name] [varchar](500) NOT NULL,
[Status] [varchar](12) NOT NULL,
[SurveyTypeId] [int] NOT NULL,
CONSTRAINT [XPKSurveys] PRIMARY KEY CLUSTERED
(
[SurveyId] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY],
CONSTRAINT [XAK1Surveys] UNIQUE NONCLUSTERED
(
[Name] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY]
GO
CREATE TABLE [dbo].[SiteSurveys](
[SiteId] [int] NOT NULL,
[SurveyId] [int] NOT NULL,
[Name] [varchar](500) NOT NULL,
[Status] [varchar](12) NOT NULL,
CONSTRAINT [XPKSiteSurveys] PRIMARY KEY CLUSTERED
(
[SiteId] ASC,
[SurveyId] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY],
CONSTRAINT [XAK1SiteSurveys] UNIQUE NONCLUSTERED
(
[SiteId] ASC,
[SurveyId] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY],
CONSTRAINT [XAK2SiteSurveys] UNIQUE NONCLUSTERED
(
[SiteId] ASC,
[Name] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY]
GO
CREATE TABLE [dbo].[SiteSurveyQuestions](
[SiteId] [int] NOT NULL,
[SurveyId] [int] NOT NULL,
[QuestionId] [int] NOT NULL,
[SurveyQuestionTypeId] [int] NULL,
[ActiveFlag] [bit] NOT NULL,
[IsRequired] [bit] NOT NULL,
CONSTRAINT [XPKSurveyQuestions] PRIMARY KEY CLUSTERED
(
[SurveyId] ASC,
[QuestionId] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY]
GO
CREATE TABLE [dbo].[Questions](
[QuestionId] [int] IDENTITY(1,1) NOT NULL,
[InternalName] [varchar](100) NOT NULL,
[ChoiceId] [int] NULL,
[ActiveFlag] [bit] NOT NULL,
CONSTRAINT [XPKQuestions] PRIMARY KEY CLUSTERED
(
[QuestionId] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY],
CONSTRAINT [XAK1QuestionsInternalName] UNIQUE NONCLUSTERED
(
[InternalName] ASC,
[ChoiceId] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY]
GO
CREATE TABLE [dbo].[Choices](
[ChoiceId] [int] IDENTITY(1,1) NOT NULL,
[QuestionId] [int] NOT NULL,
[InternalName] [varchar](100) NOT NULL,
[ActiveFlag] [bit] NOT NULL,
CONSTRAINT [XPKChoices] PRIMARY KEY CLUSTERED
(
[ChoiceId] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY],
CONSTRAINT [XAKChoiceIdQuestionId] UNIQUE NONCLUSTERED
(
[ChoiceId] ASC,
[QuestionId] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY],
CONSTRAINT [XAKChoiceInternalName] UNIQUE NONCLUSTERED
(
[QuestionId] ASC,
[InternalName] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY]
GO
ALTER TABLE [dbo].[Surveys] WITH CHECK ADD CONSTRAINT [VRSurveyStatuses_Surveys] CHECK (([Status]='Live' OR [Status]='NotLive' OR [Status]='Discontinued'))
GO
ALTER TABLE [dbo].[Surveys] CHECK CONSTRAINT [VRSurveyStatuses_Surveys]
GO
ALTER TABLE [dbo].[SiteSurveys] WITH CHECK ADD CONSTRAINT [R289] FOREIGN KEY([SurveyId])
REFERENCES [dbo].[Surveys] ([SurveyId])
GO
ALTER TABLE [dbo].[SiteSurveys] CHECK CONSTRAINT [R289]
GO
ALTER TABLE [dbo].[SiteSurveys] WITH CHECK ADD CONSTRAINT [R303] FOREIGN KEY([SiteId])
REFERENCES [dbo].[Sites] ([SiteId])
GO
ALTER TABLE [dbo].[SiteSurveys] CHECK CONSTRAINT [R303]
GO
ALTER TABLE [dbo].[SiteSurveys] WITH CHECK ADD CONSTRAINT [VRSurveyStatuses_SiteSurveys] CHECK (([Status]='Live' OR [Status]='NotLive' OR [Status]='Discontinued'))
GO
ALTER TABLE [dbo].[SiteSurveys] CHECK CONSTRAINT [VRSurveyStatuses_SiteSurveys]
GO
ALTER TABLE [dbo].[SiteSurveyQuestions] WITH CHECK ADD CONSTRAINT [QuestionsToSurveyQuestions] FOREIGN KEY([QuestionId])
REFERENCES [dbo].[Questions] ([QuestionId])
GO
ALTER TABLE [dbo].[SiteSurveyQuestions] CHECK CONSTRAINT [QuestionsToSurveyQuestions]
GO
ALTER TABLE [dbo].[SiteSurveyQuestions] WITH CHECK ADD CONSTRAINT [SurveysToSurveyQuestions] FOREIGN KEY([SurveyId])
REFERENCES [dbo].[Surveys] ([SurveyId])
GO
ALTER TABLE [dbo].[SiteSurveyQuestions] CHECK CONSTRAINT [SurveysToSurveyQuestions]
GO
ALTER TABLE [dbo].[Questions] WITH CHECK ADD CONSTRAINT [R409] FOREIGN KEY([ChoiceId])
REFERENCES [dbo].[Choices] ([ChoiceId])
GO
ALTER TABLE [dbo].[Choices] WITH CHECK ADD CONSTRAINT [R408] FOREIGN KEY([QuestionId])
REFERENCES [dbo].[Questions] ([QuestionId])
GO
ALTER TABLE [dbo].[Choices] CHECK CONSTRAINT [R408]
GO
SET ANSI_PADDING OFF
GO
GO
SET IDENTITY_INSERT [dbo].[Sites] ON
INSERT [dbo].[Sites] ([SiteId], [Name]) VALUES (1, N'Site1')
INSERT [dbo].[Sites] ([SiteId], [Name]) VALUES (2, N'Site2')
SET IDENTITY_INSERT [dbo].[Sites] OFF
SET IDENTITY_INSERT [dbo].[Surveys] ON
INSERT [dbo].[Surveys] ([SurveyId], [Name], [Status], [SurveyTypeId]) VALUES (1, N'SurveyAboutCats', N'Live', 0)
INSERT [dbo].[Surveys] ([SurveyId], [Name], [Status], [SurveyTypeId]) VALUES (2, N'Crime Survey', N'Live', 0)
SET IDENTITY_INSERT [dbo].[Surveys] OFF
SET IDENTITY_INSERT [dbo].[Questions] ON
INSERT [dbo].[Questions] ([QuestionId], [InternalName], [ChoiceId], [ActiveFlag]) VALUES (1, N'Own A Cat?', NULL, 1)
INSERT [dbo].[Questions] ([QuestionId], [InternalName], [ChoiceId], [ActiveFlag]) VALUES (2, N'Own A Dog?', NULL, 1)
INSERT [dbo].[Questions] ([QuestionId], [InternalName], [ChoiceId], [ActiveFlag]) VALUES (3, N'Witnessed any crimes recently?', NULL, 1)
INSERT [dbo].[Questions] ([QuestionId], [InternalName], [ChoiceId], [ActiveFlag]) VALUES (4, N'Committed any crimes yourself recently?', NULL, 1)
SET IDENTITY_INSERT [dbo].[Questions] OFF
SET IDENTITY_INSERT [dbo].[Choices] ON
INSERT [dbo].[Choices] ([ChoiceId], [QuestionId], [InternalName], [ActiveFlag]) VALUES (1, 1, N'Yes', 1)
INSERT [dbo].[Choices] ([ChoiceId], [QuestionId], [InternalName], [ActiveFlag]) VALUES (2, 1, N'No', 1)
INSERT [dbo].[Choices] ([ChoiceId], [QuestionId], [InternalName], [ActiveFlag]) VALUES (3, 2, N'Yes', 1)
INSERT [dbo].[Choices] ([ChoiceId], [QuestionId], [InternalName], [ActiveFlag]) VALUES (4, 2, N'No', 1)
INSERT [dbo].[Choices] ([ChoiceId], [QuestionId], [InternalName], [ActiveFlag]) VALUES (5, 3, N'Yes', 1)
INSERT [dbo].[Choices] ([ChoiceId], [QuestionId], [InternalName], [ActiveFlag]) VALUES (6, 3, N'Yes but I ain''t no snitch', 1)
INSERT [dbo].[Choices] ([ChoiceId], [QuestionId], [InternalName], [ActiveFlag]) VALUES (7, 4, N'No', 1)
INSERT [dbo].[Choices] ([ChoiceId], [QuestionId], [InternalName], [ActiveFlag]) VALUES (8, 4, N'I plead the fifth', 1)
SET IDENTITY_INSERT [dbo].[Choices] OFF
SET IDENTITY_INSERT [dbo].[Questions] ON
INSERT [dbo].[Questions] ([QuestionId], [InternalName], [ChoiceId], [ActiveFlag]) VALUES (6, N'No cats? What is wrong with you?', 2, 1)
INSERT [dbo].[Questions] ([QuestionId], [InternalName], [ChoiceId], [ActiveFlag]) VALUES (7, N'How many cats did you feed yesterday?', 1, 1)
INSERT [dbo].[Questions] ([QuestionId], [InternalName], [ChoiceId], [ActiveFlag]) VALUES (8, N'How much do you spend on cats annually?', 1, 1)
SET IDENTITY_INSERT [dbo].[Questions] OFF
SET IDENTITY_INSERT [dbo].[Choices] ON
INSERT [dbo].[Choices] ([ChoiceId], [QuestionId], [InternalName], [ActiveFlag]) VALUES (9, 6, N'I am sorry', 1)
INSERT [dbo].[Choices] ([ChoiceId], [QuestionId], [InternalName], [ActiveFlag]) VALUES (10, 7, N'1', 1)
INSERT [dbo].[Choices] ([ChoiceId], [QuestionId], [InternalName], [ActiveFlag]) VALUES (11, 7, N'2-5', 1)
INSERT [dbo].[Choices] ([ChoiceId], [QuestionId], [InternalName], [ActiveFlag]) VALUES (12, 7, N'6-10', 1)
INSERT [dbo].[Choices] ([ChoiceId], [QuestionId], [InternalName], [ActiveFlag]) VALUES (13, 7, N'11-20', 1)
INSERT [dbo].[Choices] ([ChoiceId], [QuestionId], [InternalName], [ActiveFlag]) VALUES (14, 7, N'20+', 1)
INSERT [dbo].[Choices] ([ChoiceId], [QuestionId], [InternalName], [ActiveFlag]) VALUES (15, 7, N'100+', 1)
INSERT [dbo].[Choices] ([ChoiceId], [QuestionId], [InternalName], [ActiveFlag]) VALUES (16, 8, N'0-100', 1)
INSERT [dbo].[Choices] ([ChoiceId], [QuestionId], [InternalName], [ActiveFlag]) VALUES (17, 8, N'100-500', 1)
INSERT [dbo].[Choices] ([ChoiceId], [QuestionId], [InternalName], [ActiveFlag]) VALUES (18, 8, N'500-2000', 1)
INSERT [dbo].[Choices] ([ChoiceId], [QuestionId], [InternalName], [ActiveFlag]) VALUES (19, 8, N'2000+', 1)
SET IDENTITY_INSERT [dbo].[Choices] OFF
INSERT [dbo].[SiteSurveys] ([SiteId], [SurveyId], [Name], [Status]) VALUES (1, 1, N'Site #1 Cat Survey', N'Live')
INSERT [dbo].[SiteSurveys] ([SiteId], [SurveyId], [Name], [Status]) VALUES (1, 2, N'Site #1 Crime Survey', N'Live')
INSERT [dbo].[SiteSurveys] ([SiteId], [SurveyId], [Name], [Status]) VALUES (2, 1, N'Site #2 Cat Survey', N'Live')
INSERT [dbo].[SiteSurveys] ([SiteId], [SurveyId], [Name], [Status]) VALUES (2, 2, N'Site #2 Crime Survey', N'Live')
INSERT [dbo].[SiteSurveyQuestions] ([SiteId], [SurveyId], [QuestionId], [SurveyQuestionTypeId], [ActiveFlag], [IsRequired]) VALUES (1, 1, 1, 0, 1, 0)
INSERT [dbo].[SiteSurveyQuestions] ([SiteId], [SurveyId], [QuestionId], [SurveyQuestionTypeId], [ActiveFlag], [IsRequired]) VALUES (1, 1, 2, 0, 1, 0)
GO
USE [master]
GO
-- */
答案 0 :(得分:2)
以下是一种在访问集合时可以避免从数据库中提取的方法:
var questions = session.Query<Question>()
.Fetch(x => x.Choices)
.ToList();
var choices = session.Query<Choice>()
.Fetch(x => x.Questions)
.ToList();
var questionsForSurvey = session.Query<SiteSurveyQuestion>()
.Where(x => x.Site.Id == 1 && x.Survey.Id == 1)
.ToArray();
以上显然并不理想,因为它会加载所有问题和选择,但您需要通过在{{1}中设置Question
的集合,将SiteSurveyQuestion
与SiteSurveyQuestion
相关联过滤它就像你想要的那样。这样,您就可以根据您的调查和网站ID加载所需的问题和选择。
答案 1 :(得分:2)
1 + N的解决方案将建立在特殊的NHibernate 优化功能之上(让我举一点介绍)
NHibernate可以有效地使用批量提取,也就是说,如果访问一个代理(或集合),NHibernate可以加载几个未初始化的代理。批量提取是懒惰选择提取策略的优化。有两种方法可以调整批处理获取:在类和集合级别上。
更容易理解批量提取类/实体。想象一下,您在运行时遇到以下情况:您在一个ISession中加载了25个Cat实例,每个Cat都有一个对其所有者的引用,一个Person。 Person类使用代理映射,lazy =&#34; true&#34;。如果您现在遍历所有猫并在每个猫上调用cat.Owner,NHibernate将默认执行25个SELECT语句,以检索代理所有者。您可以通过在Person:
的映射中指定批量大小来调整此行为
<class name="Person" batch-size="10">...</class>
NHibernate现在只执行三个查询,模式为10,10,5。
您还可以启用批量提取集合。例如,如果每个Person都有一个懒惰的Cats集合,并且当前在ISesssion中加载了10个人,则遍历所有人将生成10个SELECT,每个人调用一个SELECT.Cats。如果在Person的映射中为Cats集合启用批量获取,NHibernate可以预取集合:
<class name="Person">
<set name="Cats" batch-size="3">
...
</set>
</class>
批量大小为3时,NHibernate将在四个SELECT中加载3个,3个,3个,1个集合。同样,属性的值取决于特定会话中未初始化集合的预期数量。
那就是DOC。这个解决方案的优点在于,我们将在映射中拥有简单查询和优化。
在实践中,这意味着,几乎所有one-to-many
和实体映射都应该包含BatchSize(25)
(或者50或100 ...以便找出适合您的套件)
为了说明这一点,我调整了上面的一个映射
public QuestionMap()
{
Table("Questions");
// here, load this in batches by 25
BatchSize(25);
Id(x => x.Id, "QuestionId").GeneratedBy.Identity().UnsavedValue(0);
Map(x => x.InternalName);
Map(x => x.IsActive, "ActiveFlag");
HasMany(x => x.Choices)
.KeyColumn("QuestionId")
.AsBag()
.Cascade
.AllDeleteOrphan()
.Inverse()
// here again
.BatchSize(25)
.Not.LazyLoad();
}
下一步,将取决于会话的生命周期。如果我们使用 using(var session...){}
,我们会遇到麻烦。上述内容在会议之外无法奏效。所有必须通过会话填充。那怎么解决呢?
最好的方法是附加一些方法来迭代对象并将它们转换成一些&#34; DTO&#34;
using (var session = sessionFactory.OpenSession())
{
var questionsForSurvey = session.Query<SiteSurveyQuestion>()
.Where(x => x.Site.Id == 1 && x.Survey.Id == 1)
.ToArray();
var result = new List<SiteSurveyQuestionDTO>();
foreach(var s in questionsForSurvey)
{
// here we can touch all the inner properties and collections
// so NHibernate will load all needed data in batches
var dto = s.doSomething();
result.Add(dto);
}
}
我首选的方法是实施IClonable
并在.Clone()
内部实施所需的内容
using (var session = sessionFactory.OpenSession())
{
var questionsForSurvey = session.Query<SiteSurveyQuestion>()
.Where(x => x.Site.Id == 1 && x.Survey.Id == 1)
.ToArray()
.Select(s => s.Clone() as SiteSurveyQuestion);
}
检查Prototype pattern。还有更多东西about life cycle。还有一些about batch fetching
答案 2 :(得分:0)
使用Radim关于设置上述批量大小的答案,再加上为站点和调查实体添加明确的预先加载,让我在一个批量前期获得了相当不错的6个查询,并在一秒钟内获得了2个额外的查询访问第一个子问题时批处理。
N + 1已成为N / 300 + 1,最终成为......两批中总共8个查询。
以下是我更改的内容(查找带有//new
条评论的行):
public class SiteSurveyQuestionMap : ClassMap<SiteSurveyQuestion>
{
public SiteSurveyQuestionMap()
{
Table("SiteSurveyQuestions");
CompositeId()
.KeyReference(x => x.Site, "SiteId")
.KeyReference(x => x.Survey, "SurveyId")
.KeyReference(x => x.Question, "QuestionId");
References(x => x.Site, "SiteId"). Not.LazyLoad(); //new
References(x => x.Survey, "SurveyId").Not.LazyLoad(); //new
References(x => x.Question, "QuestionId").Not.LazyLoad(); //new
Map(x => x.IsActive, "ActiveFlag").Not.Nullable();
}
}
//and so on
public class QuestionMap : ClassMap<Question>
{
public QuestionMap()
{
Table("Questions");
Id(x => x.Id, "QuestionId").GeneratedBy.Identity().UnsavedValue(0);
Map(x => x.InternalName);
Map(x => x.IsActive, "ActiveFlag");
HasMany(x => x.Choices)
.KeyColumn("QuestionId")
.BatchSize(300) //new
.AsBag()
.Cascade
.AllDeleteOrphan()
.Inverse()
.Not.LazyLoad();
}
}
public class ChoiceMap : ClassMap<Choice>
{
public ChoiceMap()
{
Table("Choices");
Id(x => x.Id, "ChoiceId").GeneratedBy.Identity().UnsavedValue(0);
Map(x => x.InternalName);
Map(x => x.IsActive, "ActiveFlag");
HasMany(x => x.Questions)
.KeyColumn("ChoiceId")
.BatchSize(300) //new
.AsBag()
.Cascade
.AllDeleteOrphan()
.Inverse();
}
}
通过上述更改,NHibernate创建的SQL是可以接受的。
如果有人可以改进这个答案,请做。