在没有N + 1的情况下通过NHibernate查询 - 包括样本

时间:2015-02-16 20:53:53

标签: c# nhibernate fluent-nhibernate

我遇到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问题?我会满意以下任何一个

  • (首选)在类映射中修复以急切加载所有内容
  • (第二选择)使用LINQ提供程序使用fetch或查询提示收缩查询
  • (第三选择)上述
  • 的混合物
  • (第四选择)告诉它不可能和NHibernate的限制
  • (第五选择)HQL中的解决方案

我不想要一个HQL解决方案,因为我无法了解我的映射和/或查询错误 - 我觉得我错过了一些基本的东西,我甚至不知道在哪里看。


示例说明:

  1. 将SQL安装脚本复制并粘贴到本地SQL Server实例中,然后运行它。
  2. 创建一个测试项目(或者如果你懒惰,使用你现有的测试项目)并将NHibernate和Fluent NHibernate的nuget包添加到项目中。
  3. 运行测试。你应该看到:
    1. 由NHibernate运行生成的SQL
    2. 测试结果。
  4. 修复映射/查询,直到N + 1消失 - 你会知道当你第一次看到一堆SQL脚本输出时,然后:
  5. ```

    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
    
    
    
    
    
    
    
    
    
    
    
    
    -- */
    

3 个答案:

答案 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的集合,将SiteSurveyQuestionSiteSurveyQuestion相关联过滤它就像你想要的那样。这样,您就可以根据您的调查和网站ID加载所需的问题和选择。

答案 1 :(得分:2)

1 + N的解决方案将建立在特殊的NHibernate 优化功能之上(让我举一点介绍)

19.1.5. Using batch fetching

  

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是可以接受的。

如果有人可以改进这个答案,请做。