繁体   English   中英

通过没有N + 1的NHibernate查询-包括示例

[英]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问题? 我将对以下任何一项感到满意

  • (首选)在类映射中的修复程序,它渴望加载所有内容
  • (第二选择)使用LINQ提供程序在查询中添加获取或查询提示
  • (第三选择)上面的组合
  • (第四选择)被告知这是不可能的,并且是NHibernate的局限性
  • (第五选择)HQL解决方案

我不想要HQL解决方案,因为我不会从映射和/或查询的错误中了解到任何信息- 我感觉好像缺少了一些基本信息 ,甚至不知道在哪里查找。


样本说明:

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

```

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 优化功能的基础上(让我引述一下)

19.1.5。 使用批量提取

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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM