[英]Querying via NHibernate without an N+1 - sample included
我有一个N + 1问题,我不确定如何解决。
可以在此问题的底部找到完全可复制的样本。 因此,如果愿意,请创建数据库,设置NUnit测试和所有附带的类,并尝试在本地消除N + 1。 这是我遇到的实际问题的匿名版本。 就您所知,此代码对于帮助发射下一个登月航天飞机至关重要。 如果被问到,我不会否认。
总结问题:我正在尝试查询下面绘制的表结构。 关于此表结构,要注意的唯一奇怪的事情是,问题有选择,然后有子问题,然后有子选择。 您只能假设两个级别的问题->选择->问题->选择。
SiteSurveyQuestion
|
+---Site
|
+---Survey
|
+---Question
|
+---Choice
+---Choice
+---Choice
|
+---Question
+---Question
+---Question
|
+---Choice
+---Choice
+---Choice
我已经尝试了所有我想尝试的东西 。
在映射中,我尝试了一堆作为.Not.LazyLoad()
的引用字段, .Not.LazyLoad()
没有真正的成功。
我还尝试通过添加.Fetch()
和.FetchMany()
以及.ThenFetchMany()
许多组合来修改查询,甚至尝试运行多个.ToFuture()
查询。 这些确实对SQL查询做出了真正的改变,但是并没有改变我想要的最终结果。
这个查询被归结为“让我在此站点上获得此调查的所有问题列表,包括所有子问题”。 这是查询:
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
-- */
这是一种在访问集合时可以避免从数据库获取数据的方法:
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();
上面的内容显然不是理想的,因为它加载了所有问题和选择,但是您需要通过在Question
设置SiteSurveyQuestion
的集合来将一个Question
链接到SiteSurveyQuestion
,以SiteSurveyQuestion
需要SiteSurveyQuestion
进行过滤。 这样,您就可以根据调查和站点ID加载所需的问题和选择。
1 + N的解决方案将建立在特殊的NHibernate 优化功能的基础上(让我引述一下)
NHibernate可以有效地使用批处理获取,也就是说,如果访问了一个代理(或集合),NHibernate可以加载多个未初始化的代理。批处理获取是对惰性选择获取策略的优化。有两种方法可以调整批处理获取:类和收集级别。
批量获取类/实体更容易理解。 假设您在运行时遇到以下情况:在ISession中加载了25个Cat实例,每个Cat都有对其所有者Person的引用。 Person类与代理lazy =“ true”映射。 如果您现在遍历所有cat并在每个cat上调用cat.Owner,则NHibernate默认情况下将执行25条SELECT语句,以检索代理的所有者。 您可以通过在Person映射中指定一个批处理大小来调整此行为:
<class name="Person" batch-size="10">...</class>
NHibernate现在将仅执行三个查询,模式为10、10、5。
您还可以启用集合的批量提取。 例如,如果每个人都有一个懒惰的Cat集合,并且当前在ISesssion中加载了10个人,则对所有人进行迭代将生成10个SELECT,每个对person.Cat的调用都会选择一个。 如果您在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...){}
,则会遇到麻烦。 以上内容无法使用-在会话之外。 所有必须通过会话填充。 那么如何解决呢?
最好的办法是添加一些方法来遍历对象并将其转换为某些“ DTO”
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()
内部.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);
}
使用Radim关于在上面设置批量大小的答案,再加上对Site和Survey实体的明确的急切加载,可以让我在前一批中先后进行6次合理的查询,在访问第一个子目录时在第二批中另外进行2次查询。题。
N + 1已变为N / 300 +1,最终成为... 2个批次中总共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。
如果有人可以改善此答案,请这样做。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.