在 NHibernate 中使用 Linq 时出现重复和不必要的连接

发布于 2025-01-05 15:24:51 字数 3400 浏览 2 评论 0原文

基本上我在这个 linq-to-nhibernate-products-unnecessary 中遇到了 Linq 提供程序的相同问题-joins

List<Competitions> dtoCompetitions;
dtoCompetitions = (from compset in session.Query<FWBCompetitionSet>()
                    where compset.HeadLine == true 
                    && compset.A.B.CurrentSeason == true
                   select (new Competitions
                           {
                                       CompetitionSetID = compset.CompetitionSetID,
                                       Name = compset.Name,
                                       Description = compset.Description,
                                       Area = compset.Area,
                                       Type = compset.Type,
                                       CurrentSeason = compset.A.B.CurrentSeason,
                                       StartDate = compset.StartDate
                            }
                    )).ToList();

这会导致其生成的 SQL 中出现重复的连接

SELECT fwbcompeti0_.competitionsetid AS col_0_0_,
   fwbcompeti0_.name             AS col_1_0_,
   fwbcompeti0_.DESCRIPTION      AS col_2_0_,
   fwbcompeti0_.area             AS col_3_0_,
   fwbcompeti0_.TYPE             AS col_4_0_,
   fwbseason3_.currentseason     AS col_5_0_,
   fwbcompeti0_.startdate        AS col_6_0_
FROM   fwbcompetitionset fwbcompeti0_
       INNER JOIN A fwbcompeti1_
         ON fwbcompeti0_.competitionseasonid = fwbcompeti1_.competitionseasonid
       INNER JOIN A fwbcompeti2_
         ON fwbcompeti0_.competitionseasonid = fwbcompeti2_.competitionseasonid
       INNER JOIN B fwbseason3_
         ON fwbcompeti2_.seasonid = fwbseason3_.seasonid
WHERE  fwbcompeti0_.headline = @p0
       AND fwbseason3_.currentseason = @p1  

,请注意这些连接,它们完全重复,并且还会影响我的 SQL Server 的性能。

       INNER JOIN A fwbcompeti1_
         ON fwbcompeti0_.competitionseasonid = fwbcompeti1_.competitionseasonid
       INNER JOIN A fwbcompeti2_
         ON fwbcompeti0_.competitionseasonid = fwbcompeti2_.competitionseasonid

Update1

在NHibernate 3.2中,这个LiNQ bug仍然有效,而且我找不到简单合理的Linq解决方案。 所以我使用 QueryOver + JoinAlias + TransformUsing 完成这项工作,对我来说非常完美。

FWBCompetitionSet compset = null;
FWBCompetitionSeason compseason = null;
FWBSeason season = null;
IList<Competitions> dtoCompetitions;
dtoCompetitions = session.QueryOver<FWBCompetitionSet>(() => compset)
.JoinAlias(() => compset.FWBCompetitionSeason, () => compseason)
.JoinAlias(() => compseason.FWBSeason, () => season)
.Where(() => compset.HeadLine == true)  
.And(() => season.CurrentSeason == true)
.SelectList(
list => list
.Select(c => c.CompetitionSetID).WithAlias(() => compset.CompetitionSetID)
.Select(c => c.Name).WithAlias(() => compset.Name)
.Select(c => c.Description).WithAlias(() => compset.Description)
.Select(c => c.Area).WithAlias(() => compset.Area)
.Select(c => c.Type).WithAlias(() => compset.Type)
.Select(c => season.CurrentSeason).WithAlias(() => season.CurrentSeason)
.Select(c => c.StartDate).WithAlias(() => compset.StartDate)
)
.TransformUsing(Transformers.AliasToBean<Competitions>())
.List<Competitions>();

Basically I crossed the same problem of Linq provider in this linq-to-nhibernate-produces-unnecessary-joins

List<Competitions> dtoCompetitions;
dtoCompetitions = (from compset in session.Query<FWBCompetitionSet>()
                    where compset.HeadLine == true 
                    && compset.A.B.CurrentSeason == true
                   select (new Competitions
                           {
                                       CompetitionSetID = compset.CompetitionSetID,
                                       Name = compset.Name,
                                       Description = compset.Description,
                                       Area = compset.Area,
                                       Type = compset.Type,
                                       CurrentSeason = compset.A.B.CurrentSeason,
                                       StartDate = compset.StartDate
                            }
                    )).ToList();

Which leads to duplicated join in its generated SQL

SELECT fwbcompeti0_.competitionsetid AS col_0_0_,
   fwbcompeti0_.name             AS col_1_0_,
   fwbcompeti0_.DESCRIPTION      AS col_2_0_,
   fwbcompeti0_.area             AS col_3_0_,
   fwbcompeti0_.TYPE             AS col_4_0_,
   fwbseason3_.currentseason     AS col_5_0_,
   fwbcompeti0_.startdate        AS col_6_0_
FROM   fwbcompetitionset fwbcompeti0_
       INNER JOIN A fwbcompeti1_
         ON fwbcompeti0_.competitionseasonid = fwbcompeti1_.competitionseasonid
       INNER JOIN A fwbcompeti2_
         ON fwbcompeti0_.competitionseasonid = fwbcompeti2_.competitionseasonid
       INNER JOIN B fwbseason3_
         ON fwbcompeti2_.seasonid = fwbseason3_.seasonid
WHERE  fwbcompeti0_.headline = @p0
       AND fwbseason3_.currentseason = @p1  

Notice these joins, which are totally duplicated and also affect my SQL Server's performence.

       INNER JOIN A fwbcompeti1_
         ON fwbcompeti0_.competitionseasonid = fwbcompeti1_.competitionseasonid
       INNER JOIN A fwbcompeti2_
         ON fwbcompeti0_.competitionseasonid = fwbcompeti2_.competitionseasonid

Update1

In the NHibernate 3.2, this LiNQ bug is still valid, and I could not find a simple and reasonable Linq solution.
So I used QueryOver + JoinAlias + TransformUsing finishing the job, workds perfect to me.

FWBCompetitionSet compset = null;
FWBCompetitionSeason compseason = null;
FWBSeason season = null;
IList<Competitions> dtoCompetitions;
dtoCompetitions = session.QueryOver<FWBCompetitionSet>(() => compset)
.JoinAlias(() => compset.FWBCompetitionSeason, () => compseason)
.JoinAlias(() => compseason.FWBSeason, () => season)
.Where(() => compset.HeadLine == true)  
.And(() => season.CurrentSeason == true)
.SelectList(
list => list
.Select(c => c.CompetitionSetID).WithAlias(() => compset.CompetitionSetID)
.Select(c => c.Name).WithAlias(() => compset.Name)
.Select(c => c.Description).WithAlias(() => compset.Description)
.Select(c => c.Area).WithAlias(() => compset.Area)
.Select(c => c.Type).WithAlias(() => compset.Type)
.Select(c => season.CurrentSeason).WithAlias(() => season.CurrentSeason)
.Select(c => c.StartDate).WithAlias(() => compset.StartDate)
)
.TransformUsing(Transformers.AliasToBean<Competitions>())
.List<Competitions>();

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

落花随流水 2025-01-12 15:24:51

又一个编辑:

我想我终于知道发生了什么事。 LINQ to NHibernate 提供程序似乎无法将关联从目标表导航到源表,并且每次遇到此类关联时都会生成单独的联接。

由于您没有提供映射,因此我使用了 linq-to-nhibernate- 中的映射产生不必要的连接。该模型有一个文档,其中包含一个作业和许多翻译单元。每个TranslationUnit有许多翻译实体。

当您尝试根据作业查找翻译时,您将以相反的顺序遍历关联,并且 LINQ 提供程序会生成多个联接:一个用于翻译 -> 另一个联接。 TranslationUnit 和一个用于 TranslationUnit to Document 的。

此查询将生成冗余联接:

session.Query<TmTranslation>()
          .Where(x => x.TranslationUnit.Document.Job == job)
          .OrderBy(x => x.Id)
          .ToList();

如果将导航顺序反转为 Document ->翻译单元->翻译后,您会得到一个不会产生任何冗余连接的查询:

var items=(from doc in session.Query<Document>()
        from tu in doc.TranslationUnits
            from translation in tu.Translations
       where doc.Job ==job                        
       orderby translation.Id
       select translation).ToList();

鉴于这种怪异,QueryOver 似乎是一个更好的选择。

先前编辑:

我怀疑罪魁祸首是compset.ABCurrentSeason。第一个连接表 (fwbcompeti1_) 返回 A.B,而接下来的两个表(fwbcompeti2_ 和 fwbseason3_)用于返回 A.B。 LINQ to NHibernate 提供程序似乎没有猜测 A 没有在其他地方使用,并且无法将其从生成的语句中删除。

尝试通过将 select 中的 CurrentSeason = compset.ABCurrentSeason 替换为 CurrentSeason = true 来帮助优化器,因为您的 where 语句仅返回 CurrentSeason == true 的项目。

编辑:我的意思是像这样更改查询:

List<Competitions> dtoCompetitions;
dtoCompetitions = (from compset in session.Query<FWBCompetitionSet>()
                   where compset.HeadLine == true 
                   && compset.A.B.CurrentSeason == true
                   select (new Competitions
                       {
                                   CompetitionSetID = compset.CompetitionSetID,
                                   Name = compset.Name,
                                   Description = compset.Description,
                                   Area = compset.Area,
                                   Type = compset.Type,
                                   CurrentSeason = true,
                                   StartDate = compset.StartDate
                        }
                )).ToList();

我只需将值 compset.ABCurrentSeason 替换为 true

Yet Another Edit:

I think I finally found out what's going on. It seems that the LINQ to NHibernate provider has trouble navigating associations from the target to the source table and generates a separate join each time it encounters such an association.

Since you don't provide your mapping, I used the mapping from linq-to-nhibernate-produces-unnecessary-joins. This model has a Document with one Job and many TranslationUnits. Each TranslationUnit has many Translation entities.

When you try to find a Translation based on a Job, you are traversing the associations in the reverse order and the LINQ provider generates multiple joins: one for Translation -> TranslationUnit and one for TranslationUnit to Document.

This query will generate redundant joins:

session.Query<TmTranslation>()
          .Where(x => x.TranslationUnit.Document.Job == job)
          .OrderBy(x => x.Id)
          .ToList();

If you reverse the navigation order to Document -> TranslationUnit -> Translation, you get a query that doesn't produce any redundant joins:

var items=(from doc in session.Query<Document>()
        from tu in doc.TranslationUnits
            from translation in tu.Translations
       where doc.Job ==job                        
       orderby translation.Id
       select translation).ToList();

Given this quirkiness, QueryOver seems like a better option.

Previous Edit:

I suspect the culprit is compset.A.B.CurrentSeason. The first joined table (fwbcompeti1_) returns A.B while the next two (fwbcompeti2_ and fwbseason3_) are used to return A.B. The LINQ to NHibernate provider doesn't seem to guess that A is not used anywhere else and fails to remove it from the generated statement.

Try to help the optimizer a little by replacing CurrentSeason = compset.A.B.CurrentSeason with CurrentSeason = true from the select, since your where statement returns only items with CurrentSeason == true.

EDIT: What I mean is to change the query like this:

List<Competitions> dtoCompetitions;
dtoCompetitions = (from compset in session.Query<FWBCompetitionSet>()
                   where compset.HeadLine == true 
                   && compset.A.B.CurrentSeason == true
                   select (new Competitions
                       {
                                   CompetitionSetID = compset.CompetitionSetID,
                                   Name = compset.Name,
                                   Description = compset.Description,
                                   Area = compset.Area,
                                   Type = compset.Type,
                                   CurrentSeason = true,
                                   StartDate = compset.StartDate
                        }
                )).ToList();

I simply replace the value compset.A.B.CurrentSeason with true

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文